目录
sizeof 和 strlen 的区别
explicit 的作用(如何避免编译器进行隐式类型转换)
static 的作用
static 在类中使用的注意事项(定义、初始化和使用)
static 全局变量和普通全局变量的异同
const 作用及用法
define 和 const 的区别
用宏实现比较大小,以及两个数中的最小值
inline 作用及使用方法
宏定义(define)和内联函数(inline)的区别
new 和 malloc 如何判断是否申请到内存?
delete 实现原理?delete 和 delete[] 的区别?
new 和 malloc 的区别,delete 和 free 的区别
malloc 的原理?malloc 的底层实现?
struct 和 union 的区别
class 和 struct 的异同
volatile 的作用?是否具有原子性,对编译器有什么影响?
什么情况下一定要用 volatile, 能否和 const 一起使用?
strcpy 函数有什么缺陷?
auto 类型推导的原理
1、strlen 是头文件 <cstring> 中的函数,sizeof 是 C++ 中的运算符。
2、strlen 测量的是字符串的实际长度(其源代码如下),以 \0 结束。而 sizeof 测量的是字符数组的分配大小。
3、若字符数组 arr 作为函数的形参,sizeof(arr) 中 arr 被当作字符指针来处理,strlen(arr) 中 arr 依然是字符数组,从下述程序的运行结果中就可以看出。
4、strlen 本身是库函数,因此在程序运行过程中,计算长度;而 sizeof 在编译时,计算长度;
5、sizeof 的参数可以是类型,也可以是变量;strlen 的参数必须是 char* 类型的变量。
作用:用来声明类构造函数是显示调用的,而非隐式调用,可以阻止调用构造函数时进行隐式转换。只可用于修饰单参构造函数,因为无参构造函数和多参构造函数本身就是显示调用的,再加上 explicit 关键字也没有什么意义。
int main() { A ex(100); A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested return 0; }
作用:static 定义静态变量,静态函数。
1、保持变量内容持久:static 作用于局部变量,改变了局部变量的生存周期,使得该变量存在于定义后直到程序运行结束的这段时间。
2、隐藏:static 作用于全局变量和函数,改变了全局变量和函数的作用域,使得全局变量和函数只能在定义它的文件中使用,在源文件中不具有全局可见性。(注:普通全局变量和函数具有全局可见性,即其他的源文件也可以使用。)
3、static 作用于类的成员变量和类的成员函数,使得类变量或者类成员函数和类有关,也就是说可以不定义类的对象就可以通过类访问这些静态成员。注意:类的静态成员函数中只能访问静态成员变量或者静态成员函数,不能将静态成员函数定义成虚函数。
static 静态成员变量:
1、静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。
2、静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
3、静态成员变量可以作为成员函数的参数,而普通成员变量不可以
4、静态数据成员的类型可以是所属类的类型,而普通数据成员的类型只能是该类类型的指针或引用。
static 静态成员函数:
静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。静态成员函数做为类作用域的全局函数。
静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。
相同点:
存储方式:普通全局变量和 static 全局变量都是静态存储方式。
不同点:
1、作用域:普通全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,普通全局变量在各个源文件中都是有效的;静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
2、初始化:静态全局变量只初始化一次,防止在其他文件中使用。
作用:
1、const 修饰成员变量,定义成 const 常量,相较于宏常量,可进行类型检查,节省内存空间,提高了效率。
2、const 修饰函数参数,使得传递过来的函数参数的值不能改变。
3、const 修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable 修饰的变量除外),也不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量。
在类中的用法:
const 成员变量:
1、const 成员变量只能在类内声明、定义,在构造函数初始化列表中初始化。
2、const 成员变量只在某个对象的生存周期内是常量,对于整个类而言却是可变的,因为类可以创建多个对象,不同类的 const 成员变量的值是不同的。因此不能在类的声明中初始化 const 成员变量,类的对象还没有创建,编译器不知道他的值。
const 成员函数:
1、不能修改成员变量的值,除非有 mutable 修饰;只能访问成员变量。
2、不能调用非常量成员函数,以防修改成员变量的值。
区别:
1、编译阶段:define 是在编译预处理阶段进行替换,const 是在编译阶段确定其值。
2、安全性:define 定义的宏常量没有数据类型,只是进行简单的替换,不会进行类型安全的检查;const 定义的常量是有类型的,是要进行判断的,可以避免一些低级的错误。
3、内存占用:define 定义的宏常量,在程序中使用多少次就会进行多少次替换,内存中有多个备份,占用的是代码段的空间;const 定义的常量占用静态存储区的空间,程序运行过程中只有一份。
4、调试:define 定义的宏常量不能调试,因为在预编译阶段就已经进行替换了;const 定义的常量可以进行调试。
const 的优点:
1、有数据类型,在定义式可进行安全性检查。
2、可调式。
3、占用较少的空间。
#define MAX(X, Y) ((X)>(Y)?(X):(Y)) #define MIN(X, Y) ((X)<(Y)?(X):(Y))
作用:
inline 是一个关键字,可以用于定义内联函数。内联函数,像普通函数一样被调用,但是在调用时并不通过函数调用的机制而是直接在调用点处展开,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。
使用方法:
1、类内定义成员函数默认是内联函数
在类内定义成员函数,可以不用在函数头部加 inline 关键字,因为编译器会自动将类内定义的函数(构造函数、析构函数、普通成员函数等)声明为内联函数。
2、类外定义成员函数,若想定义为内联函数,需用关键字声明
当在类内声明函数,在类外定义函数时,如果想将该函数定义为内联函数,则可以在类内声明时不加 inline 关键字,而在类外定义函数时加上 inline 关键字。
1、内联函数是在编译时展开,而宏在编译预处理时展开;在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
2、内联函数是真正的函数,和普通函数调用的方法一样,在调用点处直接展开,避免了函数的参数压栈操作,减少了调用的开销。而宏定义编写较为复杂,常需要增加一些括号来避免歧义。
3、宏定义只进行文本替换,不会对参数的类型、语句能否正常编译等进行检查。而内联函数是真正的函数,会对参数的类型、函数体内的语句编写是否正确等进行检查。
malloc :成功申请到内存,返回指向该内存的指针;分配失败,返回 NULL 指针。
new :内存分配成功,返回该对象类型的指针;分配失败,抛出 bac_alloc 异常。
delete 的实现原理:
首先执行该对象所属类的析构函数;
进而通过调用 operator delete 的标准库函数来释放所占的内存空间。
delete 和 delete [] 的区别:
delete 用来释放单个对象所占的空间,只会调用一次析构函数;
delete [] 用来释放数组空间,会对数组中的每个成员都调用一次析构函数。
在使用的时候 new、delete 搭配使用,malloc、free 搭配使用。
1、malloc、free 是库函数,而new、delete 是关键字。
2、-new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算;malloc 在申请空间时,需要确定所申请空间的大小。
3、new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,是类型安全的操作符;malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针。
4、new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针。
5、对于自定义的类型,new 首先调用 operator new() 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete() 释放空间(底层通过 free 实现)。malloc、free 无法进行自定义类型的对象的构造和析构。
6、new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)
malloc 的原理:
1、当开辟的空间小于 128K 时,调用 brk() 函数,通过移动 _enddata 来实现;
2、当开辟空间大于 128K 时,调用 mmap() 函数,通过在虚拟地址空间中开辟一块内存空间来实现。
malloc 的底层实现:
brk() 函数实现原理:
向高地址的方向移动指向数据段的高地址的指针 _enddata。
mmap 内存映射原理:
1、进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域;
2、调用内核空间的系统调用函数 mmap(),实现文件物理地址和进程虚拟地址的一一映射关系;
3、进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。
说明:union 是联合体,struct 是结构体。
区别:
1、联合体和结构体都是由若干个数据类型不同的数据成员组成。使用时,联合体只有一个有效的成员;而结构体所有的成员都有效。
2、对联合体的不同成员赋值,将会对覆盖其他成员的值,而对于结构体的对不同成员赋值时,相互不影响。
3、联合体的大小为其内部所有变量的最大值,按照最大类型的倍数进行分配大小;结构体分配内存的大小遵循内存对齐原则。
1、struct 和 class 都可以自定义数据类型,也支持继承操作。
2、struct 中默认的访问级别是 public,默认的继承级别也是 public;class 中默认的访问级别是 private,默认的继承级别也是 private。
3、当 class 继承 struct 或者 struct 继承 class 时,默认的继承级别取决于 class 或 struct 本身, class(private 继承),struct(public 继承),即取决于派生类的默认继承级别。
volatile 的作用:当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 violatile,告知编译器不应对这样的对象进行优化。
volatile不具有原子性。
volatile 对编译器的影响:使用该关键字后,编译器不会对相应的对象进行优化,即不会将变量从内存缓存到寄存器中,防止多个线程有可能使用内存中的变量,有可能使用寄存器中的变量,从而导致程序错误。
使用 volatile 关键字的场景:
1、当多个线程都会用到某一变量,并且该变量的值有可能发生改变时,需要用 volatile 关键字对该变量进行修饰;
2、中断服务程序中访问的变量或并行设备的硬件寄存器的变量,最好用 volatile 关键字修饰。
volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。
strcpy 函数的缺陷:strcpy 函数不检查目的缓冲区的大小边界,而是将源字符串逐一的全部赋值给目的字符串地址起始的一块连续的内存空间,同时加上字符串终止符,会导致其他变量被覆盖。
auto 类型推导的原理:
编译器根据初始值来推算变量的类型,要求用 auto 定义变量时必须有初始值。编译器推断出来的 auto 类型有时和初始值类型并不完全一样,编译器会适当改变结果类型使其更符合初始化规则。