当前位置:   article > 正文

c++(二)_*p=a和*p=&a

*p=a和*p=&a

1. 类和对象

1.1. 封装

封装的意义

  • 将属性和行为作为一个整体,表现生活中的事物;
  • 将属性和行为加以权限控制
    • public -> 公共权限:类内可以访问,类外也可以访问
    • protected -> 保护权限:类内可以访问,类外不可以访问,子类可以访问
    • private -> 私有权限:类内可以访问,类外不可以访问,子类不可以访问

struct和class的区别

  • struct默认权限为公共权限
  • class默认权限为私有权限

1.2. 对象的初始化和清理

1.2.1. 构造函数

  • 编译器自动调用,完成对象的初始化工作
  • 在创建对象时为对象的成员属性赋值
1.2.1.1. 构造函数的分类

无参构造函数

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. private:
  7. string mName;
  8. int mAge;
  9. public:
  10. // 无参构造函数
  11. Student() {
  12. cout << "调用无参构造函数" << endl;
  13. };
  14. };

有参构造函数

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. private:
  7. string mName;
  8. int mAge;
  9. public:
  10. // 有参构造函数
  11. Student(string name, int age) {
  12. cout << "调用有参构造函数" << endl;
  13. mName = name;
  14. mAge = age;
  15. }
  16. string getName()
  17. {
  18. return mName;
  19. }
  20. int getAge()
  21. {
  22. return mAge;
  23. }
  24. // 析构函数
  25. ~Student() {};
  26. };

拷贝构造函数

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. private:
  7. string mName;
  8. int mAge;
  9. public:
  10. // 拷贝构造函数
  11. Student(const Student &s) {
  12. mName = s.mName;
  13. mAge = s.mAge;
  14. };
  15. string getName()
  16. {
  17. return mName;
  18. }
  19. int getAge()
  20. {
  21. return mAge;
  22. }
  23. // 析构函数
  24. ~Student() {};
  25. };
  26. int main()
  27. {
  28. Student s1 = Student("lisi", 23);
  29. // 调用拷贝构造函数
  30. Student s2 = s1;
  31. // 不能使用匿名对象构造拷贝构造方法,编译器会把代码转换为Student s2; 认为重复定义了一个s2的变量。
  32. //Student(s2); // 错误的代码
  33. }
1.2.1.2. 构造函数的初始化列表
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class CDate
  5. {
  6. public:
  7. CDate(int year, int month, int day)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };
  18. class CGoods
  19. {
  20. public:
  21. CGoods(const char* n, int a, double p, int year, int month, int day):
  22. _date(year, month, day), // 这行代码相当于 CDate _date(year, month, day),即指定有参构造函数构造CDate对象,
  23. _amount(a),
  24. _price(p)
  25. {
  26. strcpy(_name, n); // _name成员变量必须要放到构造函数体中初始化
  27. // _date = CDate(year, month, day) // 如果写在构造函数体中,相当于 CDate _date; _date = CDate(year, month, day),
  28. // 即先构造一个CDate对象(默认使用无参构造函数),然后再给这个对象赋值。
  29. // 但是CDate并没有默认构造函数,因此在编译的时候就会报错。
  30. }
  31. private:
  32. char _name[20];
  33. int _amount;
  34. double _price;
  35. CDate _date; // 成员对象
  36. };
  37. #if 0
  38. int main()
  39. {
  40. CGoods good("沙发", 1, 12000, 2022, 1, 1);
  41. }
  42. #endif // 0
1.2.1.3. 构造函数的成员初始化顺序

成员变量的初始化顺序只与其在类中被声明的顺序有关,而与在初始化列表的顺序无关。

  1. //
  2. // main.cpp
  3. // 数据成员的初始化顺序问题
  4. //
  5. // Created by apple on 2023/10/7.
  6. //
  7. #include <iostream>
  8. #include <string>
  9. using namespace std;
  10. class Point
  11. {
  12. public:
  13. Point(int ix):_iy(ix),_ix(_iy)// 会先初始化_ix,再初始化_iy
  14. {
  15. cout << "Point(int,int)" << endl;
  16. }
  17. void print()
  18. {
  19. cout << _ix << "\t" << _iy << endl;
  20. }
  21. private:
  22. int _ix;
  23. int _iy;
  24. };
  25. int main(int argc, const char * argv[]) {
  26. Point p(3);
  27. p.print();
  28. return 0;
  29. }

打印结果如下:

Point(int,int)

1 3

1.2.1.4. 拷贝构造函数的调用时机(重点)

使用一个已经创建完成的对象来初始化一个新对象

  1. Student s1 = Student("lisi", 23);
  2. // 调用拷贝构造函数
  3. Student s2 = s1;

值传递的方式给函数参数传值

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. int age;
  8. int* p_height;
  9. // 构造函数
  10. // 无参构造函数
  11. Student() {
  12. cout << "调用无参构造函数" << endl;
  13. };
  14. // 有参构造函数
  15. Student(int a, int height) {
  16. cout << "调用有参构造函数" << endl;
  17. age = a;
  18. p_height = new int(height);
  19. }
  20. // 自定义拷贝构造函数
  21. Student(const Student &s) {
  22. cout << "调用自定义的拷贝构造函数" << endl;
  23. age = s.age;
  24. p_height = new int(*s.p_height);
  25. };
  26. //析构函数
  27. ~Student() {
  28. // 在析构函数中可以做一些资源释放的工作
  29. if (p_height != nullptr) {
  30. delete p_height;
  31. p_height = nullptr;
  32. }
  33. cout << "调用析构函数" << endl;
  34. };
  35. };
  36. void func(Student s) // 值传递
  37. {
  38. cout << s.age <<"\t"<< *s.p_height << endl;
  39. }
  40. int main()
  41. {
  42. // 显式构造对象
  43. Student s = Student(23, 170);
  44. // 括号法构造对象
  45. Student s1 (23, 170);
  46. // 以值的方式将对象传递给函数参数,会调用拷贝构造函数
  47. func(s);
  48. }

输出结果如下:

  1. 调用有参构造函数
  2. 调用有参构造函数
  3. 调用自定义的拷贝构造函数
  4. 23 170
  5. 调用析构函数
  6. 调用析构函数
  7. 调用析构函数

以值方式返回局部对象

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. int age;
  8. int* p_height;
  9. // 构造函数
  10. // 无参构造函数
  11. Student() {
  12. cout << "调用无参构造函数" << endl;
  13. };
  14. // 有参构造函数
  15. Student(int a, int height) {
  16. cout << "调用有参构造函数" << endl;
  17. age = a;
  18. p_height = new int(height);
  19. }
  20. // 自定义拷贝构造函数
  21. Student(const Student &s) {
  22. cout << "调用自定义的拷贝构造函数" << endl;
  23. age = s.age;
  24. p_height = new int(*s.p_height);
  25. };
  26. //析构函数
  27. ~Student() {
  28. // 在析构函数中可以做一些资源释放的工作
  29. if (p_height != nullptr) {
  30. delete p_height;
  31. p_height = nullptr;
  32. }
  33. cout << "调用析构函数" << endl;
  34. };
  35. };
  36. Student func1()
  37. {
  38. Student s = Student(21, 180);
  39. // 以值的方式返回局部对象,会调用拷贝函数
  40. return s;
  41. }
  42. int main()
  43. {
  44. Student s = func1();
  45. }
1.2.1.5. 拷贝构造函数的形式是固定的

拷贝构造函数形式:类名(const 类名 & rhs)

是否可以去掉引用符号?即将其改为 类名(const 类名 rhs),答案是不可以,因为会产生构造函数无穷递归调用的情况。

当执行 Point pt2 = pt 时,会调用拷贝构造函数,然后拷贝构造函数的形参会初始化,初始化形参又会调用拷贝构造函数,这样无穷递归下去,直到栈溢出。

是否可以去掉const关键字?即将其改为 类名( 类名 & rhs),答案是不可以,因为非const引用不能绑定右值。

假设 func()函数的返回值是一个 Point 对象,当执行 Point pt2 = func() 时,会调用拷贝构造函数,然而当给拷贝构造函数传递参数时, 如果没有 const,Point & rp = func() 是不正确的,因为 func()是一个临时对象,是右值,非const引用不能绑定右值。

1.2.1.6. 构造函数的调用规则
  • 如果用户定义了有参构造函数,c++不再提供默认的无参构造函数,但是会提供默认的拷贝构造函数;
  • 如果用户定义了拷贝构造函数,c++不再提供其他构造函数。
1.2.1.7. 构造函数的调用方式

总结

Student("zhaoliu", 21); // 匿名对象

Student s1("zhangsan", 20); // 括号法

Student s2 = Student("lisi", 18); // 显式构造

Student s3 = {"wangwu", 19}; // 隐式转换

括号法

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. // 无参构造函数
  8. Student() {
  9. cout << "调用无参构造函数" << endl;
  10. };
  11. // 有参构造函数
  12. Student(string n, int a) {
  13. cout << "调用有参构造函数" << endl;
  14. name = n;
  15. age = a;
  16. }
  17. // 拷贝构造函数
  18. Student(const Student &s) {
  19. name=s.name;
  20. age = s.age;
  21. };
  22. string getName()
  23. {
  24. return name;
  25. }
  26. int getAge()
  27. {
  28. return age;
  29. }
  30. // 析构函数
  31. ~Student() {};
  32. private:
  33. string name;
  34. int age;
  35. };
  36. int main()
  37. {
  38. // 实例化对象:括号法
  39. // 调用无参构造方法时,不能加"()",因为编译器认为这是一个函数的声明
  40. Student s1;
  41. // 调用有参构造方法
  42. Student s2("zhansan", 22);
  43. cout << s2.getName() << "\t"<<s2.getAge() << endl;
  44. // 匿名对象,当前行执行结束之后,系统会立即收掉匿名对象
  45. Student("wangwu", 23);
  46. }

显示法

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. // 无参构造函数
  8. Student() {
  9. cout << "调用无参构造函数" << endl;
  10. };
  11. // 有参构造函数
  12. Student(string n, int a) {
  13. cout << "调用有参构造函数" << endl;
  14. name = n;
  15. age = a;
  16. }
  17. // 拷贝构造函数
  18. Student(const Student &s) {
  19. name=s.name;
  20. age = s.age;
  21. };
  22. string getName()
  23. {
  24. return name;
  25. }
  26. int getAge()
  27. {
  28. return age;
  29. }
  30. // 析构函数
  31. ~Student() {};
  32. private:
  33. string name;
  34. int age;
  35. };
  36. int main()
  37. {
  38. // 实例化对象:显示法
  39. Student s3 = Student("lisi", 23);
  40. cout << s3.getName() << "\t" << s3.getAge() << endl;
  41. // 匿名对象,当前行执行结束之后,系统会立即收掉匿名对象
  42. Student("wangwu", 23);
  43. }

