第一章
1、::作用域运算符
1.1 全局作用域,直接调用::
2、命名空间
2.1 namespace 用于解决命名冲突问题。
可以存放函数,变量,结构体,类。
namespace zzz {
void func();
int a = 10;
}
2.2 必须定义在全局作用下。
2.3 可以嵌套。
2.4 命名空间是开放的,随时追加内容。
2.5可以匿名命名空间,无名的空间。相当于写了 static 类型,只能在当前文件中使用。
namespace {
int a = 100;
}
等价于 static int a = 100;
2.6 命名空间可以起别名。
namespace zzz = jjj;
3、using 声明
3.1就近原则
3.2 using使用,避免二义性。
int a = 20;
using namespace zzz:a;
cout << a << endl; // 编译报错,编译器确定不了a。
3.3 using编译指令
using namespace zzz; // 打开命名空间 zzz
4、c++对c语言的增强
4.1 全局变量检测增强
C语言:
int a;
int a = 10; // 编译成功。
C++:
int a;
int a = 10; // 编译失败,a重定义。
4.2 函数检测增强
int test(w,s) {
} // C语言编译成功,c++编译失败(函数入参,返回值检测,函数调用 增强,c++更严格)。
4.3 类型转换检测增强
C语言: char *p = malloc(sizeof(64)); // malloc 返回值是void *
C++: char *p = (void *)malloc(sizeof(64)); 强转。
4.4 struct 增强,struct 不能实现函数,class 可以加函数
struct Person{
int age;
};
struct Persion p1; // C语言声明类型必须加struct,c++不用。
4.5 bool类型增强, C语言中没有bool类型,c++中才有(所有非0的值都会默认转换为1)
4.6 三目运算符增强
int a = 10;
int b = 20;
a > b ? a : b = 100; // C语言编译报错:20 != 100 ? 。c++自动转换成 b = 100。(C语言返回的是值,c++返回的是变量)
C语言如果也想用c++一样使用,返回变量地址:
*(a > b ? &a : &b)= 100;
4.7 const增强
C语言:
可以通过指针的方式修改 const定义的局部变量(全局变量不可以修改):
const int a = 10; // 伪常量。编译器会分配内存,所有分配内存的值都会以指针的方式被修改。
int *p = (int *) &a;
*p = 20;
默认外部链接,外部可以通过extern 调用。
C++中:
const int b = 20; // 真正常量。以键值的方式定义的b ,指向的值是20 ,不会分配内存,如果前面声明extern 会分配内存,如果是自定义变量也会申请内存。所以不能以指针的方式修改。
int *p = (int *) &a;
*p = 20; 等价于:int tmp = b; int *p = (int *) &tmp; // 编译器临时开辟一段空间tmp(看不到)。
默认内部链接,外部不能使用。
5、尽量用const 替换 #define
#define MAX 3;
在预处理就被替换了,编译器发现不了,没有数据类型,没有作用域,整个文件都存在,但是可以通过 #undef 的方式卸载。
6、引用(给变量取别名)
int a = 10;
int &b = a; 等价于 int * const b = &a; (指针常量) // &在左侧叫引用,在右侧叫取地址。
注意:1、必须初始化。一个变量可以有多个别名,但是一个别名只能指向一个变量。
2、类型必须要和变量一样。
第二章
1、内联函数:空间换时间c++中代替C语言中宏函数。宏函数有缺陷(提升程序效率)。
#define Math(x,y) x+y //宏函数定义
1.1 预处理的时候直接替换,存在潜在问题。
1.2 内联函数本身也是一个真正的函数,唯一的区别就是不需要函数调用的开销。
inline int func(int a) // 内联函数声明
inline int func(int a) {return a++;} // 内联函数定义
1.3 类内部的成员函数,默认是内联函数。
内联函数的一些限制:
- 不能出现循环语句;
- 不能出现过多的判断;
- 函数体不能太庞大;
- 不能对函数进行取地址操作。
出现这四种情况,编译器当普通函数处理。
2、函数的默认参数及其占位参数(C语言中没有)
// 参数后面 = 。。。
// 注意事项:
- 当有一个位置参数是默认参数,则后面的参数都必须带有默认参数。
- 函数声明和实现,只能有一处实现默认参数。
void fun (int a = 1) {}
// 占位参数,没有什么大用处。函数调用的时候必须要提供这个参数。
void fun1(int a, int){}
3、函数重载
c++中最重要的概念。C语言中函数只能有一个命名,c++中可以(编译器内部把函数进行了改名)。
语法:
- 函数命名相同,入参个数,类型或者个数不一样。
- 必须在同一个作用域。
- 函数返回类型不能作为重载(二义性)。
- 当函数重载遇到函数默认参数,注意二义性。
- 引用的重载,const也可以作为重载。
4、extern “C” 浅析
一个无法解析的外部命令:链接的时候报错。
在c++中,函数可以发送重载,所以c++编译器可能会把函数偷偷改名。
但是C语言中没有函数重载,所以c++中调用C语言的函数,就会链接错误。 所以extern “C” 就是告诉编译器,该函数按照C语言的方式链接。
通常是在C语言头文件中进行处理:
#ifdef __cplusplus
extern "C"
#endif
#ifdef __cplusplus
}
#endif
5、c++封装
C语言中结构体,属性的行为是分开的,没有严格执行类型转换。c++严格执行类型转换,属性和行为 可以绑定到一起,作为一个整体。
c++类中控制权限:
- public 公有成员,在类 内部和外部都可以访问。
- protected 保护成员,类内部可以访问,在当前类的子类 也可以访问,类外部不可以访问。
- private 私有成员(属性,函数),只能在类的内部可以访问,类外部不可以。
在c++中,struct 和 class 表示一个意思。唯一区别是 stuct 类中默认权限是 public,class默认是private。
建议把成员属性设置为私有,对外提供公有的方法。
6、对象的构造和析构
构造函数和析构函数,编译器自动调用(只会调用一次),完成对象的初始化和清理。
构造函数:函数名和类名相同,没有void,可以有参数,可以发生重载。
析构函数:~类名,不可以有参数,不能重载。
如果没有实现,编译器会自动帮我们实现。
构造函数的分类:
按照参数:1、无参数(默认构造),2、有参数
按照类型:拷贝构造函数(const Persion & P) 和 普通构造函数。
调用方式:1、括号法调用 。2、显示法调用
Persion p1 = Persion(100); 匿名对象
Persion p2 = 100;
Persion p3 = p1; 隐式类型转换。
7、浅拷贝
系统会提供默认拷贝构造,简单的值拷贝,如果属性里有指向堆空间数据,那么析构函数调用会释放堆空间两次,造成崩溃。
深拷贝
自己实现拷贝构造函数,每一次都创建一个新的堆空间。
第三章
1、初始化列表
Persion(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
2、类对象作为类成员
构造函数调用:从类内到类外
析构函数调用:从类外到类内
3、explicit关键字,防止隐式类型转换
4、动态对象的创建 (堆区开辟)
malloc 标准函数,不会调用构造函数,返回 void *
free 不会调用析构
new 运算符,默认调用构造函数,堆区开辟,返回相应类型的指针。通过new开辟数组,一定要提供默认构造函数,释放的时候 delete 后加 [] 。在栈上开辟的话,可以指定有参构造。
delete 调用析构,当void * 接受new时不会调用析构函数。
第四章
1、静态成员变量 static
在一个类中,若将一个成员变量声明为static类型,这就是静态成员变量,此时无论建立了多少个对象,都只共享一份数据。静态变量,在编译阶段就已经分配空间,对象都还没创建。
-
- 静态成员变量必须在类中声明定义,在类外初始化。
- 静态成员变量不属于某个对象,在为对象分配内存的时候也不包括静态成员变量。
- 静态数据成员可以通过类名或者对象名来引用。
- 也有访问权限,但是也是在类外初始化。
2、静态成员函数
在一个类中声明的函数前加static,也是共享一份内存,在编译阶段就已经分配空间。通过类名,对象调用。
- 不可以访问普通成员变量,可以访问静态成员变量。
- 也有访问权限。
单例模式
3、成员变量和成员函数分开存储
空类的大小是1,每一对象都要有一个独一无二的地址,char类型的维护这个地址。
非静态成员函数,不属于对象
静态成员变量,不属于对象
静态成员函数,不属于对象
只有非静态成员变量,才属于对象。
4、空指针调用成员函数
不涉及成员变量可以访问,如果用到了this 指针,要加this判断是否为null。
5、常函数
不允许修改this 指针指向的值。
Void fun() const {}
Mutable // 强制修改符
常对象:常对象不允许调用普通成员函数修改对象属性。常对象可以调用常函数。
6、友元(好朋友)
- 友元函数:
在类中声明外部函数,前面用friend修饰,表示友元函数,之后就可以访问私有成员函数。
- 友元类:
在类中声明另外一个类作为该类的友元类,然后可以访问该类的私有成员属性。
Friend class 类名; // 单向的不可以传递
- 类成员函数作为友元函数:
Friend void GodGay::fun();
第五章
1、运算符重载
在c++中,定义一个处理类的新运算符,
Operator 运算符 (入参){}
注意:
<< 左移运算符不能写到成员函数中
第六章
1、继承
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
构造和析构顺序
子类创建对象时,先调用父类的构造函数,然后再调用自身构造函数。
对象销毁时,析构调用刚好相反,先调用自身,然后再调用父类。
继承中的同名处理:
优先调用自身的属性和函数,如果想调用父类的,需要作用域运算符。
父类中的静态成员属性,可以被子类继承下来。
2、抽象类和虚函数
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,在父类中函数声明virtual,子类可以重写这个函数。
纯虚函数:
virtual int area() = 0;
如果父类中,出现纯虚函数,则子类中,必须要实现。
如何类中出现了纯虚函数,则不能实例化对象,称做抽象类。如果一个类继承了抽象类,必须实现纯虚函数。
3、虚析构
通过父类指针指向子类对象,导致释放不干净。
普通的析构,时不会去调用子类的析构函数,所以可能会导致 资源释放不干净。所以引入虚析构的概念。
在虚构函数前加上 virtual 关键字,后面就会调用子类的虚构函数。
纯虚析构:
Virtual ~animal() = 0;
把虚构函数声明为纯虚析构函数,类内声明,类外实现。
Note:如果类中的析构函数被声明纯虚析构函数,则类也是抽象类,不能进行实例化。
向下类型转换
Animal *an = new Animal;
Cat * cat = (Cat *) an;
父类指针强制转换为子类,cat的this寻址范围变大,实际的资源是父类an的,会产生指针调用越界,不安全!
向上类型转换
Cat *cat = new Cat;
Animal * an = (Animal *) cat;
子类指针强制转换成父类,子类an寻址范围变下,实际资源是cat的,this寻址范围变小,安全的。