C/C++语言基础
extern 关键字作用 参考链接
- extern声明变量或者函数时,它告诉编译器去其他文件中寻找定义或者实现。
- extern “C”的作用:为了实现C++、C的混合编程,使C++中能够调用C写的函数。它告诉C++编译器按照C的编译、链接规范来编译。因为C++编译器为了实现函数重载的功能,对函数名的编译和C编译器不一样,所以要加上extern “C”.
static关键字作用 参考链接
- 一种是面向过程的程序设计中的static,不涉及类。修饰变量时表示的是一个静态变量,在全局数据区分配内存,只在文件内可见,而文件之外是不可见的。修饰函数中表示静态函数,不能在其他文件中使用。
- 一种是面向对象程序设计中的static, 涉及到类。在类的数据成员前面加上static关键字,就是类的静态成员变量,是属于类的,在全局数据区分配内存,被所有的对象共享。在类的函数前面加上static,表示静态成员函数。不和任何对象有联系,没有this指针。
volatile关键字的作用 参考链接
volatile是一种类型修饰符,遇到这个关键字声明的变量, 编译器对访问该变量的代码不再进行优化。访问寄存器要比访问内存块,因此CPU总是优先访问寄存器。但是有时候可能内存中的数据发生了变化,而寄存器还保存原来的值。为了防止这种情况,使用volatile来声明变量时,系统总是从内存中读取数据,而不会从寄存器中读取。const关键字的作用
- const关键字所修饰的表示的是一个常量。取代C中的宏定义,声明的时候必须初始化。const修饰的变量不可以被修改。
- const修饰指针时。const int * 和int * const
- const 修饰引用或指针做函数的形参
- const 修改引用或指针做函数的返回值
- const修饰成员变量时,必须在构造函数列表中初始化
- const修饰成员函数时,说明该函数不应该修改非静态成员
new与malloc的区别
- new 分配内存按照数据类型进行分配,malloc分配内存按照大小分配。
- 给对象分配内存时,new 会调用构造函数,而malloc不会
- new返回的是指向对象的指针,而malloc返回的是(void * )
- new是一个操作符可以重载,而malloc是一个库函数
- new分配的内存用delete销毁,malloc用free销毁。delete销毁时调用析构函数,而free不会。
- new如果分配失败会抛出bad_malloc异常,而malloc失败会返回NULL。
- malloc分配内存不够时,可以用realloc扩容,new不可以。
#define和const定义常量的区别
- define宏是在预处理阶段展开,const常量是在编译运行时候使用。
- define宏不做类型检查,仅仅是展开替换。const常量有具体的类型,编译的时候执行类型检查。
- const定义的变量需要分配内存,在常量区分配。define定义的不占有内存。
指针和引用的区别
- 指针保存的是对象的地址,引用是对象的别名
- 指针需要通过解引用来间接访问,而引用是直接访问。
- 引用在定义的时候必须初始化,而指针则不需要。
- 指针在赋值后还可以改变,而引用不能改变。
- 有常量指针,而没有常量引用.
结构体中内存对齐?
- 从0位置开始存储
- 变量存储的起始位置是该变量大小的整数倍
- 结构体总的大小是其最大元素的整数倍,不足的后面要补齐
- 结构体中包含结构体,从结构体中最大元素的整数倍开始存
- 如果加入pragma pack(n) ,取n和变量自身大小较小的一个
内联函数有什么优点?内联函数与宏定义的区别?
- 宏定义在预编译的时候就会进行宏替换
- 内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。
- 内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。
- 使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a * b,这很危险,正确写法:#define MUL(a, b) ((a) * (b))
模板特例化
模板特化分为全特化和偏特化,模板特化的目的就是对于某一种变量类型具有不同的实现,因此需要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化纯虚函数的介绍?
- 纯虚函数是用在虚函数后加上 = 0来声明的。
- 纯虚函数只提供声明,没有实现。纯虚函数是起到声明接口的作用。
- 声明虚函数的类是抽象类,不能实例化为对象。继承抽象类的子类必须重写类的纯虚函数。否则该子类还是抽象类。
- 虽然抽象类不能实例化,但是抽象类可以有构造函数。
C++多态性与虚函数表
C++多态的实现?
多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。虚函数的作用
1.虚函数用于实现多态。
2.虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。谈谈虚函数表?
- 基类指针在调用所指对象的虚函数时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。
- 每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的vptr指针都指向这同一个虚函数表。
- 虚函数表中为什么就能准确查找相应的函数指针呢?因为继承的时候,派生类的虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。
为什么要有虚析构函数?
当基类指针指向派生类对象时,delete指针销毁对象时,如果析构函数没有定义为虚析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用所指对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。构造函数可以是虚函数吗?
不可以。因为虚函数的执行依赖于虚函数表,调用虚函数需要通过对象中指向虚函数表的vptr指针。而对象的vptr指针是在构造函数中初始化的。所以,如果构造函数是虚函数,构造对象时,vptr指针还没有初始化,将没办法进行。析构函数能够抛出异常吗?
- 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
- 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
必须在构造函数初始化式里进行初始化的数据成员有哪些?
- const常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
C++11新特性
智能指针怎么实现的?
- 构造函数中计数初始化为1
- 拷贝构造函数中计数值加1
- 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一
- 析构函数中引用计数减一
- 在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象
C++四种类型转换:static_cast, dynamic_cast, const_cast, reinterpret_cast
- const_cast用于将const变量转为非const
- static_cast用的最多,对于各种隐式转换,非const转const,void * 转指针等, static_cast能用于多态想上转化,如果向下转能成功但是不安全,结果未知
- dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
- reinterpret_cast几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用
- 为什么不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错