隐式转换法

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. // 无参构造函数
  8. Student() {
  9. cout << "调用无参构造函数" << endl;
  10. };
  11. // 有参构造函数
  12. Student(string n, int a) {
  13. cout << "调用有参构造函数" << endl;
  14. name = n;
  15. age = a;
  16. }
  17. // 拷贝构造函数
  18. Student(const Student &s) {
  19. name=s.name;
  20. age = s.age;
  21. };
  22. string getName()
  23. {
  24. return name;
  25. }
  26. int getAge()
  27. {
  28. return age;
  29. }
  30. // 析构函数
  31. ~Student() {};
  32. private:
  33. string name;
  34. int age;
  35. };
  36. int main()
  37. {
  38. // 实例化对象:隐式转换法,如果有参构造函数只有一个参数,就不需要加"{}",但是可能会导致问题,不推荐。
  39. Student s5 = { "zhaoliu", 26 };
  40. }
1.2.1.8. 带explicit 关键字的构造函数

不带 explicit 关键字

  1. class MyClass {
  2. public:
  3. MyClass(int x) {
  4. value = x;
  5. }
  6. private:
  7. int value;
  8. };
  9. void func(MyClass obj) {
  10. // do something
  11. }
  12. int main() {
  13. MyClass a = 10; // 隐式调用MyClass(int)构造函数
  14. func(10); // 隐式将int转换为MyClass对象
  15. return 0;
  16. }

带 explicit 关键字

  1. class MyClass {
  2. public:
  3. explicit MyClass(int x) {
  4. value = x;
  5. }
  6. private:
  7. int value;
  8. };
  9. void func(MyClass obj) {
  10. // do something
  11. }
  12. int main() {
  13. MyClass a = 10; // 错误:不能隐式转换int到MyClass
  14. MyClass b(10); // 正确:显式调用构造函数
  15. func(10); // 错误:不能隐式转换int到MyClass
  16. func(MyClass(10)); // 正确:显式创建MyClass对象
  17. return 0;
  18. }

1.2.2. 析构函数

  • 编译器自动调用,完成对象的清理工作
  • 在对象销毁前执行一些清理工作
1.2.2.1. 析构函数的调用时机(重点)
  • 栈对象生命周期结束时,会自动调用析构函数;
  • 全局对象在main函数退出时,会自动调用析构函数;
  • (局部)静态对象在main函数退出时,会自动调用析构函数;
  • 堆对象在执行delete时,会自动调用析构函数。

1.2.3. 浅拷贝和深拷贝(重点)

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. int age;
  8. int* p_height;
  9. // 无参构造函数
  10. Student() {
  11. cout << "调用无参构造函数" << endl;
  12. };
  13. // 有参构造函数
  14. Student(int a, int height) {
  15. cout << "调用有参构造函数" << endl;
  16. age = a;
  17. // 在堆内存中创建一块空间
  18. p_height = new int(height);
  19. }
  20. // 自定义拷贝构造函数
  21. Student(const Student &s) {
  22. cout << "调用自定义的拷贝构造函数" << endl;
  23. age = s.age;
  24. // p_height = s.p_height 编译器默认实现的就是这行代码
  25. p_height = new int(*s.p_height); // 自定义实现深拷贝
  26. };
  27. //析构函数
  28. ~Student() {
  29. // 在析构函数中可以做一些资源释放的工作
  30. if (p_height != nullptr) {
  31. // 释放内存
  32. delete p_height;
  33. p_height =nullptr;
  34. }
  35. cout << "调用析构函数" << endl;
  36. };
  37. };
  38. void test()
  39. {
  40. Student s1(18, 170);
  41. cout << "s1对象的年龄:" << s1.age << ",s1对象的身高:" << *s1.p_height << endl;
  42. // Student s2 = s1;
  43. Student s2(s1);
  44. // 如果没有自定义拷贝构造函数,默认是浅拷贝,当s2对象执行析构函数之后,p_height所指向的内存就被释放了
  45. // 然后,当s1对象执行析构函数时,再释放p_height的内存就是非法操作了,因此会报错,解决的办法就是深拷贝。
  46. // 通过自定义拷贝构造函数来实现深拷贝
  47. cout << "s2对象的年龄:" << s2.age << ",s2对象的身高:" << *s2.p_height << endl;
  48. }
  49. int main()
  50. {
  51. test();
  52. }

1.2.4. 赋值函数

  1. class Computer
  2. {
  3. public:
  4. Computer(const char *brand, double price)
  5. : _brand(new char[strlen(brand) + 1]())
  6. , _price(price)
  7. {
  8. cout << "Computer(const char *, double)" << endl;
  9. }
  10. ~Computer()
  11. {
  12. cout << "~Computer()" << endl;
  13. delete [] _brand;
  14. _brand = nullptr;
  15. }
  16. Computer &Computer::operator=(const Computer &rhs)
  17. {
  18. if(this != &rhs) //1、自复制
  19. {
  20. delete [] _brand; //2、释放左操作数
  21. _brand = nullptr;
  22. _brand = new char[strlen(rhs._brand) + 1](); //3、深拷贝
  23. strcpy(_brand, rhs._brand);
  24. _price = rhs._price;
  25. }
  26. return *this; //4、返回*this
  27. }
  28. private:
  29. char *_brand;
  30. double _price;
  31. };
  • 返回类型不必须是类类型:可以是其他类型,但一般没有这种需求。
  • 返回值不一定是引用:可以返回值类型,但这样会引入不必要的拷贝。
  1. class MyClass {
  2. public:
  3. MyClass operator=(const MyClass& other) {
  4. if (this != &other) {
  5. // 执行赋值操作
  6. }
  7. return *this; // 这里会涉及一次拷贝
  8. }
  9. };

1.2.5. 初始化列表

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Student
  5. {
  6. public:
  7. string s_name;
  8. int s_age;
  9. // 初始化列表, s_name(name)相当于string s_name=name, s_age(a)相当于int s_age = a;
  10. Student(string name, int a) :s_name(name), s_age(a)
  11. {
  12. cout << "调用有参构造函数" << endl;
  13. }
  14. };
  15. int main()
  16. {
  17. // 括号法创建对象
  18. Student s1 ("zhangsan", 18);
  19. // 显示法创建对象
  20. Student s2 = Student("lisi", 19);
  21. cout << "s_name:" << s1.s_name << endl;
  22. cout << "s_age:" << s1.s_age << endl;
  23. }

1.2.6. const成员变量(重点)

const成员变量必须要放在初始化列表中进行初始化。

  1. class Book{
  2. public:
  3. Book( int s );
  4. private:
  5. int i;
  6. const int j;
  7. int &k;
  8. Book::Book( int s ): i(s), j(s), k(s){}

1.2.7. 引用成员变量(重点)

引用成员变量也必须要放在初始化列表中进行初始化。

1.2.8. 类对象作为类成员

结论:当其他类对象作为本类成员,构造函数先构造其他类对象,再构造本对象;析构的顺序与构造相反。

1.2.9. 静态成员变量

  • 所有对象共享该成员变量
  • 在编译阶段分配内存
  • 类内声明,类外初始化
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. // 静态成员变量,类内声明
  8. static int pAge;
  9. };
  10. // 类外初始化,注意初始化的语法
  11. int Person::pAge = 18;
  12. void test()
  13. {
  14. // 静态成员变量可以直接通过类名访问
  15. cout << Person::pAge << endl;
  16. }
  17. int main()
  18. {
  19. test();
  20. }

1.2.10. 静态成员函数

  • 静态成员函数内部只能访问静态成员属性和静态成员函数;
  • 静态成员函数的参数列表中没有隐含的this指针。
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. // 静态成员函数,类内声明
  8. static void func();
  9. };
  10. // 类外初始化,注意初始化的语法
  11. void Person::func() {
  12. cout << "Person类的静态成员方法" << endl;
  13. }
  14. int main()
  15. {
  16. Person::func();
  17. }

1.2.11. 虚函数(*)

virtual

  1. #include <iostream>
  2. #include <typeinfo>
  3. using namespace std;
  4. class Base
  5. {
  6. public:
  7. Base(int data = 10):ma(data){} // 构造函数
  8. virtual void show() { cout << "Base::show()" << endl; }
  9. void show(int) { cout << "Base::show(int)" << endl; }
  10. private:
  11. int ma;
  12. };
  13. class Derive :public Base
  14. {
  15. public:
  16. Derive(int data=20):Base(data), mb(data){}
  17. void show() { cout << "Derive::show()" << endl; }
  18. private:
  19. int mb;
  20. };
  21. #if 1
  22. int main()
  23. {
  24. Derive d(50);
  25. Base *pb = &d;
  26. pb->show(); // 动态绑定 Derive::show()
  27. pb->show(10); // 静态绑定 Base::show(int)
  28. cout << sizeof(Base) << endl;
  29. cout << sizeof(Derive) << endl;
  30. cout << typeid(pb).name() << endl; // 指针的类型 class Base*
  31. // 如果Base中没有虚函数,*pb识别的就是编译时期的类型,即Base
  32. // 如果Base中有虚函数,*pb识别的就是运行时期的类型 即RTTI指针指向的类型
  33. cout << typeid(*pb).name() << endl; // 指针指向的类型 class Derive
  34. }
  35. #endif
  • 特性1:如果一个类里面定义了虚函数,那么在编译阶段,编译器就会给这个类产生唯一的一个vftable(虚函数表)。虚函数表中主要存储的内容就是RTTI指针虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的只读数据区。
  • 特性2:如果一个类里面定义了虚函数,那么这个类的内存会多存储一个指向虚函数地址的指针(vfptr)。

  • 特性3:如果派生类中的方法和继承过来的某个方法在返回值、函数名、参数列表上都相同,而且基类中的该方法是虚函数,那么派生类的这个方法会被自动处理成虚函数。

1.2.11.1. 静态绑定

类中的普通函数在编译时就确定了地址了,即为静态绑定。c++的重载函数就是静态绑定,因为在编译的时候就需要确定调用的是哪个函数。

1.2.11.2. 动态绑定

类中的虚函数在编译时不能确定函数地址,即为动态绑定。另外,必须使用指针(引用)的方式调用虚函数才会产生动态绑定。

  1. Base &rb1 = b;
  2. rb1.show();
  3. Base &rb2 = d;
  4. rb2.show();
  5. Derive *pd1 = &d;
  6. pd1->show();

