1.1 C++语言特性
1) 说说看C++的语言特性
C++是由C语言发展而来的,可以很好的兼容C语言;
在C语言的基础上添加了面向对象机制,有继承、封装和多态三个特性;
引入了模板的概念,代码的可复用性比较高;
代码质量比较高,写出的程序结构清晰,易于扩充,可读性好。
是编译性语言,编译一次将源代码编译成可执行的机器语言文件,可以在机器上执行多次。
1.2 struct
1) 介绍一下C++中的struct。
struct是结构体,描述的是数据结构集合,即一个结构体中可以包含多个不同或相同的数据类型的成员变量。
2)C语言和C++中的struct的比较
【思路】
C | C++ | |
---|---|---|
访问控制 | 没有这个概念 | 三种权限,默认情况下是public |
继承 | 无 | 支持 |
成员变量类型 | 只支持成员变量,不允许成员函数和静态成员变量 | 支持成员函数和静态成员变量 |
首先struct在C语言和C++中的访问控制是不同的。
C语言因为不支持面向对象,所以也没有访问控制这一说,只是初始化以后,不管在结构体内还是结构体外,所有的函数都可以访问和修改结构体中的内容,C++呢在默认情况下,结构体是公有继承的,所以也是允许结构体内外的函数访问和修改,但是在指定访问控制权限为私有或者保护的情况下,其他函数的访问会受到限制;
其次也是和面向对象有关系的,就是C语言中的struct不支持继承,C++支持结构体的继承;
第三点是C语言中的结构体只允许成员变量,不允许成员函数和静态成员变量的存在,C++支持结构体内的成员函数和静态变量的存在;
3)C++中struct和class的区别
结构体是多种数据结构的集合,类是对一个对象数据的封装。结构体和类在默认的访问权限和默认的继承权限上不同。结构体的默认访问权限是public,类是private。结构体默认继承权限也是public,类的默认继承权限是private。
另外,从关键字角度考虑的话,class可以定义模板参数,struct不可以。
1.3 头文件引入和查找
1)include <>和include“”
两者都是C或C++中引入头文件的语句,但是根据头文件的存储路径不同,会使用不同的括号或引号。尖括号后面跟的头文件一般是系统头文件,双引号中的一般是程序员自定义头文件。如果使用的是尖括号,编译器会从编译器自己设定的头文件路径和系统变量中去查找;使用的是双引号的话,会先从当前目录查找头文件,没有找到的话才会去系统路径中查找。
1.4 导入C函数的关键字
extern "C" {
// code here
}
1.5 C++编译
1) C++的编译过程
预编译、编译、汇编、链接
预编译:处理宏定义、处理条件预编译指令、处理include预编译指令、过滤注释、添加行号和文件标识(.i文件)
编译:词法分析、语法分析、语义分析、代码优化、目标代码生成、目标代码优化(.s文件)
汇编:将汇编代码转变成机器可执行的二进制目标文件(.o文件)
链接:将不同的源文件产生的目标文件进行链接形成可执行文件
2)C++和C对函数的不同编译
C++会根据函数所在的命名空间、所属类、参数列表等信息对函数进行重新命名;
C语言只会保留函数名或者是添加下划线,所以C语言不支持函数重载
(C++的函数地址和函数体中的内容保存在代码区)
1.6 static关键字
1)介绍一下static关键字
static关键字可以用来定义静态变量,包括全局静态变量和局部静态变量。静态变量保存在全局数据区中,初始化的静态变量会保存在.data数据段中,没有初始化的静态变量会保存在.bss段中。在程序运行过程中,静态变量只会被初始化一次(不管是静态局部变量还是静态)。
static关键字还可以用来定义静态函数,静态函数的作用域是本源文件中有效。
在类中使用static还可以定义类中的静态成员变量和静态成员函数。静态成员变量是类的所有对象共享的,存放在全局数据中。如果通过一个对象对其进行修改,通过其他对象对该静态成员变量进行访问,会同步其变化。类中的静态成员函数和静态成员变量是类似的,类中的所有对象共享该函数。静态成员函数中只能访问静态成员变量。
2)对象的非静态成员函数是怎样修改对象的成员变量的?
调用对象的非静态成员函数的时候,会隐式地将this指针作为参数传入,this指针指向当前调用该非静态成员函数的对象。当函数中涉及到对成员变量的访问和修改,都会通过this指针访问。
静态成员函数没有this指针,因为静态成员函数是所有对象共享的,不具体指向某个对象,同时也只能访问静态成员变量。
3)静态变量和全局变量是什么时候初始化的?
静态变量:
C++中,程序运行之后,在首次用到该变量的时候才会初始化。
C语言中,在编译期初始化,程序运行之前就会初始化了。
全局变量:
全局变量使用常量表达式进行初始化的时候,是在编译期完成的;
如果使用的是非常量表达式,则是在程序运行过程中完成初始化。其中如果源文件没有被链接器标记为lazy binding(懒绑定),则会在main函数执行前进行初始化,如果源文件被链接器标记为懒绑定,则在main函数执行之后进行初始化。
4)附加:对静态变量的理解
静态变量和全局变量的区别
静态变量和全局变量的区别:静态变量的作用域只在当前源文件有效,而全局变量可以被其他文件所用
不同函数中的静态局部变量是否允许同名
静态局部变量:当出现静态局部变量时,允许不同作用域下出现同名静态局部变量,并且静态局部变量地址不同,但都存放在全局区,并且函数结束调用后,静态局部变量的内存空间不会回收,只有程序结束的时候会回收。
5)附加:不同条件下静态变量的作用域、内存空间和生命周期
静态变量
作用域 | 内存空间 | 生命周期 | |
---|---|---|---|
静态局部变量 | 局部作用域 | 静态存储区 | 程序结束 |
静态全局变量 | 文件作用域 | 静态存储区 | 程序结束 |
类静态成员变量 | 类作用域 | 静态存储区 | 超出类作用域 |
1.7 数组和指针
数组是用于存储多个相同类型数据的集合,并且内存空间是连续的。指针是存放内存地址的变量,大小取决于机器的字长,32位机器中指针所占内存大小为4个字节。
数组名在有些情况下可以看成指针常量,指向数组中的首元素,对数组名进行++的操作会指向下一个元素。使用sizeof计算的大小是整个数组中所有元素占内存空间的大小。
1.8 函数指针是什么
1)函数指针是什么
函数指针是一个指针变量,指向函数的入口地址。主要用在回调中。
定义形式
int func(int a); // 函数定义
int (*f)(int a); // 函数指针,f为指向返回值为int,传入参数为int的函数
f = &func;
2)回调是什么?
在一个函数内部调用了另一个函数,调用其他函数的函数一般是别人写好的库函数,被调用的函数一般是我们自己写的。
3)指针函数
返回值类型为指针的函数。
1.9 空指针调用成员函数
1)空指针可以直接调用成员函数吗?
尽管这样的语句可能不是很安全,但是编译器会通过这样的语句。对于非虚的成员函数而言,指针指向的数据类型在确定以后,会直接根据该类型绑定非虚的成员函数的入口地址。但是对于成员函数为虚函数的,就会在运行时出错。
2)为什么调用虚函数会在运行时出错?
因为虚函数对函数地址进行的是地址晚绑定。在运行的时候,会通过访问this指针找到调用该成员函数的对象,进而通过获取对象中封装的虚函数表的地址找到该成员函数的入口地址。而使用空指针调用虚函数的时候,this指针得到的是空指针,也就不能找到某个对象,进而找不到成员函数的入口地址,所以出错。
3)函数、全局变量和静态变量的虚拟地址在编译的时候就会确定。
1.10 野指针怎样产生和避免?
产生:声明了指针但是没有初始化;释放了指针指向的内存空间却没有对该指针置空;
避免:声明后及时初始化;释放内存后及时置空;使用智能指针(坑:可能会问智能指针)
1.11 静态局部变量、全局变量、局部变量
静态局部变量、全局变量、局部变量
静态全局变量 | 静态局部变量 | 全局变量 | 局部变量 | |
---|---|---|---|---|
定义 | static | 函数内使用static关键字定义的变量 | 函数内定义 | |
作用域 | 文件作用域,其他源文件不可用 | 局部作用域,函数返回后变量不会消失,下次调用可以看到上次调用后的值 | 全局作用域,整个工程文件内都有效(通过extern关键字) | 局部作用域,函数返回后失效 |
内存空间 | 静态存储区 | 静态存储区 | 静态存储区 | 栈区 |
生命周期 | 程序结束回收 | 程序结束回收 | 程序结束回收 | 作用域外就回收内存 |
使用场景 | 本文件中使用 | 多次调用某个函数,并且下次调用不会再初始化 | 多个文件中使用到某个变量的时候 | 函数的参数、函数内的局部变量 |
静态全局变量vs全局变量:static全局变量只初使化一次,防止在其他文件单元中被引用;
静态局部变量vs普通局部变量:static局部变量只被初始化一次,下一次依据上一次结果值;
静态函数vs普通函数:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝 ;
全局变量vs静态变量:如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
静态局部变量有什么用:静态局部变量只会被初始化一次,因此就算调用函数多次,但是再次调用的时候不会再执行其初始化语句,而是在上一次调用的基础上进行修改。
1.12 内联函数和宏函数的区别
内联函数和宏函数通常函数体都比较简单。内联函数通过关键字inline实现,宏函数通过define实现。
两个函数都是通过代码膨胀来实现节省开销的,但是宏函数是在预编译期进行展开,将宏代码复制到调用函数的地方,有点类似于字符串替换,内联函数是在编译期进行代码插入的,所以宏函数不会对类型进行检查,内联函数会检查返回值、函数参数列表是否正确。
这两种函数一般都用于函数体简单、频繁调用的函数(不包含复杂的结构控制语句)。
内联函数和普通函数相比,不需要进行寻址。
1.13 i++和++i
1)异同比较:
相同点:1)都是实现变量自增和返回变量值的语句;2)都不是原子操作。
不同点:1)执行顺序不同;
2)i++效率低于++i;
i++需要创建一个临时变量保存自增前的i值,自增后再返回值;++i不需要,返回自增后的值即可。
3)i++不可以作为左值,++i可以作为左值;
i++由于使用临时变量保存返回值,所以不能以引用的方式返回;++i以引用的方式返回,可以作为左值。
1.14 new和malloc
分别是操作符和函数,都可以用来动态分配内存。
不同点:
1)malloc需要指定开辟内存空间大小,new不需要;
2)new会调用构造函数对该块内存空间进行初始化,malloc只会分配指定大小的空间并不初始化;
3)返回值类型不同,尽管返回的都是指针,但是new返回的指针带有类型,malloc返回的指针没有类型(void*)
4)发生错误的时候,new会抛出std::bad_alloc异常,malloc会返回空指针,需要手动判断是否分配成功。
1.15 const和define的区别
const是用于声明常量的关键字,define是预处理指令。
在使用const定义常量时,编译器会对其进行类型检查,而define只是进行简单的文本替换,不进行类型检查。
const在编译期生效,define在预编译期生效;
1.16 常量指针、指针常量、常量
1.17 初始化后需要立即初始化的变量
1)有什么变量是创建后需要立即初始化的?
-
常量变量:C++ 中的常量变量在定义时必须进行初始化,而且一旦初始化后就不能再修改。
-
引用类型的变量:引用是一个别名,它必须在定义时初始化为某个对象的别名,否则会导致编译错误。
-
静态成员变量:静态成员变量属于整个类,而不是属于某个类对象,因此必须在定义时进行初始化。
-
成员常量变量:成员常量变量是指在类中声明的常量变量,它们必须在构造函数初始化列表中进行初始化,而不能在构造函数体内进行初始化。