参考
- https://github.com/weidongshan/cpp_projects
- 《C++ Primer Plus》
- C++ Standards Support in GCC
- GCC
GCC中有libstdc++库的实现 - LLVM
LLVM中有libc++库的实现 - C++参考手册
- cplusplus
- 面向对象编程的3大特点
- 封装
- 继承
- 多态
- struct 声明的类里的成员都是public
- class 声明的类的成员都是private的,需要通过类的public的成员函数来访问private的成员变量
- this是一个指针,指向当前对象
- 引用命名空间里的类或者函数:
- 使用命名空间A里的类B,有3种方法:
- 在引用B的位置都用
A::B
- 在开头加上
using A::B;
,后面可以直接使用B - 在开头加入
using namespace A;
- C++里的打印需要引用头文件iostream,然后用cout来输出,在使用cout时需要导入std命名空间
- 如:
cout<<"Hello World"<<endl;
- 重载:函数名相同,参数不同(类型、数量、顺序不同)
- 注意: 这里不包括函数的返回值的类型
- 引用和指针:引用相当于变量的别名
int a = 100; int &b = a; int *c = &a; b的类型是引用,b相当于a的别名,b和a表示同一块内存,跟指针不同。 c的类型是指针,c有自己的存储空间,其中存放的是a的地址。
- 构造函数:跟类名相同的成员函数,只看名字,不看参数
- 有了构造函数,可以实现在定义对象时直接初始化,类似结构体变量在定义时初始化那样,例如:
class Person { private: int age; char *name; public: Person() {} /* 如果用户没有传name,那么使用默认值"none" */ person(int age, char *name = "none") { this.age = age; this.name = name; } }; void main() { Person per; // 注意:不可以写成Person per(); Person per2(10); Person *per3 = new Person; Person *per4 = new Person(); Person *per5 = new Person[2]; // 数组,调用两次构造函数 Person *per6 = new Person(10, "Xiaoming"); delete per3; delete per4; delete [] per5; delete per6; }
- 系统会默认提供一个无参数的构造函数
- 如果自己实现了一个有参数的构造函数,那么也必须自己实现一个无参数的构造函数
- 析构函数:跟类名相同的成员函数,并且前面有一个
~
class Person { public: Person() { cout << "Create\n" << endl; } ~Person() { cout << "Delete\n" << endl; } }; - 系统会默认提供一个无参数的析构函数 int main(int argc, const char *argv[]) { int i = 0; // 通过new创建的对象存活到调用delete,或者进程结束 for (i = 0; i < 10; i++) { Person *per = new Person(); } // 通过下面的方式创建的对象类似函数的局部变量,在栈里分配 // 每个for循环调用构造和析构 for (i = 0; i < 10; i++) { Person per; } return 0; }
- 拷贝构造函数
- 默认拷贝构造函数:值拷贝,如果有指针类型的成员,那么只拷贝指针的值
Person per; Person per2(per);
- 自定义拷贝构造函数
class Person { public: Person(Person &per) { this.name = new char[strlen(per.name)+1]; strcpy(this.name, per.name) } }
- 成员初始化列表
- 只能用于构造函数
- 必须用来初始化非静态const成员数据
- 必须用来初始化引用类型的成员数据
- 格式:
Classy:Classy(int n, int m) : mem1(n), mem2(0), mem3(n*m + 2) { // ... }
- 不同作用域的对象构造函数的调用顺序
- 全局对象在main函数之前构造
- 其余的根据程序的执行顺序进行构造
- 在函数体内部的static对象,只构造一次
- 内部有其他类的对象的类构造顺序
- 根据在类内部出现的顺序进行构造,最后调用当前类的构造函数。默认调用的是这些内部对象的无参数的构造函数
- 如果需要调用内部对象的有参数的构造函数,需要在当前类对应的构造函数上标明
- 内部有其他类的对象的类析构顺序:跟上面构造的顺序相反
- 类内部的static类型的成员属于整个类,然后需要使用类名::静态成员来访问
- 为了不创建对象也可以访问静态变量,需要在类外面定义:
int Person::cnt = 0; int Person::get_count(void) { return cnt; } class Person { private: static int cnt ; public: static get_count(void); };
- 在类的静态函数里面不能访问属于对象的成员,因为后者是非静态的,不能确定是那个对象
- 在类的静态函数里可以直接访问类的静态变量,不用在前面加类名
- 友元函数:如果函数func在类A里面被声明为friend,那么func可以访问A的private成员
class Person{ private: int age; public: friend Person add (Person &per1, Person &per2); }; Person add(Person &per1, Person &per2) { Person p; p.age = per1.age + per2.age; return p; };
- 注意:友元函数并不是类的成员函数
- 操作符重载
- 对加法操作的重载
class Person{ private: int age; public: friend Person operator+ (Person &per1, Person &per2); }; Person operator+(Person &per1, Person &per2) { Person p; p.age = per1.age + per2.age; return p; };
- 对++i的重载
class Person{ private: int age; public: friend Person operator++ (Person &per1); }; Person operator++(Person &per1) { per1.age++; return per1; }; // 或者 Person &operator++(Person &per1) { per1.age++; return per1; };
- 对i++的重载
class Person{ private: int age; public: friend Person operator++ (Person &per1, int a); }; Person operator++(Person &per1, int a) { Person tmp; tmp = per1; per1.age++; return tmp; };
- 对输出<<的重载
ostream& operator<<(ostream &o, Person p) { cout<<"age: "<<p.age; return o; }
- 继承:class A : public B
- 从一个类派生出另一个类时,原始类称为基类,继承类称为派生类
- public、protected和private类型的成员的区别
- 不能直接拿父亲的私房钱:派生类不能直接访问基类的private成员
- 可以问父亲要钱:需要通过基类的protected/public成员函数来访问基类的private成员
- 儿子总是比外人亲:派生类可以直接访问基类的protected成员,但是其他代码不可以,这里说的派生类可以访问是指在派生类的成员函数中可以访问
- public的成员,外界可以直接访问
- 在派生类中可以调整从基类继承过来的成员的权限(调高或者调低),前提是派生类可以看到基类的成员,比如基类的private成员,在派生类中就看不到
- public、protected和private在继承方面的区别
- 复写
- 派生类有跟基类相同的成员,名字和参数都一样
- 多重继承:继承多个基类, class A: public B, public C
- 如果不写public的话,默认是private继承
- 如果这些基类中有同名的成员,在派生类中访问会存在二义性,解决方法有2种:
- 在派生类访问时指明用的是哪个基类的成员:
a.B::func();
- 将同名的成员单独抽象出来,消除同名的成员,单独抽象出来的基类通过虚拟继承来实现:
class A: virtual public B {};
- 尽量避免使用多重继承
- 构造顺序:
- 先调用基类的构造:多个基类之间根据继承的顺序构造,从左到右
- 先调用虚拟基类
- 后一般基类
- 自身:
- 对象成员的构造(类的某些成员是其他类的对象)
- 自己的构造函数
- 多态:相同的调用方法调用的是不同的类里的成员函数
- 虚函数:
class Person { private: int age; public: virtual int get_age(void) {return age;} };
- 在使用指针和引用来使用对象时,才会有多态
- 只有类的成员函数才能声明为虚函数
- 静态成员函数不能是虚函数
- 内联函数不能是虚函数
- 构造函数不能是虚函数
- 析构函数一般都声明为虚函数
- 重载的函数不能是虚函数,重载是名字相同,但是参数不同
- 但是如果返回值是当前类的对象的指针或者引用时可以设置为虚函数
- 复写:函数参数、返回值相同,可以设为虚函数
- 静态联编:非虚函数,在编译时确定好调用哪个
- 动态联编:对象里面有指针指向虚函数表,通过指针找到虚函数表,调用其中的函数
- 类型转换:xxx_cast<type_id> (expression)
- reinterpret_cast<>: 相当于C风格的使用小括号的强制类型转换,这种方式无法去掉const属性
- const_cast<>:用于去掉const属性
- dynamic_cast<>: 动态类型转换,type_id必须是类的指针、类的引用或者void *
- 如果type_id是类指针的引用,那么expression也必须是一个指针
- 如果type_id是一个引用,那么expression也必须是一个引用
- 用于多态场合,即:必须有虚函数
- 有可能转换成功,也有可能转换失败,比强制类型转换更加安全
- static_cast<>: 静态类型转换,编译的时候检查是否可以转换成功
- 上行转换安全
- 下行转换不安全
- 上行转换:把派生类的对象转换为基类的对象
- 下行转换:把基类的对象转换为派生类的对象
- 抽象类:含有纯虚函数的类,抽象类不能实例化对象,抽象类用于向派生类定义框架,不提供实现,向上提供统一的接口
- 纯虚函数:虚函数后面跟“=0”,如下所示:
class Person{ public: virtual int get_age(void) = 0; // 不提供实现,只提供框架接口 };
- 派生类如果没有全部复写完基类的纯虚函数,那么这个派生类也是抽象类
- 析构函数不应该使用纯虚函数
- 函数模板
template<typename T> T& mymax(T& a, T& b) { cout<<__PRETTY_FUNCTION__<<endl; return (a < b)? b : a; } int main(int argc, char **argv) { int ia = 1, ib = 2; float fa = 1, fb = 2; double da = 1, db = 2; mymax(ia, ib); mymax(fa, fb); mymax(da, db); return 0; }
- 类模板
template<typename T> class AAA { private: T t; public: void test_func(const T &t); void print(void); }; template<typename T> void AAA<T>::test_func(const T &t) { this->t = t; } template<typename T> void AAA<T>::print(void) { cout<<t<<endl; } int main(int argc, char **argv) { AAA<int> a; a.test_func(1); a.print(); AAA<double> b; b.test_func(1.23); b.print(); return 0; }
- 类模板的重写 ```c++ template<typename T> class AAA { private: T t; public: void test_func(const T &t); void print(void); }; template<typename T> void AAA<T>::test_func(const T &t) { this->t = t; } template<typename T> void AAA<T>::print(void) { cout<<t<<endl; } template<> class AAA<int> { public: void test_func_int(const int & t) { cout<<t<<endl; } void print_int(void); }; void AAA<int>::print_int(void) { cout<<"for test"<<endl; } int main(int argc, char **argv) { AAA<int> a; a.test_func_int(1); a.print_int(); AAA<double> b; b.test_func(1.23); b.print(); return 0; } ```