由对象直接调用虚函数不会产生动态绑定,因为可以确定是哪个对象调用的。

  1. Base b;
  2. b.show(); // 静态绑定
  3. Derive d;
  4. d.show(); // 静态绑定
1.2.11.3. 哪些函数不能成为虚函数

首先说一下虚函数依赖

  • 虚函数要能产生地址,存储在虚函数表中;
  • 对象必须存在,只能通过对象来找到虚函数表的地址,进而找到虚函数地址

从虚函数依赖可知,以下函数不能成为虚函数

  • 构造函数:要执行完构造函数后才能创建对象,因此在调用构造函数时还没有对象,不能成为虚函数。且在构造函数中调用虚函数也不会发生静态绑定;
  • static成员函数:跟对象没有关系,也是不行的。
1.2.11.4. 虚析构函数

1.3. c++对象模型和this指针

1.3.1. 成员变量和成员函数内存空间归属

  • 非静态成员变量占用的内存空间属于类对象;
  • 静态成员变量、静态成员函数、非静态成员函数占用的内存空间都不属于类对象;
  • 空对象占用的空间大小为1个字节。

1.3.2. this指针的使用

this指针本质上是一个指针常量,即指向的对象是不可以再更改的,但是指向的对象的值是可以修改的。

每一个成员函数都拥有一个隐含的this指针,这个this指针作为函数的第一个参数。

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. const int NAME_LEN = 20;
  5. class CGoods
  6. {
  7. public:
  8. // 编译的时候会在第一个参数的位置添加this指针,void show(CGood* this);
  9. void show()
  10. {
  11. cout << "show方法" << endl;
  12. }
  13. // 编译的时候会在第一个参数的位置添加this指针,void setPrice(CGood* this, double price)
  14. void setPrice(double price)
  15. {
  16. _price = price;
  17. }
  18. private:
  19. char _name[NAME_LEN];
  20. double _price;
  21. int _amount;
  22. };
  23. #if 1
  24. int main()
  25. {
  26. CGoods good;
  27. good.show();
  28. }
  29. #endif
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. int age;
  8. Person(int age)
  9. {
  10. this->age = age;
  11. }
  12. // 引用作为函数的返回值 Person &p = person
  13. Person& addAge(int age)
  14. {
  15. // this指针
  16. this->age += age;
  17. // this指针的解引用就是当前对象
  18. return *this;
  19. }
  20. };
  21. void test()
  22. {
  23. Person p(10);
  24. p.addAge(10).addAge(10).addAge(10);
  25. cout << p.age << endl;
  26. }
  27. int main()
  28. {
  29. test();
  30. }

1.4. C++ this和*this的区别

  • this返回的是当前对象的地址(指向当前对象的指针);
  • *this返回的是当前对象的克隆和本身(若返回类型是A,则是克隆,若返回类型是A&,则是本身);
  1. std::unique_ptr<PaddleClasModel> PaddleClasModel::Clone() const {
  2. std::unique_ptr<PaddleClasModel> clone_model =
  3. utils::make_unique<PaddleClasModel>(PaddleClasModel(*this));
  4. clone_model->SetRuntime(clone_model->CloneRuntime());
  5. return clone_model;

PaddleClasModel(*this)调用的是默认的拷贝构造函数

  1. class PaddleClasModel {
  2. public:
  3. PaddleClasModel(const PaddleClasModel& other);
  4. };

1.4.1. 常对象和常函数

常函数

  • 成员函数后加const即为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象

  • 声明对象前加const即为常对象
  • 常对象只能调用常函数。常方法,编译器添加的是 const 修饰的 this 指针
  • 非常对象也可以调用常函数
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. int age;
  8. // 成员属性加了mutable关键字,即使在常函数中也是可以更改name属性的值的
  9. mutable string name;
  10. Person(int age, string name)
  11. {
  12. this->age = age;
  13. this->name = name;
  14. }
  15. // 常函数,使用const修饰,本质上就是const Person * const p;因此既不可以更改对象,也不可以更改对象的属性值
  16. void func() const
  17. {
  18. cout << "调用常函数" << endl;
  19. //this->age = 20; 错误,常函数不可以修改成员属性
  20. // name属性是可以修改的
  21. this->name = "wangwu";
  22. }
  23. void func2()
  24. {
  25. cout << "调用普通函数" << endl;
  26. }
  27. };
  28. void test()
  29. {
  30. Person p(10, "lisi");
  31. p.func();
  32. cout << p.name << endl;
  33. // 常对象
  34. const Person p2(20, "zhaoliu");
  35. //p2.func2(); 那么
  36. p2.func();
  37. }
  38. int main()
  39. {
  40. test();
  41. }

1.4.2. 空指针访问成员函数

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. int age;
  8. void showClass()
  9. {
  10. cout << "this is Person class" << endl;
  11. }
  12. void showPersonAge()
  13. {
  14. // age 默认指的是this->age既然this都是空了,那么访问this->age肯定就会报错了
  15. cout << "age = " << age << endl;
  16. }
  17. };
  18. void test()
  19. {
  20. Person *p = nullptr;
  21. //p->showClass(); // 正确
  22. //p->showPersonAge(); // 报错
  23. }
  24. int main()
  25. {
  26. test();
  27. }

1.5. 类和对象代码应用实践

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. using namespace std;
  4. class String
  5. {
  6. public:
  7. String(const char *str=nullptr)
  8. {
  9. if (str != nullptr)
  10. {
  11. m_data = new char[strlen(str) + 1];
  12. strcpy(this->m_data, str);
  13. }
  14. else
  15. {
  16. m_data = new char[1];
  17. *m_data = '\0';
  18. }
  19. }
  20. // 拷贝构造函数
  21. String(const String& str)
  22. {
  23. m_data = new char[strlen(str.m_data) + 1];
  24. strcpy(m_data, str.m_data);
  25. }
  26. // 析构函数
  27. ~String()
  28. {
  29. delete[] m_data;
  30. m_data = nullptr;
  31. }
  32. // 赋值构造函数
  33. String& operator=(const String & other)
  34. {
  35. if (this == &other)
  36. {
  37. return *this;
  38. }
  39. delete[] m_data; // 先释放之前指向堆内存的空间
  40. m_data = new char[strlen(other.m_data) + 1]; // 再重新申请一块堆空间,c语言的字符串最后有一个'\0'字符,因此需要多一个字符的长度
  41. strcpy(this->m_data, other.m_data); // c语言的strcpy函数,再将other的m_data值拷贝到堆空间
  42. return *this;
  43. }
  44. char* m_data;
  45. };
  46. #if 1
  47. int main()
  48. {
  49. String s1; // 调用无参构造函数
  50. String s2("hello"); // 调用有参构造函数
  51. String s3(s2); // 调用拷贝构造函数
  52. s3 = s1; // 调用赋值构造函数
  53. }
  54. #endif

1.6. 指向类成员的指针

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. int mb;
  7. static int si;
  8. void func() { cout << "call Test::func" << endl; }
  9. static void static_func() { cout << "call Test::static_func" << endl; }
  10. };
  11. int Test::si = 20;
  12. int main()
  13. {
  14. Test t1;
  15. int Test::*p = &Test::mb; // 如果写成int *p = &Test::mb; 会报错,无法从 int Test::* 转换成 int *
  16. // 指针指向普通成员变量,脱离对象访问成员是没有意义的,因此,在访问p时必须加上对象,不能直接是*p=20
  17. t1.*p = 20;
  18. cout << t1.mb << endl;
  19. // 指针指向静态成员变量,这里就可以这样写了
  20. int *p1 = &Test::si;
  21. *p1 = 30;
  22. cout << Test::si << endl;
  23. // 指针指向普通成员方法
  24. void(Test::*func)() = &Test::func; // 如果写成void(*func)() = &Test::func; 会报错,无法从 void (__thiscall Test::*)(void)转换成void (__cdecl *)(void)
  25. (t1.*func)();
  26. // 指针指向静态成员方法
  27. void(*static_func)() = &Test::static_func;
  28. (*static_func)();
  29. }

1.7. 对象数组

1.8. 友元

友元提供了另一访问类的私有成员的方案

1.8.1. 全局函数做友元

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Building
  5. {
  6. // 全局函数做友元,告诉编译器,全局函数goodGay是Building类的好朋友,可以访问类中的私有内容
  7. friend void goodGay(Building building);
  8. public:
  9. string m_SittingRoom;
  10. Building()
  11. {
  12. this->m_SittingRoom = "客厅";
  13. this->m_BedRom = "卧室";
  14. }
  15. private:
  16. string m_BedRom;
  17. };
  18. void goodGay(Building building)
  19. {
  20. cout << "好基友正在访问:" << building.m_SittingRoom << endl;
  21. cout << "好基友正在访问:" << building.m_BedRom << endl;
  22. }
  23. int main()
  24. {
  25. Building building;
  26. goodGay(building);
  27. }

1.8.2. 类做友元

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Building;
  5. class GoodGay
  6. {
  7. public:
  8. // 这里只是声明了构造方法
  9. GoodGay();
  10. // 这里只是声明了成员函数
  11. void visit();
  12. private:
  13. // 成员变量
  14. Building *building;
  15. };
  16. class Building
  17. {
  18. // 友元类,告诉编译器GoodGay类可以访问Building类中的私有内容
  19. friend class GoodGay;
  20. public:
  21. // 声明构造方法
  22. Building();
  23. public:
  24. // 公共的成员变量
  25. string m_sittingRoom;
  26. private:
  27. // 私有的成员变量
  28. string m_bedroom;
  29. };
  30. // 在类的外部定义Building构造函数
  31. Building::Building()
  32. {
  33. this->m_bedroom = "卧室";
  34. this->m_sittingRoom = "客厅";
  35. }
  36. // 在类的外部定义GoodGay构造函数
  37. GoodGay::GoodGay() {
  38. building = new Building();
  39. }
  40. // 在类的外部定义visit方法
  41. void GoodGay::visit()
  42. {
  43. cout << "好基友正在访问" << building->m_bedroom << endl;
  44. cout << "好基友正在访问" << building->m_sittingRoom << endl;
  45. }
  46. int main()
  47. {
  48. GoodGay goodGay = GoodGay();
  49. goodGay.visit();
  50. }

