基本语法
枚举类型
enum Name{A, B, C} a;
printf从右到左压栈
printf计算参数时是从右到左压栈的。例如
printf("%d, %d\n", *ptr, *(++ptr))
这句话是先算后面的参数,++后再算前面的,因此两个值是一样的。
(int &)a输出内存值
该类型转换的意思是把这个内存地址里的值当成整数输出。
if float a = 1.0f; (int)a == (int &)a
为false,
if float a = 0.0f; (int)a == (int &)a
为true。
因为内存里float型和int型的1不同,0相同。
小端存储
在X86系列的机器中,数据的存储是“小端存储”,即对于一个跨多个字节的数据,其低位存放在低地址单元,高位存放在高地址单元。比如一个int型的0x12345678
要存放在0x00000000~0x00000003四个内存单元中,那么0x00000000中存放的是低位的0x78.
预处理,const和sizeof
预处理器
C/C++编译系统编译程序的过程为预处理、编译、链接。
预处理器主要处理一下内容:
-
文件包含:
#include<iostream>
-
宏替换:
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
,#define MIN(A,B) (((A)<=(B)?A:B)
-
条件编译指令:
#ifndef
-
其他:
#line
,#error
,pragma
等。
const
const常量赋值时必须同时初始化
const修饰指针
-
const位于*左边:例如
const int * a = &b
,const修饰指针所指向的变量a。 -
const位于*右边:例如
int * const a = &b
,const修饰指针本身。
const修饰成员函数
int Point::GetY() const {return YVal;}
被定义成const的成员函数,如果企图修改数据成员的值,编译器就会报错。说明这个函数是“只读”函数。
但是如果数据成员被mutable修饰,就又可以修改了了。
const与#define
const有数据类型,可以进行类型安全检查。
sizeof
sizeof是计算栈中分配的大小。
-
char * ss1 = "0123456789"
,是一个字符指针,sizeof(ss1)=4。 -
char ss2[] = "0123456789"
,是一个字符数组,加上隐含的“\0”,sizeof(ss2)=11。 -
char ss3[100] = "0123456789"
,是一个字符数组,sizeof(ss3)=100。 -
sizeof结构体,当结构体内的元素的长度都小于处理器的位数时,以结构体内最长的数据元素为对齐单位。否则以处理器的位数为对齐单位。
sizeof和strlen的区别
-
strlen的内部实现是用一个循环计算字符串的长度,直到“\0”为止。
-
sizeof可以用类型(包括结构和类)做参数,strlen只能用char*。
-
sizeof是操作符,strlen是函数。
内联函数与宏定义
内联函数与普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译时内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
内联函数要做类型检查,比宏更安全。
指针与引用
指针和引用的差别
-
引用必须指向某些对象,不能为空。声明引用时,必须同时初始化。
-
使用指针之前,应该测试合法性,防止其为空。
-
指针可以被从新赋值以指向另一个不同的对象。
函数参数传递
-
值传递
-
指针传递(地址传递)
-
引用传递
char *strA(){
char * str = "Hello World";
return str;
}
char c[] = "hello"
是分配一个局部数组。局部数组是局部变量,对应内存中的栈。
char *c = "hello
是分配一个指针变量。字符串常量保存在只读的数据段。这里的指针也是局部变量,所以需要return一个地址。
程序内存空间如下图所示:
注意堆里存放程序员动态分配的内存空间,包括malloc和new的内存。
-
栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。类似于数据结构中的栈。栈是向低地址扩展的数据结构,是一块连续的内存空间,比较小。在函数调用时,第一个入栈的是主函数的下一条指令的地址,然后是函数的各个参数(大多数编译器由右往左入栈),然后是函数的局部变量,静态变量不入栈。
-
堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于链表。操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,遍历该链表,找到第一个空间大于所申请空间的堆节点,从空闲节点链表中删除并分配给程序。对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样delete才能正确释放。另外,找到的堆节点空间可能大于申请的大小,系统会自动将多余空间重新放入空闲链表。堆是向高地址扩展的数据结构,是一块不连续的内存空间,比较灵活,比较大。
-
全局区(静态区,static):全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域。程序结束后由系统释放。
-
文字常量区:常量字符串就是放在这里的。程序结束后由系统释放。
-
程序代码区:存放函数体的二进制代码。
函数指针
Long (* fun)(int)
上面是一个函数指针,这个指针的返回值是long,参数是int。
Long * fun(int)
去掉()后,它就是一个指针函数,返回一个long型指针的函数。
指针数组 vs. 数组指针
int *ptr[]
指针数组
int (*ptr)[]
数组指针
迷途指针 vs. 空指针
-
delete pInt
当delete一个指针的时候,实际上是编译器释放内存,但是指针本身仍然存在,这时是一个迷途指针。 -
pInt = null
使用该语句将指针置为空。
malloc/free vs. new/delete
都是用来申请动态内存和释放内存。
-
malloc/free是C/C++语言的标准库函数。
-
new/delete是C++的操作符。对于非内部数据类型的对象而言,只用malloc无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。因此,C++需要一个能完成动态内存分配和初始化工作的运算符new。
-
malloc需要显式地指出所需内存大小,new申请内存分配时无需指定内存块的大小,编译器会根据类型信息自行计算。
-
malloc分配成功会返回
void *
,需要强制类型转换。new分配成功,返回的是对象类型的指针。 -
malloc函数从堆上动态分配内存。而new从自由存储区上为对象动态分配内存(一般也是在堆上)。
this指针
-
this指针本质上是一个函数参数,只是编译器隐藏起形式的,语法层面上的参数。this只能在成员函数中使用,全局函数,静态函数无法使用。
-
this在成员函数的开始前构造,在成员的结束后清楚,这个生命周期同任何一个函数的参数是一样的。
-
this指针并不占用对象的空间。所有成员函数的参数,不管是不是隐含的,都不会占用对象的空间,只会占用参数传递时的栈空间,或者直接占用一个寄存器。
-
this指针会因编译器的不同而有不同的放置位置。可能是堆,栈,也可能是寄存器。
-
this是个指向对象的“常指针”,无法改变。
STL模版和容器
泛型函数
template<typename T> T fun(T x, T y){}
容器
数据结构 | 描述 | 头文件 |
---|---|---|
向量(vector) | 连续存储的元素 | vector |
列表(list) | 由节点组成的双向链表 | list |
集合(set) | 红黑树,默认按升序,无重复元素 | set |
映射(map) | 红黑树,{key, value} | map |
Hash映射(unordered_map) | Hash | unordered_map |
队列(queue) | 先进先出 | queue |
双队列(deque) | 连续存储的指向不同元素的指针所组成的数组 | deque |
优先队列(priority_queue) | 排序队列(最大堆) | queue |
栈(stack) | 后进先出 | stack |
面向对象
面向对象设计三原则
-
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
-
继承:可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。
-
多态:一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。
C++空类默认产生的类成员函数
默认构造函数,析构函数,复制构造函数,赋值函数。
struct vs. class
C++中二者的区别仅在于class中变量默认是private,而struct中变量默认是public。
类的成员变量
静态成员变量可以在一个类的所有实例间共享数据。
常量成员变量必须在构造函数的初始化列表里初始化或者将其设置为static
类成员函数的初始化变量顺序
fun(i):para1(i), para2(i)
类中初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的。
多态的概念
一个接口,多种方法
虚函素就是允许被其子类重新定义的成员函数,而子类重新定义父类虚函数的做法,称为override。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的。这样的函数地址是在运行期绑定的(晚绑定)。
函数重载是指允许存在多个同名函数,而这些函数的参数列表不同,与多态无关。
多态性就是允许你将父对象设置成为和一个或更多它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
虚析构函数:当一个类被用作基类的时候,会把他的析构函数写成虚函数。
ClxBase * p = new ClxChild
这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
友元
friend
友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性。
可以访问俩对象的私有成员。比如计算两个点之间的距离。
友元函数和友元类。
公有继承,私有继承,保护继承
-
公有继承:
class child: public parent
派生类的对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有成员和保护成员。 -
私有继承:
class child: private parent
派生类对象不可访问基类成员,派生类可以访问基类的公有成员和保护成员,但是基类的成员都作为派生类的私有成员,即不可往下继承。 -
保护继承:
class child: protect parent
派生类对象不可访问基类成员,派生类可以访问基类的公有成员和保护成员,且基类的成员都作为派生类的保护成员,不可被派生类的子类的对象访问,可被派生类的子类访问。
虚表
编译器会为每个包含虚函数的类创建一个虚表(vtable)。在vtable中,编译器防止特定类的虚函数地址。编译器秘密地设置一个指针(vpointer),指向这个vtable。
基类指针做虚函数调用时(多态调用),编译器静态地插入取得这个vptr,并在vtable表中查找函数地址的代码,这样就能调用正确的函数使晚绑定发生。
多重继承
class child: public parent1, public parent2
虚继承:类D继承自B和C,B和C都继承自A,在类D中出现两次A。为了节省内存空间,可以将B,C对A的继承定义为虚拟继承,A就成了虚拟基类,形成一个菱形继承。
纯虚函数
virtual void fun() = 0
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现由派生类去做。
运算符重载
Node operator + (const Node & node)
运算符重载实际上是函数的重载。
两种形式为成员函数形式和友元函数形式
-
运算符被定义为全局函数:对一元运算符是一个参数,对二元运算符是两个参数。
-
运算符被定义为成员函数:对一元运算符是没有参数,对二元运算符是一个参数。