60
题了1、堆和栈的区别
2、什么是野指针?产生的的原因?
3、进程间通信的方法
4、程序中的内存分配方法
5、malloc 和 new 的区别
6、为什么局部变量未初始化时,每次初始化的结果是不确定的?
7、C语言中volatile的作用和用法
8、编译有几个阶段?每个阶段都了什么事情?
9、描述一下数组指针和指针数组
10、指针和引用的区别
11、内联函数和宏函数的区别
12、在什么情况下栈会溢出?如何避免?
13、深拷贝和浅拷贝的区别
14、从一个源文件到可执行文件的过程?
15、#include<>和#include" "的区别
16、gdb调试
17、C++在什么情况下必须要初始化列表?
18、C语言中的结构体怎么定义节省空间?
19、STM32 中断是怎么进入到中断服务程序的?
20、结构体和共用体的区别?
21、说说C/C++中static关键字?
22、说说C/C++中const关键字?
23、volatile的作用
24、声明和定义的区别?
25、赋值和赋初值的区别?
26、局部变量和全局变量是否可以重名?
27、如何引用一个已经定义过的全局变量(外部变量)?
28、局部变量和全局变量有什么区别?
29、const 与 #define 相比有何不同?
30、数组与指针的区别?
31、队列和栈的区别?
32、堆(Heap)与栈(Stack)的区别?
33、static全局变量和全局变量的区别?
34、static函数与普通函数的区别?
35、什么是预处理?何时需要预处理?
36、用预处理指令#define声明一个常量来表明一年中有多少秒(忽略闰年问题)
37、C++程序中调用C编译器后的函数,为什么要加上extern “C”
38、不能做switch()的参数类型的是?
39、实现x是否为2的所感次幂的判断?
40、说说你对中断服务子程序(ISR)的理解?
41、请解释函数的空间复杂度和时间复杂度的含义。
42、请解释函数的扇入和扇出的概念。
43、 请描述函数指针和指针函数?
44、定义一个变量 array_fun,它是一个数组,数组含有10个函数指针,函数指针指向的函数包含有1个整型参数和一个字符型参数,并且有一个整型返回值。
45、给定一个变量a,写段代码,第一个设置a的bit3,第二个清除a的bit3
46、以下关于指针说法正确的是?
47、若有函数 void fun(double a[], int *n){……}
48、ioremap函数的作用?
49、mmap函数的做作用?
50、常用的Linux同步互斥机制?
51、下面定义了一个枚举型,其SUNDAY的值为?
52、使用C语言定义一个名为Student的结构体,其中包含以下信息:
53、初始化下面的数组指针和指针数组
54、unsigned int a = 0x11223344、设置变量a的bit1和清除a的bit2
55、下面交换函数(swap)中有什么问题?
56、写一个“标准"宏 MIN ,这个宏输入两个参数并返回较小的一个
57、说明break、continue和return的不同
58、h头文件中的 ifndef / define / endif 的作用?
59、sizeof 和 strlen 的区别?
60、C和C++中的struct有什么不同?
1、栈由系统自动分配,而堆是人为申请开辟;
2、栈获得的空间较小,而堆获得的空间较大;
3、栈由系统自动分配,速度较快,而堆一般速度比较慢;
4、栈是连续的空间,而堆是不连续的空间。
野指针的指向的位置是随机,是不正确的指针。
原因:
1、创建指针时没有对指针进行初始化
2、释放指针后没有将指针指向的内存置NULL
char *p = (char *)malloc(sizeof(10));
free(p);
p = NULL;
1、管道(有名管道和无名管道)
2、信号量
3、消息队列
4、共享内存
5、信号灯集
6、网络套接字(socket)
1、连续分配方式
2、基本分页存储管理方式
3、基本分段存储管理方式
4、段页式分配方式
1、malloc 是C函数,new 是C++关键字
2、申请的内存所在位置不同:
malloc 函数是从堆空间上动态分匹配内存;new 操作符是从自由内存上为对象动态分配内存空间。
3、返回类型安全性不同:
malloc 内存分配成功则返回(void *)类型,需要强制转换为我们需要的类型;new 成功分配内存则返回对象类型的指针,类型严格与对象匹配,无需进行类型转换,故new 是符合安全性的操作符。
定义局部变量时其实就是再栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。局部变量是存放在栈区中,由系统自动申请和释放,里面的数据可能没有及时的被释放,值是脏的,是不确定的。
作用:
1、告诉编译器不要优化volatile修饰的变量,这个变量可能会有意想不到的变化。
2、每次读取被volatile修饰的变量时都要从内存中重新读取,而不是从缓冲区和寄存器中读取
用法:
1、状态寄存器
2、多进程访问共享变量
3、终端服务子程序访问的非自动变量
1、词法分析:读入源程序,对源程序的字符流进行扫描和分解,识别出单词
2、语法分析:将单词序列分成不同的语法短语
3、语义分析:检查有没有语义错误
4、中间代码生成:将源程序变成一种内部表示形式
5、中间代码优化:对中间代码进行优化,使之在时间和空间上更加节省
6、目标代码生成:生成汇编指令
1、数组指针:是一个指针,指向一个数组(指向数组的指针)。
int (*p)[100];
//一般是用来指向二维数组
2、指针数组:是一个数组,数组每个元素都是指针。
int* p[10];
一般用有没有括号()
来区分。
一个有 10 个指针的数组,该指针是指向一个整型数:
int *ptr_array[10];
一个指向有 10 个整型数数组的指针:
int (*ptr_to_array)[10];
1、指针是实体,占用内存空间;引用是别名,与变量共享内存空间。
2、指针不用初始化或初始化未NUL;引用定义时必须
初始化。
3、指针中途可以修改指向;引用不可以;
4、指针可以指向NULL;引用不能为空。
5、sizeof(指针)计算的是指针本身的大小;而sizeof(引用)计算的是它引用的对象的大小。
6、如果返回的是动态分配的内存或对象,必须
使用指针,使用引用会发生内存泄漏。
7、指针使用时需要解引用;引用使用时不需要解引用
8、指针有二级指针,多级指针;引用没有二级引用,没有多级引用。
相同的点:
二者都是通过将函数调用替换成完整的函数体,相比函数调用的时间和空间开销而言,二者都提高了效率。
不同点:
1、宏函数不是函数,而内联函数是函数,因此内联函数可以调试,而宏函数不可以。
2、宏函数的代码展开阶段在预处理阶段,而内联函数在编译阶段,因此内联函数有类型安全检查,而宏函数没有。
3、内联函数可以作为类的成员函数,作为类的成员函数可以访问类中所有的成员(包括private成员),而宏定义不行。
溢出:
1、局部数组过大
2、递归调用层次过多
3、指针或数组越界
避免:
1、增大栈空间
2、改用动态分配内存空间,使用堆(heap)而不是栈(stack)。
1、浅拷贝:只是对指针进行拷贝,拷贝后的两个或多个指针指向原来的同一内存空间。
2、深拷贝:不但对指针进行了拷贝、而且对指针的指向的内容进行了拷贝,两个或多个指针指向不同的内存空间。
1、
预处理
:对带#
的语句进行文本替换、头文件展开、注释删除等,该阶段不会进行语法检查,将产生.i
文件gcc hello.c -E helo.i
2、编译
:将预处理的结果编译成汇编语言,进行语法检查,将.i
文件转换为.s
文件,gcc hello.i -S hello.s
3、汇编
:将汇编语言转换为二进制机器码,产生.o
文件,gcc hello.s -c hello.o
4、链接
:生成可执行文件gcc hello.o -o app
1、#include <>:是在系统下标准库中查找,Linux系统下的标准库在
/usr/include
2、#include " ":系统首先在当前目录中查找,找不到 ,则在标准库中查找。
1、编译代码并启动
gcc -g test.c -o app //-g参数是告诉编译器编译时加入调试信息
gdb -q app //gdb启动时加上-q会屏蔽版本等无用的信息
2、调试
①查看源码
list 5,20 //显示第5行到第20行的代码
list init //显示init函数周围的代码,实现范围是和list参数有关
list test.c :5,15 //显示源文件test.c第5行到第15行代码,一般用于调试多源文件
②设置断点
break 10 //在当前文件中的第10行设置为断点
break test.c :15 //在其他文件中的15行设置为断点
b init //在函数init()处设置断点
info break //查看断点信息
③运行
run
④单步执行
step
⑤查看变量值
print count
⑥查看堆栈信息
breaktrace
⑦退出gdb
quit
1、常量成员变量:
如果类中有常量成员变量(被const
修饰),它们必须在构造函数的初始化列表中进行初始化,因为常量成员变量无法在构造函数体内赋值。
class MyClass {
private:
const int myConst;
public:
MyClass(int value) : myConst(value) {
// 构造函数体内无法给常量成员变量赋值
}
};
2、引用成员变量:
引用成员变量也需要在初始化列表中初始化,因为引用必须在创建时初始化,而一旦初始化,它将一直引用同一个对象。
class MyClass {
private:
int& myRef;
public:
// 错误的方式,无法在构造函数体内初始化引用
// MyClass(int value) { myRef = value; }
// 正确的方式,在初始化列表中初始化引用
MyClass(int value) : myRef(value) {}
};
3、成员对象是类
类型:
如果类中有其他类类型的成员对象,最好在初始化列表中调用其构造函数进行初始化,以确保成员对象在构造函数体内使用之前已经被正确构造。
class Class1 {
public:
Class1(int value) {
// 构造函数体
}
};
class Class2 {
private:
Class1 c1;
public:
// 正确的方式,在初始化列表中初始化成员对象
Class2(int value) : c1(value) {
// 构造函数体
}
};
1、在保证值域足够的情况下,用小字节变量代替大字节变量,如:用
short
代int
2、将各个成员按其所占字节数从小到大声明(字节对齐)、如果某变量的访问频率较高时可以将它放在前面。
在STM32中,为了区分不同的中断,每个设备有自己的中断号。系统有
0-255
一个256
个中断号,系统有一张中断向量表,用于存放256个中断服务程序入口地址。每个中断入口地址对应一段代码,即中断服务程序。
1、内存分配方式不同:
在结构体中,每个成员都有自己的内存空间,每个成员可以存储不同类型的数据。
在共用体中,所用成员共享一块内存空间,共用体的大小是它最大成员的大小。
2、成员访问:
在结构体中,可以访问结构体中所有的成员,每个成员都有自己的地址。
在共用体中,职能访问当前被赋值的成员,因为多有成员共享相同的内存。
改变存储区域 和 限制作用域
①修饰局部变量: 静态局部变量
存储区域:栈区–>数据段 作用域:局部作用域
static修饰的静态局部变量只执行一次,其会改变局部变量的存储位置,使得局部变量的生命周期得到延长,直到程序结束后才释放。
②修饰全局变量: 静态全局变量
限制作用域:外部链接–>内部链接
static修饰全局部变量时,变成了静态全局变量,会改变全部变量的链接性,从而使得全局变量作用域变为只能在本文本中访问,extern关键字也无法调用。
③修饰函数:
被static修饰的函数,叫静态函数。同样是只能在本文不使用,不能被外部其他问价调用。
④C++中还可以修饰类数据成员:
类的静态数据成员是类的所有实例共享的成员。它们必须在类外部进行定义和初始化。
⑤C++中还可以修饰类成员函数: 静态成员函数
类中声明的静态成员函数是属于整个类的,而不是类的实例。它们可以通过类名直接调用,而不需要创建类的实例。(不用通过对象调用)
只要一个变量前用const来修饰,就意味着该变量里的时数据只能被访问,而不能被修改,也就是意味着“只读”(readonly)
①修饰变量:
此时变量只能使用不可修改,直接修改(编译报错)与修改地址(程序异常)都不行
const int a = 20 ;
②修饰指针变量类型(常量指针):
const修饰指针变量的类型,不可以修改指针指向地址里面的内容,可以修改指针的指向。
int c = 100;
const int *p = &c;
int d =999;
p = &d;//可以修改指针的指向
③修饰指针变量(指针常量):
可以修改指针指向地址里面的内容,但是不能修改指针的指向
int c = 200;
int * const p = &c;//const修饰指针,即指针常量
*p = 888;//可以修改指针指向地址里的内容
④既可以修饰指针变量类型又可以修饰指针:
既不能修改指针指向地址里面的内容,也不能修改指针的指向。
int c = 200;
const int * const p = &c;//即修饰指针变量类型又修饰指针变量(两者都不能修改)
⑤修饰返回值:
如果给以“指针传递”方式的函数返回值加上const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值之只能给被赋给加上const 修饰的同类型指针。
⑥修饰函数形参:
如果参数作为输入参数,可以防止数据被改变,起到保护作用,增加程序的健壮性。
⑦C++中还可以修饰类成员变量:
对成员变量的修饰与普通的变量差不多,都是只读,但是在类中的const变量只能通过构造函数的初始化列表来进行初始化。
⑧C++中还可以修饰类成员函数:
首先const修饰类成员函数保留了它修饰的函数的用法,其次,增加了成员函数的声明后面添加const的用法。
void show(const int* p) const ;
⑨修饰类对象 类似于修饰变量:
在实例化对象时,被const修饰,该对象只能访问类中被const修饰的从原变量和被const修饰成员函数。
volatile关键字声明的变量,告诉编译器不要对该变量进行优化,被编译器对该变量不再优化,从而可以提供对特殊地址的访问。被volatile关键字声明的关键字变量,该变量的作用是防止编译器直接从CPU寄存器中的取值,或者在缓冲区中取值,而要在内存中重新取值。
1、声明:不开辟内存空间
2、定义:开辟内存空间,并且同时进行赋值
赋值:将一个变量的当前值设置为另一个值,
int x; x = 10;
赋初值:在声明变量时的同时进行初始化,int x = 5;
可以。局部会覆盖全局
1、引用头文件
2、、extern关键字
注:如果用引用头文件的方式来引用某个文件的全局变量,假定你将该变量名写错了,那么在编译阶段会报错;如果你用extern来引用时,你还是写错了该变量名,那么在编译阶段不会报错,,而是在链接时会报错。
1、作用域不同:
全局变量:整个程序
局部变量:当前函数或循环体内
2、内存存储方式不同:
全局变量:全局数据区
局部变量:栈区
3、生命周期不同:
全局变量:和主程序生命周期一样,程序的销毁而结束
局部变量:函数内部或循环内部,随着函数的退出或者是循环的退出而结束
相同点:两点点都是用来定义常量
不同点:
1、处理阶段不同:
#define 在预处理阶段展开,而 const 在编译阶段阶段
2、类型检查:
#define 只是简单的文本替换,没有类型检查,而 const 有对应的数据类型,要进行类型判断。
3、存储方式不同:
#define 宏定义时没有分配内存空间,而const 定义时会在内存中分配空间
1、数组名是一个地址常量,不可以被赋值,也不可以自增(++)自减(–),不能偏移。
2、指针是变量,可以++ – 可以偏移
对于数组来说,保存在一片连续内存单元中的,而数组名就是这片连续内存单元的首地址
队列:先进先出(FIFO)
栈:后进先出(LIFO)
1、Stack空间是由系统自动分配、自动释放。Heap空间是由手动分配和释放
2、Stack空间有限,Heap很大,开辟相对自由。
在堆中开辟空间C语言是malloc函数实现,在C++中是new操作符实现
相同点:
1、生命周期:
都是从程序启动到结束
2、内存分配:
都存储在全局数据区
不同点:
1、作用域:
普通全局变量:在整个程序中都可见和可访问。可以被程序中的所有函数调用和其他文件引用
static全局变量:作用现在在声明它的文件内,不被其他文件访问和引用。
2、链接性:
普通全局变量:具有外连接性,可以被外部的其他文件访问和引用
static全局变量:只有内链接性,只能在声明的文件内访问
3、初始化:
普通全局变量:会被局部变量覆盖。
static全局变量:只初始化一次
static函数在内存中只有一份,普通函数在调用中维持一分拷贝。(跟333点性质大概一致)
## 35、什么是预处理?何时需要预处理? >程序编译过程中的第一个阶段,它在程序编译之前进行,进行宏替换、文件包含处理、条件编译(`#if #else #ifdef #ifndef #elif #endif`)等编译指令、删除注释减轻文件大小、多余空格处理。为程序后面的编译、汇编链接做准备。 >用处: >不经常改动的大型代码体:避免多次编译都要重新处理头文件,加速编译过程 >程序由多个模块组成。
#define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL
//UL表示无符号长整形
在C++中支持函数重载,而在C语言中不支持函数重载。函数被C++编译器编译后在库中的函数名与C语言的不同。所以C++编译器提供了C连接交换指定符号extern “C” 来解决名字匹配问题。
实型(浮点型)
bool isPowerOfTwo(int x){
return (x > 0) && ((x & (x - 1)) == 0);
}
1、ISR 不能有返回值
2、ISR 不能传递参数
因为他们都是由硬件中断触发,不像普通函数那样由程序显示调用,中断程序目标是尽快响应中断请求,执行必要操作。
3、中断执行时间短、不涉及耗时操作尽量避免输出等原则。
4、避免使用不可重入函数或类型变量,比如浮点类型具有不可重入性、printf()函数具有重入性,但是printf函数相对来说代码量庞大,时间上会消耗,而中断服务程序执行时间要相对很短。
时间复杂度:
运行时间和输入数据之间的关系。执行所需的时间。
表示法:O(1) O(log n) O(n) O(n log n) O(n^2)
空间复杂度:
执行过程中所需的存储空间与输入之间的关系。执行所需的额外空间。
表示法:O(1) O(n) O(n^2)
扇入(Fan-in):表示一个模块或函数被其他模块或函数调用的次数(被动)
扇出(Fan-out):表示一个模块或函数调用其他模块或函数的次数(主动)
函数指针: 指向函数的指针。它是一个存储函数地址的变量,可以用于调用函数。
// 声明一个函数指针
int (*funcPtr)(int, int);
// 将函数指针指向某个函数
funcPtr = &add;
// 通过函数指针调用函数
int result = funcPtr(2, 3);
指针函数: 返回指针类型的函数
// 声明一个指针函数
int* pointerFunction(int x, int y) {
// 返回一个整数指针
int* result = malloc(sizeof(int));
*result = x + y;
return result;
}
一个指向函数的指针,该函数有一个整型参数并返回一个整型数:
int (*func_ptr)(int);
一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数:
int (*func_ptr_array[10])(int);
int (*array_fun[10])(int, char);
#define BIT3 (0x1 << 3)
int main(){
static int a;
a |= BIT3; //将a的高三位置1
a &= ~BIT3; //将a的第三位置0
}
A:int (*p)[5]是具有5个元素的指针数组
B:int *p[5]是指向元素个数为5的数组的指针
C:定义uint32_t *p=0x02000000,则p++达式的值为0x02000004
D:int(*p)(int,int),p为指向函数的指针变量
①指针数组和数组指针
指针数组:是一个数组,其每个元素都是一个指针。
int *p[10]; //是一个包含 10个元素的数组,每个元素都是一个指向 int 类型的指针。
数组指针:是一个指针,它指向一个数组。与指针数组不同,数组指针指向的数组元素的数据类型是相同的。int (*p)[10]; //是一个指针,它指向一个包含10个int元素的数组。
void fun(double a[], int *n){……}
A:调用fun函数时只有数组执行按值传送,其他实参和形参之间执行按地址传送
B:形参a和n都是指针变量
C:形参a是一个数组名,n是指针变量
D:调用fun函数时将把double型实参数组元素一一对应地送给形参a数组
答案:C
A:数组是以地址传递
B、C:a是一个数组名,但是它将被解释为指向数组首元素的指针
D:调用fun函数时,实际上将double型数组的地址传递给a而不是数组元素。
ioremap是一个Linux内核中的函数
作用:将物理地址映射到内核虚拟地址空间中
场景:通常用于与硬件设备通信,如:访问I/O端口或内存映射的设备寄存器
void __iomem *ioremap(phys_addr_t offset, unsigned long size);
//offset:物理地址的偏移量。
//size:要映射的内存大小。
mmap是一种系统调用函数
作用:将文件或其他对象映射到进程的地址空间中
场景:文件、进程通信和共享内存
①互斥锁(Mutex)
②读写锁(ReadWrite Lock)
③自旋锁(Spin Lock)
④条件变量
⑤信号量(Semaphore)
#include
enum Day{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};
默认情况下第一个枚举元素的值为 0 ,后续的元素会比前面的元素的值大 1
答案:0
①姓名(name):一个包含最多30个字符的字符串。
②学号(studentID):一个整数。
③三门课程的成绩(grades):使用一个包含三个整数的数组。
struct Student {
char name[31];
int studentID;
int grades[3];
}
数组指针
和指针数组
int (* pa)[3]
---->数组指针
int arr[3] = {1, 2, 3};
int (*pa)[3] = &arr;
int *pb[3]
---->指针数组
int a = 1, b = 2, c = 3;
int *pb[3] = {&a, &b, c&};
①设置变量a的bit1,其他位不变。
unsigned int a = 0x11223344;
a |= (1 << 1);
(1 << 1)表示将二进制数0000001左移一位得到0000010
②清除a变量的bit2,其他位不变。
unsigned int a = 0x11223344;
a &= ~(1 << 2);
00000001左移两位得到00000100这时候只有第 2 位为 1 接着取反 ~(1 << 2)就将只有第二位的1变为0,其他位变为 1 任然不变
void swap(int *p, int *q){
int *temp;
*temp = *p;
p = *q;
q = *temp;
}
首先正确代码为:
void swap(int *p, int *q){
int temp;
temp = *p;
*p = *q;
*q = temp;
}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
①
break
(在循环或者是switch语句中使用):立即终止当前循环或switch语句,跳出当前循环或switch语句体。
②continue
(在循环中使用):跳过当前循环体中的余下的代码,直接进入下一次迭代(循环)。
③return
(在函数中使用):用于结束函数的执行,并可选择返回一个值给调用方。
防止头文件重复包含重复展开,起到头文件保护和宏定义保护的作用
sizeof
和 strlen
的区别?sizeof
:计算所占的字节大小、在编译阶段进行
strlen
:字符串的具体长度(即字符个数)只针对字符串
、在程序运行时进行
①C中的struct不可以包含成员函数,而C++中则可以
②C++中的struct和class的主要区别在于默认的权限不同,struct默认的是public、而class默认的是private
③C语言中没有类的感念,所以struct中没有继承,而C++中的struct有继承
1、什么是面向对象?
2、三大特性中的多态?
3、你对抽象类的理解,怎么定义抽象类?
4、
面向对象是一种编程思想,将现实的问题抽象成一个对象,每个对象都有自己的属性和行为(方法),通过调用该对象的属性和方法来解决来对应的问题。
—————————————————————
面向对象会有对象和类,又会有封装、继承和多态
对象:就是将一个程序中的事物看作是互相关联的对象,每个对象有自己的状态(属性)和行为(方法)
类:类是对象的模板,他定义了对象有哪些属性和方法。
封装:将数据和操作数据的方法捆绑在一起,形成一个独立的单元。而对于外部来说,只需要通过任何与这个单元进行交互,而不需要了解该单元内部的具体实现。
继承:可以通过已经存在的类创建新的类,新的类继承已存在类的属性和方法,这就是派生类继承了基类。
多态:不同的对象可以共用相同的接口,实现不同的功能。
所以说面向对象编程语言的三大特性就是:封装 继承 多态
多态:不同的对象可以共用相同的接口,实现不同的功能。就是多种形态
多态的构成条件:不同类存在继承关联时就会有多态。
多态又分为编译时多态(静态多态)
和运行时多态(动态多态)
①静态多态:通过
函数的重载和运算符重载
来实现,编译器根据函数或者运算符的参数类型选择调用合适的函数或运算符。这就是静态多态的体现。
// 函数重载的例子
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
// 在编译时根据参数类型选择调用合适的函数
int result1 = add(1, 2);
double result2 = add(1.5, 2.5);
②动态多态:通过
虚函数和继承
来实现,在运行时程序会根据对象的实际类型来调用相应的函数。这就是动态多态的体现。
class Shape {
public:
virtual void draw() {//基类写一个虚函数
// 基类的默认实现
}
};
class Circle : public Shape {
public:
void draw() override {//重写Shape基类的draw函数
// 圆形的绘制实现
}
};
class Square : public Shape {
public:
void draw() override {//重写Shape基类的draw函数
// 正方形的绘制实现
}
};
// 在运行时根据对象的实际类型调用相应的 draw() 函数
Shape* shapes[] = { new Circle, new Square };
for (int i = 0; i < 2; ++i) {
shapes[i]->draw();
}
动态多态的条件:
①:在基类中声明虚函数(virtual
)。
②:在派生类中覆盖(重写override
)基类中的虚函数。
抽象类:用于实现抽象数据类型的机制。抽象类
不能被实例化
,它的存在主要是为了作为其他类的基类,提供一个共同的接口。抽象类通常包含纯虚函数
,至少有一个纯虚函数,这使得抽象类不能被实例化。
纯虚函数:虚函数 = 0;
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
virtual double area() const = 0; // 另一个纯虚函数
};
为什么抽象类不能够被实例化?
①:抽象类中包含了至少一个纯虚函数,时virtual修饰的函数 = 0 ,他表示该函数没有提供实现,而是有派生类负责实现。
②:抽象类的目的时作为其他类的基类,提供一个通用的接口,但他本身没有完整的实现。所以实例化对象无法调用为实现的纯虚函数。
引用是给变量取别名,他是一个已经存在的变量提供了另一个名字。
用途:
①:避免拷贝开销:使用引用而不是拷贝变量,可以避免不必要的内存拷贝,提高程序的效率。
②:参数传递:使用引用作为函数参数,可以使函数修改传递给它的变量的值,而不是仅仅操作传递的拷贝。
③使代码更简洁。