1.8.3. 成员函数做友元

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Building;
  5. class GoodGay
  6. {
  7. public:
  8. // 这里只是声明了构造方法
  9. GoodGay();
  10. // 这里只是声明了成员函数
  11. void visit();
  12. private:
  13. // 成员变量
  14. Building *building;
  15. };
  16. class Building
  17. {
  18. // 告诉编译器GoodGay类中的成员函数visit可以访问Building类中的私有内容
  19. friend void GoodGay::visit();
  20. public:
  21. // 声明构造方法
  22. Building();
  23. public:
  24. // 公共的成员变量
  25. string m_sittingRoom;
  26. private:
  27. // 私有的成员变量
  28. string m_bedroom;
  29. };
  30. // 在类的外部定义Building构造函数
  31. Building::Building()
  32. {
  33. this->m_bedroom = "卧室";
  34. this->m_sittingRoom = "客厅";
  35. }
  36. // 在类的外部定义GoodGay构造函数
  37. GoodGay::GoodGay() {
  38. // 无参构造函数"()"可以省略
  39. //building = new Building;
  40. building = new Building();
  41. }
  42. // 在类的外部定义visit方法
  43. void GoodGay::visit()
  44. {
  45. cout << "好基友正在访问" << building->m_bedroom << endl;
  46. cout << "好基友正在访问" << building->m_sittingRoom << endl;
  47. }
  48. int main()
  49. {
  50. GoodGay goodGay = GoodGay();
  51. goodGay.visit();
  52. }

1.9. 运算符重载

注意事项:

  • 对于内置的数据类型的表达式的运算符是不可能改变的;
  • 不要滥用运算符重载

1.9.1. 算术运算符重载

1.9.1.1. 加号运算符重载
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. int m_a;
  8. int m_b;
  9. // 使用成员函数重载+运算符
  10. //Person operator+(Person &p)
  11. //{
  12. // Person temp;
  13. // temp.m_a = m_a + p.m_a;
  14. // temp.m_b = m_b + p.m_b;
  15. // return temp;
  16. //}
  17. };
  18. // 使用全局函数重载+运算符
  19. Person operator+(Person &p1, Person &p2)
  20. {
  21. Person temp;
  22. temp.m_a = p1.m_a + p2.m_a;
  23. temp.m_b = p1.m_b + p2.m_b;
  24. return temp;
  25. }
  26. // 运算符重载的函数重载
  27. Person operator+(Person &p1, int num)
  28. {
  29. Person temp;
  30. temp.m_a = p1.m_a + num;
  31. temp.m_b = p1.m_b + num;
  32. return temp;
  33. }
  34. int main()
  35. {
  36. // 使用括号法调用无参构造函数不能加括号,因此是Person p1,而不是Person p1()
  37. Person p1;
  38. p1.m_a = 10;
  39. p1.m_b = 10;
  40. Person p2;
  41. p2.m_a = 10;
  42. p2.m_b = 10;
  43. Person p3 = p1 + p2;
  44. cout << "p3.m_a = " << p3.m_a << "\t" << "p3.m_b = " << p3.m_b << endl;
  45. Person p4 = p1 + 100;
  46. cout << "p4.m_a = " << p4.m_a << "\t" << "p4.m_b = " << p4.m_b << endl;
  47. }
1.9.1.2. 左移运算符重载

作用:

  • 输出自定义对象的成员变量;
  • 只能使用全局函数重载版本;
  • 如果要输出对象的私有成员,可以配合友元一起使用。
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. // 全局函数做友元,可以访问类中的私有属性
  7. friend ostream & operator<<(ostream &out, Person &p);
  8. private:
  9. int m_a;
  10. int m_b;
  11. public:
  12. void setA(int a)
  13. {
  14. m_a = a;
  15. }
  16. void setB(int b)
  17. {
  18. m_b = b;
  19. }
  20. };
  21. // 使用全局函数重载+运算符
  22. ostream & operator<<(ostream &out, Person &p)
  23. {
  24. out << "p.m_a = " << p.m_a << ", p.m_b = " << p.m_b;
  25. return out;
  26. }
  27. int main()
  28. {
  29. // 使用括号法调用无参构造函数不能加括号,因此是Person p1,而不是Person p1()
  30. Person p1;
  31. p1.setA(10);
  32. p1.setB(10);
  33. cout << p1 << endl;
  34. }
1.9.1.3. 递增运算符重载
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class MyInteger
  5. {
  6. // 全局函数做友元,可以访问类中的私有属性
  7. friend ostream & operator<<(ostream &out, MyInteger myint);
  8. private:
  9. int m_a;
  10. public:
  11. MyInteger()
  12. {
  13. m_a = 1;
  14. }
  15. // 重载前置++运算符,这里必须返回引用,即同一个对象
  16. MyInteger& operator++()
  17. {
  18. ++m_a;
  19. return *this;
  20. }
  21. // 重载后置++运算符
  22. MyInteger operator++(int) //int代表占位参数
  23. {
  24. MyInteger temp = *this; // 先保存当前对象 *this 就表示当前对象
  25. m_a++; // 然后再让对象中的m_a的值自增
  26. return temp;
  27. }
  28. };
  29. // 使用全局函数重载<<运算符
  30. ostream & operator<<(ostream &out, MyInteger myint)
  31. {
  32. out << "myint.m_a = " << myint.m_a;
  33. return out;
  34. }
  35. int main()
  36. {
  37. MyInteger myint;
  38. cout << ++myint << endl;
  39. cout << myint << endl;
  40. cout << myint++ << endl;
  41. cout << myint << endl;
  42. }
1.9.1.4. 赋值运算符重载
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. int *m_age;
  8. public:
  9. Person(int age)
  10. {
  11. // new int 返回的是int类型的指针
  12. m_age = new int(age);
  13. }
  14. ~Person()
  15. {
  16. if (m_age != nullptr)
  17. {
  18. delete m_age;
  19. m_age = nullptr;
  20. }
  21. }
  22. // 重载赋值运算符
  23. Person& operator=(Person &p)
  24. {
  25. if (m_age != nullptr) {
  26. delete m_age; // 释放m_age的内存
  27. m_age = new int(*p.m_age); // 重新拷贝一份,放在堆内存,在拷贝之前需要将this指针指向的对象的m_age属性的空间给释放,防止野指针
  28. return *this; // 为了能够链式调用,需要返回对象的引用
  29. }
  30. }
  31. };
  32. int main()
  33. {
  34. Person p1(10);
  35. Person p2(20);
  36. Person p3(30);
  37. // 默认是浅拷贝,在析构函数中清空内存就会存在问题,因此需要手动改为深拷贝
  38. p3 = p2 = p1;
  39. cout << "p1的年龄为:" << *p1.m_age << endl;
  40. cout << "p2的年龄为:" << *p2.m_age << endl;
  41. cout << "p3的年龄为:" << *p3.m_age << endl;
  42. }
1.9.1.5. new&delete运算符重载

void* operator new(size_t size)

void operator delete(void* ptr)

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. void* operator new(size_t size) // 参数必须是size_t(unsigned long long),返回值必须是void*。是静态成员函数
  5. {
  6. cout << "调用了全局重载的new:" << size << "字节。\n";
  7. // 申请内存
  8. void* ptr = malloc(size);
  9. cout << "申请到的内存的地址是:" << ptr << endl;
  10. return ptr;
  11. }
  12. void operator delete(void* ptr) // 参数必须是void *,返回值必须是void。是静态成员函数
  13. {
  14. cout << "调用了全局重载的delete。\n";
  15. // 判断是否是空指针
  16. if (ptr == 0) return; // 对空指针delete是安全的。
  17. free(ptr); // 释放内存。
  18. }
  19. class CGirl // 超女类CGirl。
  20. {
  21. public:
  22. int m_bh; // 编号。
  23. int m_xw; // 胸围。
  24. CGirl(int bh, int xw) { m_bh = bh, m_xw = xw; cout << "调用了构造函数CGirl()\n"; }
  25. ~CGirl() { cout << "调用了析构函数~CGirl()\n"; }
  26. void* operator new(size_t size)
  27. {
  28. cout << "调用了类的重载的new:" << size << "字节。\n";
  29. // 申请内存
  30. void* ptr = malloc(size);
  31. cout << "申请到的内存的地址是:" << ptr << endl;
  32. return ptr;
  33. }
  34. void operator delete(void* ptr) // 参数必须是void *,返回值必须是void。
  35. {
  36. cout << "调用了类的重载的delete。\n";
  37. // 判断是否是空指针
  38. if (ptr == 0) return; // 对空指针delete是安全的。
  39. free(ptr); // 释放内存。
  40. }
  41. };
  42. int main()
  43. {
  44. // 会调用全局重载函数new
  45. int* p1 = new int(3);
  46. // 会调用全局重载函数delete
  47. delete p1;
  48. CGirl* p2 = new CGirl(3, 8);
  49. cout << "p2的地址是:" << p2 << "编号:" << p2->m_bh << ",胸围:" << p2->m_xw << endl;
  50. delete p2;
  51. }

1.10. 类的自动类型转换

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class CGirl // 超女类CGirl。
  5. {
  6. public:
  7. int m_bh; // 编号。
  8. string m_name; // 姓名。
  9. double m_weight; // 体重,单位:kg。
  10. // 默认构造函数。
  11. CGirl() { m_bh = 0; m_name.clear(); m_weight = 0; cout << "调用了CGirl()\n"; }
  12. // 自我介绍的方法。
  13. void show() { cout << "bh=" << m_bh << ",name=" << m_name << ",weight=" << m_weight << endl; }
  14. // 关闭自动类型转换,但是可以显式转换
  15. explicit CGirl(int bh) { m_bh = bh; m_name.clear(); m_weight = 0; cout << "调用了CGirl(int bh)\n"; }
  16. //CGirl(double weight) { m_bh = 0; m_name.clear(); m_weight = weight; cout << "调用了CGirl(double weight)\n"; }
  17. };
  18. int main()
  19. {
  20. //CGirl g1(8); // 常规的写法。
  21. //CGirl g1 = CGirl(8); // 显式转换。
  22. //CGirl g1 = 8; // 隐式转换。
  23. CGirl g1; // 创建对象。
  24. g1 = (CGirl)8; // 隐式转换,用CGirl(8)创建临时对象,再赋值给g。
  25. //CGirl g1 = 8.7; // 隐式转换。
  26. //g1.show();
  27. }

1.11. 继承

1.11.1. 继承的基本语法

class 派生类名:[继承方式]基类名

1.11.2. 继承方式

  • public
  • protected
  • private

默认是private。不管继承方式如何,基类中的private成员在派生类中始终不能使用。

