第I部分 C++基础
C是面向过程的语言,C++是面向对象的语言
C++中new和delete是对内存分配的运算符,取代了C中的malloc和free
C++中有引用的概念,C中没有
C++引入了类的概念,C中没有
C++有函数重载,C中不能
C变量只能在函数的开头处声明和定义,而C++随时定义随时使用
关键字相关
- const的作用,宏定义与const的区别(const为变量,宏定义为展开)
1.1 const比宏的优势:
- 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。
- 通过给优化器一些附加的信息,使用关键字const能产生更紧凑的代码。
- 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
1.2 const关键字至少有下列n个作用:(主要为变量,指针,函数)
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;(也可以修饰返回值,表示返回的为一个const值)
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
- 顶层const与底层const:
顶层const表示对象本身为常量,如const int s = 12, int * const a = 12,底层const表示所指向的对象为常量(多用于指针),如const int* a = 12
声明引用const的都是底层const,在进行拷贝时两边的对象都必须具有相同的底层const对象 - inline关键字是做什么用的?inline关键字在什么情况下会展开失败?
inline类似于宏替换,使用函数体替换调用处的函数名,省去了调用函数的开销,增快了代码的执行效率。但是又不是宏替换,inline函数是真正的函数,编译器会考虑语义。解决一些频繁调用的小函数对栈内存重复开辟所带来的消耗。
函数体内代码长度过大,包含复杂的结构控制语句(while,switch),包含内联函数本身,含有递归均会导致展开失败。所以inline不适用于本身函数量大的情况;同时inline也不能用于析构与构造函数(因为其可能使用了父类函数);
inline与define的区别:
1)宏不是函数,只是用起来像函数,只要定义了就会实现,而inline本身是函数,并且编译器会检查是否需要inline;
2)宏在预编译时进行替换,inline在编译时展开;
3)宏是直接的字符替换,而inline自带了参数检查。 - static用法
1.隐藏:(static函数,static变量均可)当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。加了就是隐藏性
2.用作变量或成员变量:因为static变量存放在静态存储区,所以它具备持久性和默认值0;
3.用作静态成员函数:类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态成员变量和静态成员函数,并可以被外部自由访问。 - struct和class的区别
(1)最本质的区别是默认的访问控制:默认的继承访问权限struct是public的,class是private的。
(2)少用的区别:class这个关键字还用于定义模板参数,就像typename(如template<typename T, class A>)。但关键字struct不用于定义模板参数。 - volatile关键字
三个特性:易变性、不可优化性与顺序性。
(1)易变性:该变量在寄存器中被使用后立即写回内存,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。
(2)不可优化性:volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
(3)顺序性:运行时能保证Volatile变量间的顺序性,编译器不会进行乱序优化。但是对于Volatile变量与非Volatile变量的顺序,编译器不保证顺序,可能会进行乱序优化。(如果要彻底的阻止CPU的乱序优化执行能力,需要调用CPU中的barrier指令,这条指令的作用是阻止CPU将barrier指令之前的指令交换到barrier指令之后)
一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他 - extern关键字
1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: extern “C” void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!
第二,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。 - mutable关键字
用此关键字附加修饰类的非静态和非常量数据成员,使该成员一直处于可变状态,使得其即使在后置const的函数中仍然可以被更改 - 全局变量名字与局部变量名字相同的情况
当你定义了一个大全局变量后,全局都可以使用,但是要是你在子函数中(不是main函数)也定义了一个相同的变量,无论你是否用static,子函数就按子函数定义的来搞(即全局变量被局部变量覆盖了),子函数结束,并不会影响你的大全局变量。 - std::move
std::move方法将左值转换为右值,优先触发移动构造函数。 - void *memset(void *s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节(typedef unsigned int size_t)用ch替换并返回s。用于给一整块内存填充值,主要用于初始化与数据清除。 - assert用法
#include <assert.h> void assert( int expression );
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。 - sizeof与strlen
(1)sizeof对char类型计数包括尾’\0’字符,strlen对char类型计数不包括尾。同时sizeof对于字符指针和字符数组的判断方式是不同的,指针是指针大小,字符数组为数组包含尾字符的长度;
(2)size_t strlen(char const* str)可见strlen就是判断字符串长度的,无论针对字符指针还是字符数组(字符数组名会退化成字符指针) - 数字类型默认为unsigned int
数字类的常量默认符号位unsigned int(没有符号位,所以最大为2^32位)注意对于0x80000000这样的为int型负数最小值的值,需要将其强制转换为int型
指针与引用相关
- 值传递与引用传递区别
1)值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
2)引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。 - 指针与引用的区别
(1)指针:对于一个类型T,T* 就是指向T的指针类型,也即一个T* 类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const、volatile(当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile,告诉编译器不要对这样的对象优化)等等。
(2)引用:引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用
(3)区别本质:指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。
① 引用不可以为空,但指针可以为空。定义一个引用的时候,必须初始化。因此使用指针之前必须做判空操作,而引用就不必。
② 引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。
③ 引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节(32位)。
④ const int* p-> 指向常量的指针,int * const p->本身是常量的指针.后者需要在定义的时候初始化。对于引用来讲int const & p=i;和 const int &p=i;没什么区别,都是指指向的对象是常量。
⑤ 引用和指针的++自增运算符意义不同,指针的++表示的地址的变化,一般是向下4个字节的大小(一个指针的大小),引用的++就是对应元素的++操作。
⑥ 值传递和引用传递(值传递为数据复制,引用传递为起别名) - 指针间转换需要注意的问题?
(1)普通指针的转换,类型!!!
(2)普通指针转换为共享指针时只能转换一次
(3)当普通指针作为对象成员时,最好只赋值给一个对象变量,否则会出现,一个对象删除时,将指针删除,其他保存此指针的对象再删除时,无法删除一个已经删除的指针,而智能指针就可以多次赋值。 - char型指针与char[]型首地址指针:
C++特性,在cout char类型(包括char[]的首地址)时,会cout其所指向的对象,而不是指针所存地址。
如果需要得到地址,将其类型转换。而char[]的首地址已经变成了对应数组的地址,不会与字符常量的地址相同。
函数相关
- 函数指针的定义及其使用方式
void(*pf)() = test_fun1;
(*pf)(); - C++初始化列表使用
- 还有一些C++库函数的实现(比如strcpy之类的)