1.11.3. 继承中构造和析构顺序

  • 创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数;
  • 销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。如果手工调用派生类的析构函数,也会调用基类的析构函数
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. class A { // 基类
  4. public:
  5. int m_a;
  6. private:
  7. int m_b;
  8. public:
  9. A() : m_a(0), m_b(0) // 基类的默认构造函数。
  10. {
  11. cout << "调用了基类的默认构造函数A()。\n";
  12. }
  13. A(int a, int b) : m_a(a), m_b(b) // 基类有两个参数的构造函数。
  14. {
  15. cout << "调用了基类的构造函数A(int a,int b)。\n";
  16. }
  17. A(const A &a) : m_a(a.m_a + 1), m_b(a.m_b + 1) // 基类的拷贝构造函数。
  18. {
  19. cout << "调用了基类的拷贝构造函数A(const A &a)。\n";
  20. }
  21. // 显示基类A全部的成员。
  22. void showA() { cout << "m_a=" << m_a << ",m_b=" << m_b << endl; }
  23. };
  24. class B :public A // 派生类
  25. {
  26. public:
  27. int m_c;
  28. B() : m_c(0), A() // 派生类的默认构造函数,指明用基类的默认构造函数(不指明也无所谓)。顺序也无所谓
  29. {
  30. cout << "调用了派生类的默认构造函数B()。\n";
  31. }
  32. B(int a, int b, int c) : A(a, b), m_c(c) // 指明用基类的有两个参数的构造函数。
  33. {
  34. cout << "调用了派生类的构造函数B(int a,int b,int c)。\n";
  35. }
  36. B(const A& a, int c) :A(a), m_c(c) // 指明用基类的拷贝构造函数。
  37. {
  38. cout << "调用了派生类的构造函数B(const A &a,int c) 。\n";
  39. }
  40. // 显示派生类B全部的成员。
  41. void showB() { cout << "m_c=" << m_c << endl << endl; }
  42. };
  43. int main()
  44. {
  45. B b1; // 将调用基类默认的构造函数。
  46. b1.showA(); b1.showB();
  47. B b2(1, 2, 3); // 将调用基类有两个参数的构造函数。
  48. b2.showA(); b2.showB();
  49. A a(10, 20); // 创建基类对象。
  50. B b3(a, 30); // 将调用基类的拷贝造函数。
  51. b3.showA(); b3.showB();
  52. }

注意事项:

  • 如果没有指定基类构造函数,将使用基类的默认构造函数。如果基类没有默认构造函数,将报错;
  • 可以用初始化列表指明要使用的基类构造函数;
  • 基类构造函数负责初始化被继承的数据成员,派生类构造函数主要用于初始化新增的数据成员;
  • 派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数;

1.11.4. 继承中同名成员处理方式

  • 子类对象可以直接访问到子类中同名成员;
  • 子类对象加作用域可以访问到父类同名成员;
  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

1.11.5. 继承同名静态成员处理方式

  • 访问子类同名成员,直接访问即可;
  • 访问父类同名成员,需要加作用域。

1.11.6. 多继承语法

class 子类: 继承方式 父类1, 继承方式 父类2 ...

c++实际开发中不建议用多继承

1.12. 多态

1.12.1. 背景

通过基类只能访问派生类的成员变量,但是不能访问派生类的成员函数。

1.12.2. 虚函数

为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

注意事项:

  • 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加;
  • 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数;
  • 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员;
  • 构造函数不能是虚函数;
  • 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。
  1. #include <iostream>
  2. using namespace std;
  3. //军队
  4. class Troops {
  5. public:
  6. // 基类设置为虚函数
  7. virtual void fight() { cout << "Strike back!" << endl; }
  8. };
  9. //陆军
  10. class Army : public Troops {
  11. public:
  12. void fight() { cout << "--Army is fighting!" << endl; }
  13. };
  14. //99A主战坦克
  15. class _99A : public Army {
  16. public:
  17. void fight() { cout << "----99A(Tank) is fighting!" << endl; }
  18. };
  19. //武直10武装直升机
  20. class WZ_10 : public Army {
  21. public:
  22. void fight() { cout << "----WZ-10(Helicopter) is fighting!" << endl; }
  23. };
  24. //长剑10巡航导弹
  25. class CJ_10 : public Army {
  26. public:
  27. void fight() { cout << "----CJ-10(Missile) is fighting!" << endl; }
  28. };
  29. //空军
  30. class AirForce : public Troops {
  31. public:
  32. void fight() { cout << "--AirForce is fighting!" << endl; }
  33. };
  34. //J-20隐形歼击机
  35. class J_20 : public AirForce {
  36. public:
  37. void fight() { cout << "----J-20(Fighter Plane) is fighting!" << endl; }
  38. };
  39. //CH5无人机
  40. class CH_5 : public AirForce {
  41. public:
  42. void fight() { cout << "----CH-5(UAV) is fighting!" << endl; }
  43. };
  44. //6K轰炸机
  45. class H_6K : public AirForce {
  46. public:
  47. void fight() { cout << "----H-6K(Bomber) is fighting!" << endl; }
  48. };
  49. int main() {
  50. // 基类指针
  51. Troops* p = new Troops;
  52. p->fight();
  53. //陆军
  54. p = new Army;
  55. p->fight();
  56. p = new _99A;
  57. p->fight();
  58. p = new WZ_10;
  59. p->fight();
  60. p = new CJ_10;
  61. p->fight();
  62. //空军
  63. p = new AirForce;
  64. p->fight();
  65. p = new J_20;
  66. p->fight();
  67. p = new CH_5;
  68. p->fight();
  69. p = new H_6K;
  70. p->fight();
  71. return 0;
  72. }

1.12.3. 纯虚函数

语法格式:virtual 返回值类型 函数名 (函数参数) = 0;

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。

  1. #include <iostream>
  2. using namespace std;
  3. //线
  4. class Line {
  5. public:
  6. Line(float len);
  7. virtual float area() = 0;
  8. virtual float volume() = 0;
  9. protected:
  10. float m_len;
  11. };
  12. //类外定义构造函数
  13. Line::Line(float len) : m_len(len) { }
  14. //矩形,继承线类,也是一个抽象类,不能实例化对象
  15. class Rec : public Line {
  16. public:
  17. Rec(float len, float width);
  18. float area();
  19. protected:
  20. float m_width;
  21. };
  22. Rec::Rec(float len, float width) : Line(len), m_width(width) { }
  23. float Rec::area() { return m_len * m_width; }
  24. //长方体
  25. class Cuboid : public Rec {
  26. public:
  27. Cuboid(float len, float width, float height);
  28. float area();
  29. float volume();
  30. protected:
  31. float m_height;
  32. };
  33. Cuboid::Cuboid(float len, float width, float height) : Rec(len, width), m_height(height) { }
  34. float Cuboid::area() { return 2 * (m_len * m_width + m_len * m_height + m_width * m_height); }
  35. float Cuboid::volume() { return m_len * m_width * m_height; }
  36. //正方体
  37. class Cube : public Cuboid {
  38. public:
  39. Cube(float len);
  40. float area();
  41. float volume();
  42. };
  43. Cube::Cube(float len) : Cuboid(len, len, len) { }
  44. float Cube::area() { return 6 * m_len * m_len; }
  45. float Cube::volume() { return m_len * m_len * m_len; }
  46. int main() {
  47. //Line* p0 = new Rec(10, 20); // error
  48. Line* p = new Cuboid(10, 20, 30);
  49. cout << "The area of Cuboid is " << p->area() << endl;
  50. cout << "The volume of Cuboid is " << p->volume() << endl;
  51. p = new Cube(15);
  52. cout << "The area of Cube is " << p->area() << endl;
  53. cout << "The volume of Cube is " << p->volume() << endl;
  54. return 0;
  55. }

运行结果:
The area of Cuboid is 2200
The volume of Cuboid is 6000
The area of Cube is 1350
The volume of Cube is 3375

本例中定义了四个类,它们的继承关系为:Line --> Rec --> Cuboid --> Cube。

Line 是一个抽象类,也是最顶层的基类,在 Line 类中定义了两个纯虚函数 area() 和 volume()。

在 Rec 类中,实现了 area() 函数;所谓实现,就是定义了纯虚函数的函数体。但这时 Rec 仍不能被实例化,因为它没有实现继承来的 volume() 函数,volume() 仍然是纯虚函数,所以 Rec 也仍然是抽象类。

直到 Cuboid 类,才实现了 volume() 函数,才是一个完整的类,才可以被实例化。

可以发现,Line 类表示“线”,没有面积和体积,但它仍然定义了 area() 和 volume() 两个纯虚函数。这样的用意很明显:Line 类不需要被实例化,但是它为派生类提供了“约束条件”,派生类必须要实现这两个函数,完成计算面积和体积的功能,否则就不能实例化。

在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现)。这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的。虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”。

抽象基类除了约束派生类的功能,还可以实现多态。请注意第 51 行代码,指针 p 的类型是 Line,但是它却可以访问派生类中的 area() 和 volume() 函数,正是由于在 Line 类中将这两个函数定义为纯虚函数;如果不这样做,51 行后面的代码都是错误的。我想,这或许才是C++提供纯虚函数的主要目的。

关于纯虚函数的几点说明

1) 一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。
2) 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。如下例所示:

  1. //顶层函数不能被声明为纯虚函数
  2. void fun() = 0; //compile error
  3. class base{
  4. public :
  5. //普通成员函数不能被声明为纯虚函数
  6. void display() = 0; //compile error
  7. };

1.12.4. 虚析构函数

  1. #include<iostream>
  2. using namespace std;
  3. class ClxBase
  4. {
  5. public:
  6. ClxBase() {};
  7. virtual ~ClxBase() { cout << "delete ClxBase" << endl; };
  8. virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
  9. };
  10. class ClxDerived : public ClxBase
  11. {
  12. public:
  13. ClxDerived() {};
  14. ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
  15. void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
  16. };
  17. int main(int argc, char const* argv[])
  18. {
  19. ClxBase* pTest = new ClxDerived;
  20. pTest->DoSomething();
  21. delete pTest;
  22. return 0;
  23. }

打印结果如下:

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

delete ClxBase

如果基类ClxBase的析构函数没有定义成虚函数,那么打印结果为:

Do something in class ClxDerived!

delete ClxBase

即不会调用派生类的析构函数,这样会造成数据泄露的问题。

虚析构函数的作用:

(1)如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调用父类的析构函数,而不调用子类的析构函数。
(2)如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调用子类的析构函数,再调用父类的析构函数。

1.12.5. 运行阶段类型识别 dynamic_cast

语法格式:派生类指针 = dynamic_cast<派生类类型 *>(基类指针);

如果转换成功,dynamic_cast返回对象的地址,如果失败,返回nullptr。

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. class Hero // 英雄基类
  4. {
  5. public:
  6. int viability; // 生存能力。
  7. int attack; // 攻击伤害。
  8. virtual void skill1() { cout << "英雄释放了一技能。\n"; }
  9. virtual void skill2() { cout << "英雄释放了二技能。\n"; }
  10. virtual void uskill() { cout << "英雄释放了大绝招。\n"; }
  11. };
  12. class XS :public Hero // 西施派生类
  13. {
  14. public:
  15. void skill1() { cout << "西施释放了一技能。\n"; }
  16. void skill2() { cout << "西施释放了二技能。\n"; }
  17. void uskill() { cout << "西施释放了大招。\n"; }
  18. void show() { cout << "我是天下第一美女。\n"; }
  19. };
  20. class HX :public Hero // 韩信派生类
  21. {
  22. public:
  23. void skill1() { cout << "韩信释放了一技能。\n"; }
  24. void skill2() { cout << "韩信释放了二技能。\n"; }
  25. void uskill() { cout << "韩信释放了大招。\n"; }
  26. };
  27. class LB :public Hero // 李白派生类
  28. {
  29. public:
  30. void skill1() { cout << "李白释放了一技能。\n"; }
  31. void skill2() { cout << "李白释放了二技能。\n"; }
  32. void uskill() { cout << "李白释放了大招。\n"; }
  33. };
  34. int main()
  35. {
  36. // 根据用户选择的英雄,施展一技能、二技能和大招。
  37. int id = 0; // 英雄的id。
  38. cout << "请输入英雄(1-西施;2-韩信;3-李白。):";
  39. cin >> id;
  40. // 创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数。
  41. Hero* ptr = nullptr;
  42. if (id == 1) { // 1-西施
  43. ptr = new XS;
  44. }
  45. else if (id == 2) { // 2-韩信
  46. ptr = new HX;
  47. }
  48. else if (id == 3) { // 3-李白
  49. ptr = new LB;
  50. }
  51. if (ptr != nullptr) {
  52. ptr->skill1();
  53. ptr->skill2();
  54. ptr->uskill();
  55. // 如果基类指针指向的对象是西施,那么就调用西施的show()函数。
  56. //if (id == 1) {
  57. // XS* pxs = (XS *)ptr; // C风格强制转换的方法,程序员必须保证目标类型正确。
  58. // pxs->show();
  59. //}
  60. XS* xsptr = dynamic_cast<XS*>(ptr); // 把基类指针转换为派生类。
  61. if (xsptr != nullptr) xsptr->show(); // 如果转换成功,调用派生类西施的非虚函数。
  62. delete ptr;
  63. }
  64. // 以下代码演示把基类引用转换为派生类引用时发生异常的情况。
  65. /*HX hx;
  66. Hero& rh = hx;
  67. try{
  68. XS & rxs= dynamic_cast<XS &>(rh);
  69. }
  70. catch (bad_cast) {
  71. cout << "出现了bad_cast异常。\n";
  72. }*/
  73. }

注意:

1)dynamic_cast只适用于包含虚函数的类。

2)dynamic_cast可以将派生类指针转换为基类指针,这种画蛇添足的做法没有意义。

3)dynamic_cast可以用于引用,但是,没有与空指针对应的引用值,如果转换请求不正确,会出现bad_cast异常。

1.13. 函数模板

  1. template <typename T>
  2. void Swap(T &a, T &b)
  3. {
  4. T tmp = a;
  5. a = b;
  6. b = tmp;
  7. }

1.13.1. 函数模板的注意事项

  • 可以为类的成员函数创建模板,但不能是虚函数析构函数
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class CGirl
  5. {
  6. public:
  7. template<typename T>
  8. CGirl(T a)
  9. {
  10. cout << "a= " << a << endl;
  11. }
  12. template<typename T>
  13. void show()
  14. {
  15. cout << "show方法" << endl;
  16. }
  17. // 错误的
  18. //template<typename T>
  19. //virtual void show()
  20. //{
  21. // cout << "show方法" << endl;
  22. //}
  23. //template<typename T>
  24. //~CGirl()
  25. //{
  26. //}
  27. };
  28. int main()
  29. {
  30. int a = 10;
  31. CGirl g = CGirl(a);
  32. g.show<int>();
  33. }
  • 使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上。
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. template <typename T>
  5. void Swap(T &a, T &b) // 传引用
  6. {
  7. T tmp = a;
  8. a = b;
  9. b = tmp;
  10. }
  11. int main()
  12. {
  13. // 错误的,传引用,必须是数据类型一致的,不能进行隐式转换
  14. //int a = 10;
  15. //char b = 30;
  16. //Swap(a, b);
  17. }
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. template <typename T>
  5. void Swap(T a, T b) // 传值
  6. {
  7. T tmp = a;
  8. a = b;
  9. b = tmp;
  10. }
  11. int main()
  12. {
  13. // 正确的
  14. int a = 10;
  15. char b = 30;
  16. Swap<int>(a, b); // 可以发生隐式转换
  17. }
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. // 函数模板中没有用到模板参数
  5. template <typename T>
  6. void Swap()
  7. {
  8. cout << "调用了Swap函数" << endl;
  9. }
  10. int main()
  11. {
  12. // 错误的
  13. //Swap();
  14. // 正确的,显式的指定。
  15. Swap<int>();
  16. }
  • 使用函数模板时,推导的数据类型必须适应函数模板中的代码。
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. template <typename T>
  5. T Add(T a, T b)
  6. {
  7. return a + b;
  8. }
  9. class CGirl
  10. {
  11. };
  12. int main()
  13. {
  14. //错误的,CGirl对象没有+运算
  15. //CGirl g1;
  16. //CGirl g2;
  17. //Add(g1 + g2);
  18. }
  • 使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。
  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. template <typename T>
  5. void Swap(T a, T b)
  6. {
  7. T tmp = a;
  8. a = b;
  9. b = tmp;
  10. }
  11. int main()
  12. {
  13. // 正确的
  14. int a = 10;
  15. char b = 30;
  16. Swap<int>(a, b); // 显式指定了int数据类型,可以发生从char到int的数据类型转换
  17. }

1.13.2. 函数模板具体化

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. class CGirl // 超女类。
  4. {
  5. public:
  6. int m_bh; // 编号。
  7. string m_name; // 姓名。
  8. int m_rank; // 排名。
  9. };
  10. template <typename T>
  11. void Swap(T& a, T& b); // 交换两个变量的值函数模板。
  12. template<>
  13. void Swap<CGirl>(CGirl& g1, CGirl& g2); // 交换两个超女对象的排名。这个函数是函数模板的具体化函数
  14. // template<>
  15. // void Swap(CGirl& g1, CGirl& g2); // 交换两个超女对象的排名。
  16. int main()
  17. {
  18. int a = 10, b = 20;
  19. Swap(a, b); // 使用了函数模板。
  20. cout << "a=" << a << ",b=" << b << endl;
  21. CGirl g1, g2;
  22. g1.m_rank = 1; g2.m_rank = 2;
  23. Swap(g1, g2); // 使用了超女类的具体化函数。
  24. cout << "g1.m_rank=" << g1.m_rank << ",g2.m_rank=" << g2.m_rank << endl;
  25. }

编译器使用各种函数的规则:

  1. 具体化优先于常规模板,普通函数优先于具体化和常规模板。
  2. 如果希望使用函数模板,可以用空模板参数强制使用函数模板。
  3. 如果函数模板能产生更好的匹配,将优先于普通函数。
  1. #include <iostream> // 包含头文件。
  2. #include<string>
  3. using namespace std; // 指定缺省的命名空间。
  4. void Swap(int a, int b) // 普通函数。
  5. {
  6. cout << "使用了普通函数。\n";
  7. }
  8. template <typename T>
  9. void Swap(T a, T b) // 函数模板。
  10. {
  11. cout << "使用了函数模板。\n";
  12. }
  13. template <>
  14. void Swap(int a, int b) // 函数模板的具体化版本。
  15. {
  16. cout << "使用了具体化的函数模板。\n";
  17. }
  18. int main()
  19. {
  20. Swap(1,2); // 会调用普通函数
  21. Swap('c', 'd'); // 会调用函数模板,因为不用进行隐式转换
  22. Swap<>(1,2); // 用空模板,会强制调用函数模板的具体化版本
  23. }

1.13.3. 函数模板分文件编写

记住下面两点就可以了:

  • 函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。
  • 函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。

1.13.4. 函数模板高级

1.13.4.1. decltype关键字

语法:decltype(expression) var;

作用:用于分析表达式的数据类型

  1. #include <iostream> // 包含头文件。
  2. #include<string>
  3. using namespace std; // 指定缺省的命名空间。
  4. template <typename T>
  5. auto Swap(T a, T b) // 函数模板。
  6. {
  7. decltype(a + b) temp = a + b;
  8. cout << "temp = " << temp << endl;
  9. return temp;
  10. }
  11. int main()
  12. {
  13. auto res = Swap('c', 'd');
  14. cout << res << endl;
  15. }
  1. #include <iostream> // 包含头文件。
  2. #include<string>
  3. using namespace std; // 指定缺省的命名空间。
  4. int func()
  5. {
  6. cout << "调用了func函数" << endl;
  7. return 3;
  8. }
  9. int main()
  10. {
  11. decltype(func()) f = func(); // 函数返回值的数据类型
  12. cout << f << endl;
  13. decltype(func) *f = func; // 函数类型
  14. f(); // 调用func函数
  15. }
1.13.4.2. typename 的用法
  1. template <typename T>
  2. struct MakeUniqueResult {
  3. using scalar = std::unique_ptr<T>;
  4. };
  5. template <typename T, typename... Args>
  6. typename MakeUniqueResult<T>::scalar make_unique(Args &&... args) { // NOLINT
  7. return std::unique_ptr<T>(
  8. new T(std::forward<Args>(args)...)); // NOLINT(build/c++11)
  9. }

上面代码中,编译器无法自动区分 MakeUniqueResult<T>::scalar 是一个类型还是一个成员变量。为了明确告诉编译器 scalar 是一个类型,我们使用 typename 关键字。没有 typename,编译器会产生错误,因为它不能确定 scalar 的含义。

1.14. 类模板

1.14.1. 语法

  1. template <class T>
  2. class 类模板名
  3. {
  4. 类的定义;
  5. };

1.14.2. 注意事项

  • 在创建对象的时候,必须指明具体的数据类型。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template <class T1, class T2>
  4. class AA
  5. {
  6. public:
  7. T1 m_a; // 通用类型用于成员变量。
  8. T2 m_b; // 通用类型用于成员变量。
  9. AA() { } // 默认构造函数是空的。
  10. // 通用类型用于成员函数的参数。
  11. AA(T1 a, T2 b) :m_a(a), m_b(b) { }
  12. // 通用类型用于成员函数的返回值。
  13. T1 geta() // 获取成员m_a的值。
  14. {
  15. T1 a = 2; // 通用类型用于成员函数的代码中。
  16. return m_a + a;
  17. }
  18. T2 getb(); // 获取成员m_b的值。
  19. };
  20. // 模板类的成员函数可以在类外实现。
  21. template<class T1, class T2>
  22. T2 AA<T1, T2>::getb()
  23. {
  24. return m_b;
  25. }
  26. int main()
  27. {
  28. AA<int, string>* a; // 在创建对象的时候,必须指明具体的数据类型。AA a 是错误的。
  29. }
  • 使用类模板时,数据类型必须适应类模板中的代码。
  • 类模板可以为通用数据类型指定缺省的数据类型。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template <class T1, class T2=string> // 指定通用数据类型的数据类型
  4. class AA
  5. {
  6. }
  • 模板类的成员函数可以在类外实现。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template <class T1, class T2>
  4. class AA
  5. {
  6. public:
  7. T1 m_a; // 通用类型用于成员变量。
  8. T2 m_b; // 通用类型用于成员变量。
  9. AA() { } // 默认构造函数是空的。
  10. // 通用类型用于成员函数的参数。
  11. AA(T1 a, T2 b) :m_a(a), m_b(b) { }
  12. T2 getb(); // 获取成员m_b的值。
  13. };
  14. // 模板类的成员函数可以在类外实现。
  15. template<class T1, class T2>
  16. T2 AA<T1, T2>::getb()
  17. {
  18. return m_b;
  19. }
  • 可以用new创建模板类对象。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template <class T1, class T2=string>
  4. class AA
  5. {
  6. public:
  7. T1 m_a; // 通用类型用于成员变量。
  8. T2 m_b; // 通用类型用于成员变量。
  9. AA() { } // 默认构造函数是空的。
  10. // 通用类型用于成员函数的参数。
  11. AA(T1 a, T2 b) :m_a(a), m_b(b) { }
  12. // 通用类型用于成员函数的返回值。
  13. T1 geta() // 获取成员m_a的值。
  14. {
  15. T1 a = 2; // 通用类型用于成员函数的代码中。
  16. return m_a + a;
  17. }
  18. };
  19. int main()
  20. {
  21. AA<int, string> *b = new AA<int, string>();
  22. }
  • 在程序中,模板类的成员函数使用了才会创建。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template <class T1, class T2=string>
  4. class AA
  5. {
  6. public:
  7. T1 m_a; // 通用类型用于成员变量。
  8. T2 m_b; // 通用类型用于成员变量。
  9. AA() { } // 默认构造函数是空的。
  10. // 该成员函数并不会被调用,因此也不会报错
  11. T1 gethaha()
  12. {
  13. return m_a.hahaha();
  14. }
  15. // 通用类型用于成员函数的参数。
  16. AA(T1 a, T2 b) :m_a(a), m_b(b) { }
  17. };
  18. int main()
  19. {
  20. AA<int, string>* a; //在程序中,模板类的成员函数使用了才会创建。
  21. }

1.14.3. 类模板的具体化(重点)

  • 可以部分具体化,也可以完全具体化;
  • 具体化程度高的类优先于具体化程度低的类,具体化的类优先于没有具体化的类。
  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. /
  4. // 类模板
  5. template<class T1, class T2>
  6. class AA { // 类模板。
  7. public:
  8. T1 m_x;
  9. T2 m_y;
  10. AA(const T1 x, const T2 y) :m_x(x), m_y(y) { cout << "类模板:构造函数。\n"; }
  11. void show() const;
  12. };
  13. template<class T1, class T2>
  14. void AA<T1, T2>::show() const { // 成员函数类外实现。
  15. cout << "类模板:x = " << m_x << ", y = " << m_y << endl;
  16. }
  17. /
  18. // 类模板完全具体化
  19. template<>
  20. class AA<int, string> {
  21. public:
  22. int m_x;
  23. string m_y;
  24. AA(const int x, const string y) :m_x(x), m_y(y) { cout << "完全具体化:构造函数。\n"; }
  25. void show() const;
  26. };
  27. void AA<int, string>::show() const { // 成员函数类外实现。
  28. cout << "完全具体化:x = " << m_x << ", y = " << m_y << endl;
  29. }
  30. /
  31. // 类模板部分具体化
  32. template<class T1>
  33. class AA<T1, string> {
  34. public:
  35. T1 m_x;
  36. string m_y;
  37. AA(const T1 x, const string y) :m_x(x), m_y(y) { cout << "部分具体化:构造函数。\n"; }
  38. void show() const;
  39. };
  40. template<class T1>
  41. void AA<T1, string>::show() const { // 成员函数类外实现。
  42. cout << "部分具体化:x = " << m_x << ", y = " << m_y << endl;
  43. }
  44. /
  45. int main()
  46. {
  47. // 具体化程度高的类优先于具体化程度低的类,具体化的类优先于没有具体化的类。
  48. AA<int, string> aa1(8, "我是一只傻傻鸟。"); // 将使用完全具体化的类。
  49. AA<char, string> aa2(8, "我是一只傻傻鸟。"); // 将使用部分具体化的类。
  50. AA<int, double> aa3(8, 999999); // 将使用模板类。
  51. }

1.14.4. 类模板与继承

模板类继承普通类

  1. #include<string>
  2. #include<iostream>
  3. using namespace std;
  4. // 普通类
  5. class AA
  6. {
  7. public:
  8. int m_a;
  9. AA(int a) :m_a(a) { cout << "调用了AA的构造函数。\n"; }
  10. void func1() { cout << "调用了func1()函数:m_a=" << m_a << endl;; }
  11. };
  12. // 模板类
  13. template<class T1, class T2>
  14. class BB:public AA
  15. {
  16. public:
  17. T1 m_x;
  18. T2 m_y;
  19. BB(const T1 x, const T2 y, int a) :AA(a), m_x(x), m_y(y)
  20. {
  21. cout << "调用了BB的构造函数。\n";
  22. }
  23. // 常函数
  24. void func2() const
  25. {
  26. cout << "调用了func2()函数:x = " << m_x << ", y = " << m_y << endl;
  27. }
  28. };
  29. int main()
  30. {
  31. BB<int, string> bb(8, "我是一只傻傻鸟", 3);
  32. bb.func2();
  33. }

普通类继承模板类的实例化版本

  1. #include<string>
  2. #include<iostream>
  3. using namespace std;
  4. // 模板类
  5. template<class T1, class T2>
  6. class AA
  7. {
  8. public:
  9. T1 m_x;
  10. T2 m_y;
  11. // 构造函数
  12. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "调用了AA的构造函数。\n"; }
  13. // 常函数
  14. void func1() const
  15. {
  16. cout << "调用了func1()函数:x = " << m_x << ", y = " << m_y << endl;
  17. }
  18. };
  19. // 普通类
  20. class BB:public AA<int,string>
  21. {
  22. public:
  23. int m_a;
  24. BB(int a, int x, string y) : AA(x, y), m_a(a) { cout << "调用了BB的构造函数。\n"; }
  25. void func2() { cout << "调用了func2()函数:m_a = " << m_a << endl;; }
  26. };
  27. int main()
  28. {
  29. BB bb(3, 8, "我是一只傻傻鸟。");
  30. bb.func1();
  31. bb.func2();
  32. }
  33. // 28行代码 AA(x, y)

普通类继承模板类

  1. #include<string>
  2. #include<iostream>
  3. using namespace std;
  4. // 模板类
  5. template<class T1, class T2>
  6. class AA
  7. {
  8. public:
  9. T1 m_x;
  10. T2 m_y;
  11. // 构造函数
  12. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "调用了AA的构造函数。\n"; }
  13. // 常函数
  14. void func1() const
  15. {
  16. cout << "调用了func1()函数:x = " << m_x << ", y = " << m_y << endl;
  17. }
  18. };
  19. // 普通类
  20. template<class T1, class T2>
  21. class BB :public AA<T1, T2>
  22. {
  23. public:
  24. int m_a;
  25. BB(int a, const T1 x, const T2 y) : AA<T1, T2>(x, y), m_a(a) { cout << "调用了BB的构造函数。\n"; }
  26. void func2() { cout << "调用了func2()函数:m_a = " << m_a << endl;; }
  27. };
  28. int main()
  29. {
  30. BB<int,string> bb(3, 8, "我是一只傻傻鸟。");
  31. bb.func1();
  32. bb.func2();
  33. }
  34. // 关键代码在29行, AA<T1, T2>(x, y)需要指定泛型

模板类继承模板类

  1. #include<string>
  2. #include<iostream>
  3. using namespace std;
  4. template<class T1, class T2>
  5. class BB
  6. {
  7. public:
  8. T1 m_x;
  9. T2 m_y;
  10. BB(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "调用了BB的构造函数。\n"; }
  11. void func2() const { cout << "调用了func2()函数:x = " << m_x << ", y = " << m_y << endl; }
  12. };
  13. template<class T, class T1, class T2>
  14. class CC :public BB<T1, T2> // 模板类继承模板类。
  15. {
  16. public:
  17. T m_a;
  18. CC(const T a, const T1 x, const T2 y) : BB<T1, T2>(x, y), m_a(a) { cout << "调用了CC的构造函数。\n"; }
  19. void func3() { cout << "调用了func3()函数:m_a = " << m_a << endl;; }
  20. };
  21. int main()
  22. {
  23. CC<int, int, string> cc(3, 8, "我是一只傻傻鸟。");
  24. cc.func3();
  25. cc.func2();
  26. }

模板类继承模板参数给出的基类

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. class AA {
  4. public:
  5. AA() { cout << "调用了AA的构造函数AA()。\n"; }
  6. AA(int a) { cout << "调用了AA的构造函数AA(int a)。\n"; }
  7. };
  8. class BB {
  9. public:
  10. BB() { cout << "调用了BB的构造函数BB()。\n"; }
  11. BB(int a) { cout << "调用了BB的构造函数BB(int a)。\n"; }
  12. };
  13. class CC {
  14. public:
  15. CC() { cout << "调用了CC的构造函数CC()。\n"; }
  16. CC(int a) { cout << "调用了CC的构造函数CC(int a)。\n"; }
  17. };
  18. template<class T>
  19. class DD {
  20. public:
  21. DD() { cout << "调用了DD的构造函数DD()。\n"; }
  22. DD(int a) { cout << "调用了DD的构造函数DD(int a)。\n"; }
  23. };
  24. template<class T>
  25. class EE : public T { // 模板类继承模板参数给出的基类。
  26. public:
  27. EE() :T() { cout << "调用了EE的构造函数EE()。\n"; }
  28. EE(int a) :T(a) { cout << "调用了EE的构造函数EE(int a)。\n"; }
  29. };
  30. int main()
  31. {
  32. EE<AA> ea1; // AA作为基类。
  33. EE<BB> eb1; // BB作为基类。
  34. EE<CC> ec1; // CC作为基类。
  35. EE<DD<int>> ed1; // EE<int>作为基类。
  36. // EE<DD> ed1; // DD作为基类,错误。
  37. }

1.14.5. 类模板与函数

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template<class T1, class T2>
  4. class AA // 模板类AA。
  5. {
  6. public:
  7. T1 m_x;
  8. T2 m_y;
  9. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
  10. void show() const { cout << "show() x = " << m_x << ", y = " << m_y << endl; }
  11. };
  12. // 采用普通函数,参数和返回值是模板类AA的实例化版本。
  13. AA<int, string> func(AA<int, string>& aa)
  14. {
  15. aa.show();
  16. cout << "调用了func(AA<int, string> &aa)函数。\n";
  17. return aa;
  18. }
  19. // 函数模板,参数和返回值是的模板类AA。
  20. template <typename T1, typename T2>
  21. AA<T1, T2> func(AA<T1, T2>& aa)
  22. {
  23. aa.show();
  24. cout << "调用了func(AA<T1, T2> &aa)函数。\n";
  25. return aa;
  26. }
  27. // 函数模板,参数和返回值是任意类型。
  28. template <typename T>
  29. T func(T& aa)
  30. {
  31. aa.show();
  32. cout << "调用了func(AA<T> &aa)函数。\n";
  33. return aa;
  34. }
  35. int main()
  36. {
  37. AA<int, string> aa(3, "我是一只傻傻鸟。");
  38. func(aa);
  39. }

1.14.6. 类模板与友元

非模板友元

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. template<class T1, class T2>
  4. class AA
  5. {
  6. T1 m_x;
  7. T2 m_y;
  8. public:
  9. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
  10. // 非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内实现。
  11. friend void show(const AA<T1, T2>& a)
  12. {
  13. cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
  14. }
  15. /* friend void show(const AA<int, string>& a);
  16. friend void show(const AA<char, string>& a);*/
  17. };
  18. //void show(const AA<int, string>& a)
  19. //{
  20. // cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
  21. //}
  22. //
  23. //void show(const AA<char, string>& a)
  24. //{
  25. // cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
  26. //}
  27. int main()
  28. {
  29. AA<int, string> a(88, "我是一只傻傻鸟。");
  30. show(a);
  31. AA<char, string> b(88, "我是一只傻傻鸟。");
  32. show(b);
  33. }

约束模板友元

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. // 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
  4. template <typename T>
  5. void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
  6. template<class T1, class T2>
  7. class AA // 模板类AA。
  8. {
  9. friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
  10. T1 m_x;
  11. T2 m_y;
  12. public:
  13. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
  14. };
  15. template<class T1, class T2>
  16. class BB // 模板类BB。
  17. {
  18. friend void show<>(BB<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
  19. T1 m_x;
  20. T2 m_y;
  21. public:
  22. BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
  23. };
  24. template <typename T> // 第三步:友元函数模板的定义。
  25. void show(T& a)
  26. {
  27. cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
  28. }
  29. template <> // 第三步:具体化版本。
  30. void show(AA<int, string>& a)
  31. {
  32. cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
  33. }
  34. template <> // 第三步:具体化版本。
  35. void show(BB<int, string>& a)
  36. {
  37. cout << "具体BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
  38. }
  39. int main()
  40. {
  41. AA<int, string> a1(88, "我是一只傻傻鸟。");
  42. show(a1); // 将使用具体化的版本。
  43. AA<char, string> a2(88, "我是一只傻傻鸟。");
  44. show(a2); // 将使用通用的版本。
  45. BB<int, string> b1(88, "我是一只傻傻鸟。");
  46. show(b1); // 将使用具体化的版本。
  47. BB<char, string> b2(88, "我是一只傻傻鸟。");
  48. show(b2); // 将使用通用的版本。
  49. }

非约束模板友元

  1. #include <iostream> // 包含头文件。
  2. using namespace std; // 指定缺省的命名空间。
  3. // 非类模板约束的友元函数,实例化后,每个函数都是每个每个类的友元。
  4. template<class T1, class T2>
  5. class AA
  6. {
  7. template <typename T> friend void show(T& a); // 把函数模板设置为友元。
  8. T1 m_x;
  9. T2 m_y;
  10. public:
  11. AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
  12. };
  13. template <typename T> void show(T& a) // 通用的函数模板。
  14. {
  15. cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
  16. }
  17. template <>void show(AA<int, string>& a) // 函数模板的具体版本。
  18. {
  19. cout << "具体<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
  20. }
  21. int main()
  22. {
  23. AA<int, string> a(88, "我是一只傻傻鸟。");
  24. show(a); // 将使用具体化的版本。
  25. AA<char, string> b(88, "我是一只傻傻鸟。");
  26. show(b); // 将使用通用的版本。
  27. }

1.14.7. 成员类模板

  1. #include<iostream>
  2. using namespace std;
  3. template<class T1, class T2>
  4. class AA
  5. {
  6. public:
  7. T1 m_x;
  8. T2 m_y;
  9. AA(const T1 x, const T2 y):m_x(x), m_y(y)
  10. {
  11. cout << "调用AA的构造函数\n";
  12. }
  13. void show()
  14. {
  15. cout << "m_x=" << m_x << ",m_y=" << m_y << endl;
  16. }
  17. template<class T>
  18. class BB
  19. {
  20. public:
  21. T m_a;
  22. T1 m_b;
  23. BB() {};
  24. void show();
  25. };
  26. BB<string> m_bb;
  27. template<typename T>
  28. void show(T tt);
  29. };
  30. // 顺序不能写反
  31. template<class T1, class T2>
  32. template<class T>
  33. void AA<T1, T2>::BB<T>::show()
  34. {
  35. cout << "m_a=" << m_a << ",m_b=" << m_b << endl;
  36. }
  37. // 顺序不能写反
  38. template<class T1, class T2>
  39. template<typename T>
  40. void AA<T1, T2>::show(T t)
  41. {
  42. cout << "tt=" << t << endl;
  43. cout << "m_x=" << m_x << ",m_y=" << m_y << endl;
  44. m_bb.show();
  45. }
  46. int main()
  47. {
  48. AA<int, string> a(88, "我是一只傻傻鸟。");
  49. a.show();
  50. a.m_bb.m_a = "我有一只小小鸟。";
  51. a.m_bb.show();
  52. a.show("你是一只什么鸟?");
  53. }

1.14.8. 类模板做参数

  1. #include<iostream>
  2. using namespace std;
  3. template<class T, int len>
  4. class LinkedList
  5. {
  6. public:
  7. T* m_head; // 链表头节点
  8. int m_len = len; // 链表长度
  9. void insert() { cout << "向链表中插入了一条记录。\n"; }
  10. void m_delete() { cout << "向链表中删除了一条记录。\n"; }
  11. void update() { cout << "向链表中更新了一条记录。\n"; }
  12. };
  13. template <class T, int len>
  14. class Array // 数组类模板
  15. {
  16. public:
  17. T* m_data; // 数组指针
  18. int m_len = len; // 数组长度
  19. void insert() { cout << "向数组中插入了一条记录。\n"; }
  20. void m_delete() { cout << "向数组中删除了一条记录。\n"; }
  21. void update() { cout << "向数组中更新了一条记录。\n"; }
  22. };
  23. //核心代码 template<class , int> class 表示模板类参数
  24. template<template<class , int> class table_type, class data_type, int len>
  25. class LinearList
  26. {
  27. public:
  28. table_type<data_type, len> m_table;
  29. void insert() { m_table.insert(); } // 线性表插入操作。
  30. void m_delete() { m_table.m_delete(); } // 线性表删除操作。
  31. void update() { m_table.update(); } // 线性表更新操作。
  32. void oper() // 按业务要求操作线性表。
  33. {
  34. cout << "len=" << m_table.m_len << endl;
  35. m_table.insert();
  36. m_table.update();
  37. }
  38. };
  39. int main()
  40. {
  41. // 创建线性表对象,容器类型为链表,链表的数据类型为int,表长为20
  42. LinearList<LinkedList, int, 20> a;
  43. a.insert();
  44. a.m_delete();
  45. a.update();
  46. // 创建线性表对象,容器类型为数组,数组的数据类型为string,表长为20
  47. LinearList<Array, string, 20> b;
  48. b.insert();
  49. b.m_delete();
  50. b.update();
  51. }

2. 强制转换

2.1. static_cast

用于内置数据类型之间的转换

  1. #include <iostream>
  2. using namespace std;
  3. int main(int argc, char* argv[])
  4. {
  5. int ii = 3;
  6. long ll = ii; // 绝对安全,可以隐式转换,不会出现警告。
  7. double dd = 1.23;
  8. long ll1 = dd; // 可以隐式转换,但是,会出现可能丢失数据的警告。
  9. long ll2 = (long)dd; // C风格:显式转换,不会出现警告。
  10. long ll3 = static_cast<long>(dd); // C++风格:显式转换,不会出现警告。
  11. cout << "ll1=" << ll1 << ",ll2=" << ll2 << ",ll3=" << ll3 << endl;
  12. }

用于指针之间的转换

  1. #include <iostream>
  2. using namespace std;
  3. void func(void* ptr) { // 其它类型指针 -> void *指针 -> 其它类型指针
  4. double* pp = static_cast<double*>(ptr);
  5. }
  6. int main(int argc, char* argv[])
  7. {
  8. int ii = 10;
  9. //double* pd1 = &ii; // 错误,不能隐式转换。
  10. double* pd2 = (double*) &ii; // C风格,强制转换。
  11. //double* pd3 = static_cast<double*>(&ii); // 错误,static_cast不支持不同类型指针的转换。
  12. void* pv = &ii; // 任何类型的指针都可以隐式转换成void*
  13. double* pd4 = static_cast<double*>(pv); // static_cast可以把void *转换成其它类型的指针。
  14. func(&ii);
  15. }

2.2. const_cast

2.3. reinterpret_cast

类似c风格的强制转换

  1. int* p = nullptr;
  2. double* b = reinterpret_cast<double*>(p); //正确,但是有风险

2.4. dynamic_cast

主要用在继承结构中,可以支持RTTI类型识别的上下转换

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号