做网站的毕业答辩ppt实例,网站收录入口是什么,网站小程序,wordpress 技术主题1. 类和对象
1.1. 封装
封装的意义
将属性和行为作为一个整体#xff0c;表现生活中的事物#xff1b;将属性和行为加以权限控制 public - 公共权限#xff1a;类内可以访问#xff0c;类外也可以访问protected - 保护权限#xff1a;类内可以访问#xff0c;…1. 类和对象
1.1. 封装
封装的意义
将属性和行为作为一个整体表现生活中的事物将属性和行为加以权限控制 public - 公共权限类内可以访问类外也可以访问protected - 保护权限类内可以访问类外不可以访问子类可以访问private - 私有权限类内可以访问类外不可以访问子类不可以访问
struct和class的区别
struct默认权限为公共权限class默认权限为私有权限
1.2. 对象的初始化和清理
1.2.1. 构造函数
编译器自动调用完成对象的初始化工作在创建对象时为对象的成员属性赋值
1.2.1.1. 构造函数的分类
无参构造函数
#includeiostream
#includestring
using namespace std;class Student
{
private:string mName;int mAge;
public:// 无参构造函数Student() {cout 调用无参构造函数 endl;};
};
有参构造函数
#includeiostream
#includestring
using namespace std;class Student
{
private:string mName;int mAge;
public:// 有参构造函数Student(string name, int age) {cout 调用有参构造函数 endl;mName name;mAge age;}string getName(){return mName;}int getAge(){return mAge;}// 析构函数~Student() {};
};
拷贝构造函数
#includeiostream
#includestring
using namespace std;class Student
{
private:string mName;int mAge;
public:// 拷贝构造函数Student(const Student s) {mName s.mName;mAge s.mAge;};string getName(){return mName;}int getAge(){return mAge;}// 析构函数~Student() {};
};int main()
{Student s1 Student(lisi, 23);// 调用拷贝构造函数Student s2 s1;// 不能使用匿名对象构造拷贝构造方法,编译器会把代码转换为Student s2; 认为重复定义了一个s2的变量。//Student(s2); // 错误的代码
}
1.2.1.2. 构造函数的初始化列表
#includeiostream
#includestring
using namespace std;class CDate
{
public:CDate(int year, int month, int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};class CGoods
{
public:CGoods(const char* n, int a, double p, int year, int month, int day):_date(year, month, day), // 这行代码相当于 CDate _date(year, month, day)即指定有参构造函数构造CDate对象_amount(a), _price(p){strcpy(_name, n); // _name成员变量必须要放到构造函数体中初始化// _date CDate(year, month, day) // 如果写在构造函数体中相当于 CDate _date; _date CDate(year, month, day),// 即先构造一个CDate对象默认使用无参构造函数,然后再给这个对象赋值。// 但是CDate并没有默认构造函数因此在编译的时候就会报错。 }
private:char _name[20];int _amount;double _price;CDate _date; // 成员对象
};#if 0
int main()
{CGoods good(沙发, 1, 12000, 2022, 1, 1);
}
#endif // 0
1.2.1.3. 构造函数的成员初始化顺序
成员变量的初始化顺序只与其在类中被声明的顺序有关而与在初始化列表的顺序无关。
//
// main.cpp
// 数据成员的初始化顺序问题
//
// Created by apple on 2023/10/7.
//#include iostream
#include stringusing namespace std;class Point
{
public:Point(int ix):_iy(ix),_ix(_iy)// 会先初始化_ix,再初始化_iy{cout Point(int,int) endl;}void print(){cout _ix \t _iy endl;}private:int _ix;int _iy;
};int main(int argc, const char * argv[]) {Point p(3);p.print();return 0;
}
打印结果如下
Point(int,int)
1 3
1.2.1.4. 拷贝构造函数的调用时机(重点)
使用一个已经创建完成的对象来初始化一个新对象
Student s1 Student(lisi, 23);
// 调用拷贝构造函数
Student s2 s1;
值传递的方式给函数参数传值
#include iostream
#include string
using namespace std;class Student
{
public:int age;int* p_height;// 构造函数// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(int a, int height) {cout 调用有参构造函数 endl;age a;p_height new int(height);}// 自定义拷贝构造函数Student(const Student s) {cout 调用自定义的拷贝构造函数 endl;age s.age;p_height new int(*s.p_height);};//析构函数~Student() {// 在析构函数中可以做一些资源释放的工作if (p_height ! nullptr) {delete p_height;p_height nullptr;}cout 调用析构函数 endl;};
};void func(Student s) // 值传递
{cout s.age \t *s.p_height endl;
}int main()
{// 显式构造对象Student s Student(23, 170);// 括号法构造对象Student s1 (23, 170);// 以值的方式将对象传递给函数参数会调用拷贝构造函数func(s);
}
输出结果如下
调用有参构造函数调用有参构造函数调用自定义的拷贝构造函数23 170调用析构函数调用析构函数调用析构函数
以值方式返回局部对象
#includeiostream
#includestring
using namespace std;class Student
{
public:int age;int* p_height;// 构造函数// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(int a, int height) {cout 调用有参构造函数 endl;age a;p_height new int(height);}// 自定义拷贝构造函数Student(const Student s) {cout 调用自定义的拷贝构造函数 endl;age s.age;p_height new int(*s.p_height);};//析构函数~Student() {// 在析构函数中可以做一些资源释放的工作if (p_height ! nullptr) {delete p_height;p_height nullptr;}cout 调用析构函数 endl;};
};Student func1()
{Student s Student(21, 180);// 以值的方式返回局部对象会调用拷贝函数return s;
}int main()
{Student s func1();
}
1.2.1.5. 拷贝构造函数的形式是固定的
拷贝构造函数形式类名(const 类名 rhs)
是否可以去掉引用符号即将其改为 类名(const 类名 rhs)答案是不可以因为会产生构造函数无穷递归调用的情况。 当执行 Point pt2 pt 时会调用拷贝构造函数然后拷贝构造函数的形参会初始化初始化形参又会调用拷贝构造函数这样无穷递归下去直到栈溢出。
是否可以去掉const关键字即将其改为 类名( 类名 rhs)答案是不可以因为非const引用不能绑定右值。
假设 func()函数的返回值是一个 Point 对象当执行 Point pt2 func() 时会调用拷贝构造函数然而当给拷贝构造函数传递参数时 如果没有 constPoint 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}; // 隐式转换
括号法
#includeiostream
#includestring
using namespace std;class Student
{
public:// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(string n, int a) {cout 调用有参构造函数 endl;name n;age a;}// 拷贝构造函数Student(const Student s) {names.name;age s.age;};string getName(){return name;}int getAge(){return age;}// 析构函数~Student() {};private:string name;int age;
};
int main()
{// 实例化对象括号法// 调用无参构造方法时不能加()因为编译器认为这是一个函数的声明Student s1;// 调用有参构造方法Student s2(zhansan, 22);cout s2.getName() \ts2.getAge() endl;// 匿名对象当前行执行结束之后系统会立即收掉匿名对象Student(wangwu, 23); }
显示法
#includeiostream
#includestring
using namespace std;class Student
{
public:// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(string n, int a) {cout 调用有参构造函数 endl;name n;age a;}// 拷贝构造函数Student(const Student s) {names.name;age s.age;};string getName(){return name;}int getAge(){return age;}// 析构函数~Student() {};private:string name;int age;
};
int main()
{// 实例化对象显示法Student s3 Student(lisi, 23);cout s3.getName() \t s3.getAge() endl;// 匿名对象当前行执行结束之后系统会立即收掉匿名对象Student(wangwu, 23);
}
隐式转换法
#includeiostream
#includestring
using namespace std;class Student
{
public:// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(string n, int a) {cout 调用有参构造函数 endl;name n;age a;}// 拷贝构造函数Student(const Student s) {names.name;age s.age;};string getName(){return name;}int getAge(){return age;}// 析构函数~Student() {};private:string name;int age;
};
int main()
{// 实例化对象隐式转换法如果有参构造函数只有一个参数就不需要加{}但是可能会导致问题不推荐。Student s5 { zhaoliu, 26 };
}
1.2.1.8. 带explicit 关键字的构造函数
不带 explicit 关键字
class MyClass {
public:MyClass(int x) {value x;}
private:int value;
};void func(MyClass obj) {// do something
}int main() {MyClass a 10; // 隐式调用MyClass(int)构造函数func(10); // 隐式将int转换为MyClass对象return 0;
}
带 explicit 关键字
class MyClass {
public:explicit MyClass(int x) {value x;}
private:int value;
};void func(MyClass obj) {// do something
}int main() {MyClass a 10; // 错误不能隐式转换int到MyClassMyClass b(10); // 正确显式调用构造函数func(10); // 错误不能隐式转换int到MyClassfunc(MyClass(10)); // 正确显式创建MyClass对象return 0;
}
1.2.2. 析构函数
编译器自动调用完成对象的清理工作在对象销毁前执行一些清理工作
1.2.2.1. 析构函数的调用时机(重点)
栈对象生命周期结束时会自动调用析构函数全局对象在main函数退出时会自动调用析构函数(局部)静态对象在main函数退出时会自动调用析构函数堆对象在执行delete时会自动调用析构函数。
1.2.3. 浅拷贝和深拷贝重点
#includeiostream
#includestring
using namespace std;class Student
{
public:int age;int* p_height;// 无参构造函数Student() {cout 调用无参构造函数 endl;};// 有参构造函数Student(int a, int height) {cout 调用有参构造函数 endl;age a;// 在堆内存中创建一块空间p_height new int(height);}// 自定义拷贝构造函数Student(const Student s) {cout 调用自定义的拷贝构造函数 endl;age s.age;// p_height s.p_height 编译器默认实现的就是这行代码p_height new int(*s.p_height); // 自定义实现深拷贝};//析构函数~Student() {// 在析构函数中可以做一些资源释放的工作if (p_height ! nullptr) {// 释放内存delete p_height;p_height nullptr;}cout 调用析构函数 endl;};
};void test()
{Student s1(18, 170);cout s1对象的年龄 s1.age s1对象的身高 *s1.p_height endl;// Student s2 s1;Student s2(s1);// 如果没有自定义拷贝构造函数默认是浅拷贝当s2对象执行析构函数之后p_height所指向的内存就被释放了// 然后当s1对象执行析构函数时再释放p_height的内存就是非法操作了因此会报错解决的办法就是深拷贝。// 通过自定义拷贝构造函数来实现深拷贝cout s2对象的年龄 s2.age s2对象的身高 *s2.p_height endl;
}
int main()
{test();
} 1.2.4. 赋值函数
class Computer
{
public:Computer(const char *brand, double price): _brand(new char[strlen(brand) 1]()), _price(price){cout Computer(const char *, double) endl;}~Computer(){cout ~Computer() endl;delete [] _brand;_brand nullptr;}Computer Computer::operator(const Computer rhs){if(this ! rhs) //1、自复制{delete [] _brand; //2、释放左操作数_brand nullptr;_brand new char[strlen(rhs._brand) 1](); //3、深拷贝strcpy(_brand, rhs._brand);_price rhs._price;}return *this; //4、返回*this}
private:char *_brand;double _price;
};
返回类型不必须是类类型可以是其他类型但一般没有这种需求。返回值不一定是引用可以返回值类型但这样会引入不必要的拷贝。
class MyClass {
public:MyClass operator(const MyClass other) {if (this ! other) {// 执行赋值操作}return *this; // 这里会涉及一次拷贝}
};
1.2.5. 初始化列表
#includeiostream
#includestring
using namespace std;class Student
{
public:string s_name;int s_age;// 初始化列表, s_name(name)相当于string s_namename, s_age(a)相当于int s_age a;Student(string name, int a) :s_name(name), s_age(a){cout 调用有参构造函数 endl;}
};int main()
{// 括号法创建对象Student s1 (zhangsan, 18);// 显示法创建对象Student s2 Student(lisi, 19);cout s_name: s1.s_name endl;cout s_age: s1.s_age endl;
}
1.2.6. const成员变量(重点)
const成员变量必须要放在初始化列表中进行初始化。
class Book{
public:Book( int s );
private:int i;const int j;int k;Book::Book( int s ): i(s), j(s), k(s){}
1.2.7. 引用成员变量(重点)
引用成员变量也必须要放在初始化列表中进行初始化。
1.2.8. 类对象作为类成员
结论当其他类对象作为本类成员构造函数先构造其他类对象再构造本对象析构的顺序与构造相反。
1.2.9. 静态成员变量
所有对象共享该成员变量在编译阶段分配内存类内声明类外初始化
#includeiostream
#includestring
using namespace std;class Person
{
public:// 静态成员变量类内声明static int pAge;
};
// 类外初始化注意初始化的语法
int Person::pAge 18;void test()
{// 静态成员变量可以直接通过类名访问cout Person::pAge endl;
}int main()
{test();
}
1.2.10. 静态成员函数
静态成员函数内部只能访问静态成员属性和静态成员函数静态成员函数的参数列表中没有隐含的this指针。
#includeiostream
#includestring
using namespace std;class Person
{
public:// 静态成员函数类内声明static void func();
};
// 类外初始化注意初始化的语法
void Person::func() {cout Person类的静态成员方法 endl;
}int main()
{Person::func();
}
1.2.11. 虚函数(*)
virtual
#include iostream
#include typeinfousing namespace std;class Base
{
public:Base(int data 10):ma(data){} // 构造函数virtual void show() { cout Base::show() endl; }void show(int) { cout Base::show(int) endl; }
private:int ma;
};
class Derive :public Base
{
public:Derive(int data20):Base(data), mb(data){}void show() { cout Derive::show() endl; }
private:int mb;
};#if 1
int main()
{Derive d(50);Base *pb d;pb-show(); // 动态绑定 Derive::show()pb-show(10); // 静态绑定 Base::show(int)cout sizeof(Base) endl;cout sizeof(Derive) endl;cout typeid(pb).name() endl; // 指针的类型 class Base*// 如果Base中没有虚函数*pb识别的就是编译时期的类型即Base// 如果Base中有虚函数*pb识别的就是运行时期的类型 即RTTI指针指向的类型cout typeid(*pb).name() endl; // 指针指向的类型 class Derive
}
#endif
特性1如果一个类里面定义了虚函数那么在编译阶段编译器就会给这个类产生唯一的一个vftable(虚函数表)。虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时每一张虚函数表都会加载到内存的只读数据区。特性2如果一个类里面定义了虚函数那么这个类的内存会多存储一个指向虚函数地址的指针vfptr。 特性3如果派生类中的方法和继承过来的某个方法在返回值、函数名、参数列表上都相同而且基类中的该方法是虚函数那么派生类的这个方法会被自动处理成虚函数。 1.2.11.1. 静态绑定
类中的普通函数在编译时就确定了地址了即为静态绑定。c的重载函数就是静态绑定因为在编译的时候就需要确定调用的是哪个函数。
1.2.11.2. 动态绑定
类中的虚函数在编译时不能确定函数地址即为动态绑定。另外必须使用指针引用的方式调用虚函数才会产生动态绑定。
Base rb1 b;
rb1.show();
Base rb2 d;
rb2.show();Derive *pd1 d;
pd1-show();
由对象直接调用虚函数不会产生动态绑定,因为可以确定是哪个对象调用的。
Base b;
b.show(); // 静态绑定
Derive d;
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指针作为函数的第一个参数。
#includeiostream
#includestringusing namespace std;const int NAME_LEN 20;class CGoods
{
public:// 编译的时候会在第一个参数的位置添加this指针,void show(CGood* this);void show(){cout show方法 endl;}// 编译的时候会在第一个参数的位置添加this指针,void setPrice(CGood* this, double price)void setPrice(double price){_price price;}
private:char _name[NAME_LEN];double _price;int _amount;
};#if 1
int main()
{CGoods good;good.show();
}
#endif
#includeiostream
#includestringusing namespace std;class Person
{
public:int age;Person(int age){this-age age;}// 引用作为函数的返回值 Person p personPerson addAge(int age){// this指针this-age age;// this指针的解引用就是当前对象return *this;}
};void test()
{Person p(10);p.addAge(10).addAge(10).addAge(10);cout p.age endl;
}
int main()
{test();
}
1.4. C this和*this的区别
this返回的是当前对象的地址(指向当前对象的指针);*this返回的是当前对象的克隆和本身(若返回类型是A则是克隆若返回类型是A则是本身);
std::unique_ptrPaddleClasModel PaddleClasModel::Clone() const {std::unique_ptrPaddleClasModel clone_model utils::make_uniquePaddleClasModel(PaddleClasModel(*this));clone_model-SetRuntime(clone_model-CloneRuntime());return clone_model;
PaddleClasModel(*this)调用的是默认的拷贝构造函数
class PaddleClasModel {
public:PaddleClasModel(const PaddleClasModel other);
};
1.4.1. 常对象和常函数
常函数
成员函数后加const即为常函数常函数内不可以修改成员属性成员属性声明时加关键字mutable后在常函数中依然可以修改
常对象
声明对象前加const即为常对象常对象只能调用常函数。常方法编译器添加的是 const 修饰的 this 指针非常对象也可以调用常函数
#includeiostream
#includestringusing namespace std;class Person
{
public:int age;// 成员属性加了mutable关键字即使在常函数中也是可以更改name属性的值的mutable string name;Person(int age, string name){this-age age;this-name name;}// 常函数使用const修饰本质上就是const Person * const p;因此既不可以更改对象也不可以更改对象的属性值void func() const{cout 调用常函数 endl;//this-age 20; 错误,常函数不可以修改成员属性// name属性是可以修改的this-name wangwu;}void func2(){cout 调用普通函数 endl;}};void test()
{Person p(10, lisi);p.func();cout p.name endl;// 常对象const Person p2(20, zhaoliu);//p2.func2(); 那么p2.func();}
int main()
{test();
}
1.4.2. 空指针访问成员函数
#includeiostream
#includestring
using namespace std;class Person
{
public:int age;void showClass(){cout this is Person class endl;}void showPersonAge(){// age 默认指的是this-age既然this都是空了那么访问this-age肯定就会报错了cout age age endl;}
};void test()
{Person *p nullptr;//p-showClass(); // 正确//p-showPersonAge(); // 报错
}int main()
{test();
}
1.5. 类和对象代码应用实践
#define _CRT_SECURE_NO_WARNINGS
#include iostreamusing namespace std;class String
{
public:String(const char *strnullptr){if (str ! nullptr){m_data new char[strlen(str) 1];strcpy(this-m_data, str);}else{m_data new char[1];*m_data \0;}}// 拷贝构造函数String(const String str){m_data new char[strlen(str.m_data) 1];strcpy(m_data, str.m_data);}// 析构函数~String(){delete[] m_data;m_data nullptr;}// 赋值构造函数String operator(const String other){if (this other){return *this;}delete[] m_data; // 先释放之前指向堆内存的空间m_data new char[strlen(other.m_data) 1]; // 再重新申请一块堆空间c语言的字符串最后有一个\0字符因此需要多一个字符的长度strcpy(this-m_data, other.m_data); // c语言的strcpy函数再将other的m_data值拷贝到堆空间return *this;}char* m_data;
};#if 1
int main()
{String s1; // 调用无参构造函数String s2(hello); // 调用有参构造函数 String s3(s2); // 调用拷贝构造函数s3 s1; // 调用赋值构造函数
}
#endif
1.6. 指向类成员的指针
#include iostreamusing namespace std;class Test
{
public:int mb;static int si;void func() { cout call Test::func endl; }static void static_func() { cout call Test::static_func endl; }
};int Test::si 20;int main()
{Test t1;int Test::*p Test::mb; // 如果写成int *p Test::mb; 会报错无法从 int Test::* 转换成 int *// 指针指向普通成员变量脱离对象访问成员是没有意义的因此在访问p时必须加上对象不能直接是*p20t1.*p 20;cout t1.mb endl;// 指针指向静态成员变量,这里就可以这样写了int *p1 Test::si;*p1 30;cout Test::si endl;// 指针指向普通成员方法void(Test::*func)() Test::func; // 如果写成void(*func)() Test::func; 会报错无法从 void (__thiscall Test::*)(void)转换成void (__cdecl *)(void)(t1.*func)();// 指针指向静态成员方法void(*static_func)() Test::static_func;(*static_func)();}
1.7. 对象数组 1.8. 友元
友元提供了另一访问类的私有成员的方案
1.8.1. 全局函数做友元
#includeiostream
#includestringusing namespace std;class Building
{// 全局函数做友元告诉编译器全局函数goodGay是Building类的好朋友可以访问类中的私有内容friend void goodGay(Building building);
public:string m_SittingRoom;Building(){this-m_SittingRoom 客厅;this-m_BedRom 卧室;}
private:string m_BedRom;
};void goodGay(Building building)
{cout 好基友正在访问 building.m_SittingRoom endl;cout 好基友正在访问 building.m_BedRom endl;
}int main()
{Building building;goodGay(building);
}
1.8.2. 类做友元
#includeiostream
#includestringusing namespace std;class Building;class GoodGay
{
public:// 这里只是声明了构造方法GoodGay();// 这里只是声明了成员函数void visit();
private:// 成员变量Building *building;
};
class Building
{// 友元类告诉编译器GoodGay类可以访问Building类中的私有内容friend class GoodGay;
public:// 声明构造方法Building();public:// 公共的成员变量string m_sittingRoom;
private:// 私有的成员变量string m_bedroom;
};
// 在类的外部定义Building构造函数
Building::Building()
{this-m_bedroom 卧室;this-m_sittingRoom 客厅;
}
// 在类的外部定义GoodGay构造函数
GoodGay::GoodGay() {building new Building();
}
// 在类的外部定义visit方法
void GoodGay::visit()
{cout 好基友正在访问 building-m_bedroom endl;cout 好基友正在访问 building-m_sittingRoom endl;
}int main()
{GoodGay goodGay GoodGay();goodGay.visit();
}
1.8.3. 成员函数做友元
#includeiostream
#includestringusing namespace std;class Building;class GoodGay
{
public:// 这里只是声明了构造方法GoodGay();// 这里只是声明了成员函数void visit();
private:// 成员变量Building *building;
};
class Building
{// 告诉编译器GoodGay类中的成员函数visit可以访问Building类中的私有内容friend void GoodGay::visit();
public:// 声明构造方法Building();public:// 公共的成员变量string m_sittingRoom;
private:// 私有的成员变量string m_bedroom;
};
// 在类的外部定义Building构造函数
Building::Building()
{this-m_bedroom 卧室;this-m_sittingRoom 客厅;
}
// 在类的外部定义GoodGay构造函数
GoodGay::GoodGay() {// 无参构造函数()可以省略//building new Building;building new Building();}
// 在类的外部定义visit方法
void GoodGay::visit()
{cout 好基友正在访问 building-m_bedroom endl;cout 好基友正在访问 building-m_sittingRoom endl;
}int main()
{GoodGay goodGay GoodGay();goodGay.visit();
}
1.9. 运算符重载
注意事项
对于内置的数据类型的表达式的运算符是不可能改变的不要滥用运算符重载
1.9.1. 算术运算符重载
1.9.1.1. 加号运算符重载
#includeiostream
#includestringusing namespace std;class Person
{
public:int m_a;int m_b;// 使用成员函数重载运算符//Person operator(Person p)//{// Person temp;// temp.m_a m_a p.m_a;// temp.m_b m_b p.m_b;// return temp;//}
};// 使用全局函数重载运算符
Person operator(Person p1, Person p2)
{Person temp;temp.m_a p1.m_a p2.m_a;temp.m_b p1.m_b p2.m_b;return temp;
}// 运算符重载的函数重载
Person operator(Person p1, int num)
{Person temp;temp.m_a p1.m_a num;temp.m_b p1.m_b num;return temp;
}
int main()
{// 使用括号法调用无参构造函数不能加括号因此是Person p1而不是Person p1()Person p1;p1.m_a 10;p1.m_b 10;Person p2;p2.m_a 10;p2.m_b 10;Person p3 p1 p2;cout p3.m_a p3.m_a \t p3.m_b p3.m_b endl;Person p4 p1 100;cout p4.m_a p4.m_a \t p4.m_b p4.m_b endl;
}
1.9.1.2. 左移运算符重载
作用
输出自定义对象的成员变量;只能使用全局函数重载版本;如果要输出对象的私有成员可以配合友元一起使用。
#includeiostream
#includestringusing namespace std;class Person
{// 全局函数做友元可以访问类中的私有属性friend ostream operator(ostream out, Person p);
private:int m_a;int m_b;
public:void setA(int a){m_a a;}void setB(int b){m_b b;}
};// 使用全局函数重载运算符
ostream operator(ostream out, Person p)
{out p.m_a p.m_a , p.m_b p.m_b;return out;
}int main()
{// 使用括号法调用无参构造函数不能加括号因此是Person p1而不是Person p1()Person p1;p1.setA(10);p1.setB(10);cout p1 endl;
}
1.9.1.3. 递增运算符重载
#includeiostream
#includestringusing namespace std;class MyInteger
{// 全局函数做友元可以访问类中的私有属性friend ostream operator(ostream out, MyInteger myint);
private:int m_a;
public:MyInteger(){m_a 1;}// 重载前置运算符这里必须返回引用即同一个对象MyInteger operator(){m_a;return *this;}// 重载后置运算符MyInteger operator(int) //int代表占位参数{MyInteger temp *this; // 先保存当前对象 *this 就表示当前对象m_a; // 然后再让对象中的m_a的值自增return temp;}
};// 使用全局函数重载运算符
ostream operator(ostream out, MyInteger myint)
{out myint.m_a myint.m_a;return out;
}int main()
{MyInteger myint;cout myint endl;cout myint endl;cout myint endl;cout myint endl;
}
1.9.1.4. 赋值运算符重载
#includeiostream
#includestringusing namespace std;class Person
{
public:int *m_age;
public:Person(int age){// new int 返回的是int类型的指针m_age new int(age);}~Person(){if (m_age ! nullptr){delete m_age;m_age nullptr;}}// 重载赋值运算符Person operator(Person p){if (m_age ! nullptr) {delete m_age; // 释放m_age的内存m_age new int(*p.m_age); // 重新拷贝一份放在堆内存在拷贝之前需要将this指针指向的对象的m_age属性的空间给释放防止野指针return *this; // 为了能够链式调用需要返回对象的引用}}};int main()
{Person p1(10);Person p2(20);Person p3(30);// 默认是浅拷贝,在析构函数中清空内存就会存在问题因此需要手动改为深拷贝p3 p2 p1;cout p1的年龄为 *p1.m_age endl;cout p2的年龄为 *p2.m_age endl;cout p3的年龄为 *p3.m_age endl;}
1.9.1.5. newdelete运算符重载
void* operator new(size_t size)
void operator delete(void* ptr)
#includeiostream
#includestringusing namespace std;void* operator new(size_t size) // 参数必须是size_tunsigned long long返回值必须是void*。是静态成员函数
{cout 调用了全局重载的new size 字节。\n;// 申请内存void* ptr malloc(size);cout 申请到的内存的地址是 ptr endl;return ptr;
}void operator delete(void* ptr) // 参数必须是void *返回值必须是void。是静态成员函数
{cout 调用了全局重载的delete。\n;// 判断是否是空指针if (ptr 0) return; // 对空指针delete是安全的。free(ptr); // 释放内存。
}class CGirl // 超女类CGirl。
{
public:int m_bh; // 编号。int m_xw; // 胸围。CGirl(int bh, int xw) { m_bh bh, m_xw xw; cout 调用了构造函数CGirl()\n; }~CGirl() { cout 调用了析构函数~CGirl()\n; }void* operator new(size_t size){cout 调用了类的重载的new size 字节。\n;// 申请内存void* ptr malloc(size);cout 申请到的内存的地址是 ptr endl;return ptr;}void operator delete(void* ptr) // 参数必须是void *返回值必须是void。{cout 调用了类的重载的delete。\n;// 判断是否是空指针if (ptr 0) return; // 对空指针delete是安全的。free(ptr); // 释放内存。}
};int main()
{// 会调用全局重载函数newint* p1 new int(3);// 会调用全局重载函数deletedelete p1;CGirl* p2 new CGirl(3, 8);cout p2的地址是 p2 编号 p2-m_bh 胸围 p2-m_xw endl;delete p2;
}
1.10. 类的自动类型转换
#includeiostream
#includestringusing namespace std;class CGirl // 超女类CGirl。
{
public:int m_bh; // 编号。string m_name; // 姓名。double m_weight; // 体重单位kg。// 默认构造函数。CGirl() { m_bh 0; m_name.clear(); m_weight 0; cout 调用了CGirl()\n; }// 自我介绍的方法。void show() { cout bh m_bh ,name m_name ,weight m_weight endl; }// 关闭自动类型转换但是可以显式转换explicit CGirl(int bh) { m_bh bh; m_name.clear(); m_weight 0; cout 调用了CGirl(int bh)\n; }//CGirl(double weight) { m_bh 0; m_name.clear(); m_weight weight; cout 调用了CGirl(double weight)\n; }
};
int main()
{//CGirl g1(8); // 常规的写法。//CGirl g1 CGirl(8); // 显式转换。//CGirl g1 8; // 隐式转换。CGirl g1; // 创建对象。g1 (CGirl)8; // 隐式转换用CGirl(8)创建临时对象再赋值给g。//CGirl g1 8.7; // 隐式转换。//g1.show();
}
1.11. 继承
1.11.1. 继承的基本语法
class 派生类名:继承方式基类名
1.11.2. 继承方式
publicprotectedprivate
默认是private。不管继承方式如何基类中的private成员在派生类中始终不能使用。
1.11.3. 继承中构造和析构顺序
创建派生类对象时先调用基类的构造函数再调用派生类的构造函数销毁派生类对象时先调用派生类的析构函数再调用基类的析构函数。如果手工调用派生类的析构函数也会调用基类的析构函数
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。class A { // 基类
public:int m_a;
private:int m_b;
public:A() : m_a(0), m_b(0) // 基类的默认构造函数。{cout 调用了基类的默认构造函数A()。\n;}A(int a, int b) : m_a(a), m_b(b) // 基类有两个参数的构造函数。{cout 调用了基类的构造函数A(int a,int b)。\n;}A(const A a) : m_a(a.m_a 1), m_b(a.m_b 1) // 基类的拷贝构造函数。{cout 调用了基类的拷贝构造函数A(const A a)。\n;}// 显示基类A全部的成员。void showA() { cout m_a m_a ,m_b m_b endl; }
};class B :public A // 派生类
{
public:int m_c;B() : m_c(0), A() // 派生类的默认构造函数指明用基类的默认构造函数不指明也无所谓。顺序也无所谓{cout 调用了派生类的默认构造函数B()。\n;}B(int a, int b, int c) : A(a, b), m_c(c) // 指明用基类的有两个参数的构造函数。{cout 调用了派生类的构造函数B(int a,int b,int c)。\n;}B(const A a, int c) :A(a), m_c(c) // 指明用基类的拷贝构造函数。{cout 调用了派生类的构造函数B(const A a,int c) 。\n;}// 显示派生类B全部的成员。void showB() { cout m_c m_c endl endl; }
};int main()
{B b1; // 将调用基类默认的构造函数。b1.showA(); b1.showB();B b2(1, 2, 3); // 将调用基类有两个参数的构造函数。b2.showA(); b2.showB();A a(10, 20); // 创建基类对象。B b3(a, 30); // 将调用基类的拷贝造函数。b3.showA(); b3.showB();
}
注意事项
如果没有指定基类构造函数将使用基类的默认构造函数。如果基类没有默认构造函数将报错可以用初始化列表指明要使用的基类构造函数基类构造函数负责初始化被继承的数据成员派生类构造函数主要用于初始化新增的数据成员派生类的构造函数总是调用一个基类构造函数包括拷贝构造函数
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 关键字函数定义处可以加也可以不加为了方便你可以只将基类中的函数声明为虚函数这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数当在基类中定义了虚函数时如果派生类没有定义新的函数来遮蔽此函数那么将使用基类的虚函数。因为通过基类的指针只能访问从基类继承过去的成员不能访问派生类新增的成员构造函数不能是虚函数析构函数可以声明为虚函数而且有时候必须要声明为虚函数。
#include iostream
using namespace std;//军队
class Troops {
public:// 基类设置为虚函数virtual void fight() { cout Strike back! endl; }
};//陆军
class Army : public Troops {
public:void fight() { cout --Army is fighting! endl; }
};
//99A主战坦克
class _99A : public Army {
public:void fight() { cout ----99A(Tank) is fighting! endl; }
};
//武直10武装直升机
class WZ_10 : public Army {
public:void fight() { cout ----WZ-10(Helicopter) is fighting! endl; }
};
//长剑10巡航导弹
class CJ_10 : public Army {
public:void fight() { cout ----CJ-10(Missile) is fighting! endl; }
};//空军
class AirForce : public Troops {
public:void fight() { cout --AirForce is fighting! endl; }
};
//J-20隐形歼击机
class J_20 : public AirForce {
public:void fight() { cout ----J-20(Fighter Plane) is fighting! endl; }
};
//CH5无人机
class CH_5 : public AirForce {
public:void fight() { cout ----CH-5(UAV) is fighting! endl; }
};
//轰6K轰炸机
class H_6K : public AirForce {
public:void fight() { cout ----H-6K(Bomber) is fighting! endl; }
};int main() {// 基类指针Troops* p new Troops;p-fight();//陆军p new Army;p-fight();p new _99A;p-fight();p new WZ_10;p-fight();p new CJ_10;p-fight();//空军p new AirForce;p-fight();p new J_20;p-fight();p new CH_5;p-fight();p new H_6K;p-fight();return 0;
}
1.12.3. 纯虚函数
语法格式virtual 返回值类型 函数名 (函数参数) 0;
纯虚函数没有函数体只有函数声明在虚函数声明的结尾加上0表明此函数为纯虚函数。
包含纯虚函数的类称为抽象类Abstract Class。之所以说它抽象是因为它无法实例化也就是无法创建对象。原因很明显纯虚函数没有函数体不是完整的函数无法调用也无法为其分配内存空间。
抽象类通常是作为基类让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。
#include iostream
using namespace std;//线
class Line {
public:Line(float len);virtual float area() 0;virtual float volume() 0;
protected:float m_len;
};
//类外定义构造函数
Line::Line(float len) : m_len(len) { }//矩形继承线类也是一个抽象类不能实例化对象
class Rec : public Line {
public:Rec(float len, float width);float area();
protected:float m_width;
};
Rec::Rec(float len, float width) : Line(len), m_width(width) { }
float Rec::area() { return m_len * m_width; }//长方体
class Cuboid : public Rec {
public:Cuboid(float len, float width, float height);float area();float volume();
protected:float m_height;
};
Cuboid::Cuboid(float len, float width, float height) : Rec(len, width), m_height(height) { }
float Cuboid::area() { return 2 * (m_len * m_width m_len * m_height m_width * m_height); }
float Cuboid::volume() { return m_len * m_width * m_height; }//正方体
class Cube : public Cuboid {
public:Cube(float len);float area();float volume();
};
Cube::Cube(float len) : Cuboid(len, len, len) { }
float Cube::area() { return 6 * m_len * m_len; }
float Cube::volume() { return m_len * m_len * m_len; }int main() {//Line* p0 new Rec(10, 20); // errorLine* p new Cuboid(10, 20, 30);cout The area of Cuboid is p-area() endl;cout The volume of Cuboid is p-volume() endl;p new Cube(15);cout The area of Cube is p-area() endl;cout The volume of Cube is p-volume() endl;return 0;
}
运行结果 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) 只有类中的虚函数才能被声明为纯虚函数普通成员函数和顶层函数均不能声明为纯虚函数。如下例所示
//顶层函数不能被声明为纯虚函数
void fun() 0; //compile error
class base{
public ://普通成员函数不能被声明为纯虚函数void display() 0; //compile error
};
1.12.4. 虚析构函数
#includeiostream
using namespace std;class ClxBase
{
public:ClxBase() {};virtual ~ClxBase() { cout delete ClxBase endl; };virtual void DoSomething() { cout Do something in class ClxBase! endl; };
};class ClxDerived : public ClxBase
{
public:ClxDerived() {};~ClxDerived() { cout Output from the destructor of class ClxDerived! endl; };void DoSomething() { cout Do something in class ClxDerived! endl; };
};int main(int argc, char const* argv[])
{ClxBase* pTest new ClxDerived;pTest-DoSomething();delete pTest;return 0;
}
打印结果如下
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。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。class Hero // 英雄基类
{
public:int viability; // 生存能力。int attack; // 攻击伤害。virtual void skill1() { cout 英雄释放了一技能。\n; }virtual void skill2() { cout 英雄释放了二技能。\n; }virtual void uskill() { cout 英雄释放了大绝招。\n; }
};class XS :public Hero // 西施派生类
{
public:void skill1() { cout 西施释放了一技能。\n; }void skill2() { cout 西施释放了二技能。\n; }void uskill() { cout 西施释放了大招。\n; }void show() { cout 我是天下第一美女。\n; }
};class HX :public Hero // 韩信派生类
{
public:void skill1() { cout 韩信释放了一技能。\n; }void skill2() { cout 韩信释放了二技能。\n; }void uskill() { cout 韩信释放了大招。\n; }
};class LB :public Hero // 李白派生类
{
public:void skill1() { cout 李白释放了一技能。\n; }void skill2() { cout 李白释放了二技能。\n; }void uskill() { cout 李白释放了大招。\n; }
};int main()
{// 根据用户选择的英雄施展一技能、二技能和大招。int id 0; // 英雄的id。cout 请输入英雄1-西施2-韩信3-李白。;cin id;// 创建基类指针让它指向派生类对象用基类指针调用派生类的成员函数。Hero* ptr nullptr;if (id 1) { // 1-西施ptr new XS;}else if (id 2) { // 2-韩信ptr new HX;}else if (id 3) { // 3-李白ptr new LB;}if (ptr ! nullptr) {ptr-skill1();ptr-skill2();ptr-uskill();// 如果基类指针指向的对象是西施那么就调用西施的show()函数。//if (id 1) {// XS* pxs (XS *)ptr; // C风格强制转换的方法程序员必须保证目标类型正确。// pxs-show();//}XS* xsptr dynamic_castXS*(ptr); // 把基类指针转换为派生类。if (xsptr ! nullptr) xsptr-show(); // 如果转换成功调用派生类西施的非虚函数。delete ptr;}// 以下代码演示把基类引用转换为派生类引用时发生异常的情况。/*HX hx;Hero rh hx;try{XS rxs dynamic_castXS (rh);}catch (bad_cast) {cout 出现了bad_cast异常。\n;}*/
}
注意
1dynamic_cast只适用于包含虚函数的类。
2dynamic_cast可以将派生类指针转换为基类指针这种画蛇添足的做法没有意义。
3dynamic_cast可以用于引用但是没有与空指针对应的引用值如果转换请求不正确会出现bad_cast异常。
1.13. 函数模板
template typename T
void Swap(T a, T b)
{T tmp a;a b;b tmp;
}
1.13.1. 函数模板的注意事项
可以为类的成员函数创建模板但不能是虚函数和析构函数。
#includeiostream
#includestring
using namespace std;class CGirl
{
public:templatetypename TCGirl(T a){cout a a endl;}templatetypename Tvoid show(){cout show方法 endl;}// 错误的//templatetypename T//virtual void show()//{// cout show方法 endl;//}//templatetypename T//~CGirl()//{//}
};int main()
{int a 10;CGirl g CGirl(a);g.showint();
}
使用函数模板时必须明确数据类型确保实参与函数模板能匹配上。
#includeiostream
#includestring
using namespace std;template typename T
void Swap(T a, T b) // 传引用
{T tmp a;a b;b tmp;
}int main()
{// 错误的传引用必须是数据类型一致的不能进行隐式转换//int a 10;//char b 30;//Swap(a, b);
}
#includeiostream
#includestring
using namespace std;template typename T
void Swap(T a, T b) // 传值
{T tmp a;a b;b tmp;
}int main()
{// 正确的int a 10;char b 30;Swapint(a, b); // 可以发生隐式转换
}
#includeiostream
#includestring
using namespace std;// 函数模板中没有用到模板参数
template typename T
void Swap()
{cout 调用了Swap函数 endl;
}int main()
{// 错误的//Swap();// 正确的显式的指定。Swapint();
}
使用函数模板时推导的数据类型必须适应函数模板中的代码。
#includeiostream
#includestring
using namespace std;template typename T
T Add(T a, T b)
{return a b;
}class CGirl
{
};
int main()
{//错误的CGirl对象没有运算//CGirl g1;//CGirl g2;//Add(g1 g2);
}
使用函数模板时如果是自动类型推导不会发生隐式类型转换如果显式指定了函数模板的数据类型可以发生隐式类型转换。
#includeiostream
#includestring
using namespace std;template typename T
void Swap(T a, T b)
{T tmp a;a b;b tmp;
}int main()
{// 正确的int a 10;char b 30;Swapint(a, b); // 显式指定了int数据类型可以发生从char到int的数据类型转换
}
1.13.2. 函数模板具体化
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。class CGirl // 超女类。
{
public:int m_bh; // 编号。string m_name; // 姓名。int m_rank; // 排名。
};template typename T
void Swap(T a, T b); // 交换两个变量的值函数模板。template
void SwapCGirl(CGirl g1, CGirl g2); // 交换两个超女对象的排名。这个函数是函数模板的具体化函数
// template
// void Swap(CGirl g1, CGirl g2); // 交换两个超女对象的排名。int main()
{int a 10, b 20;Swap(a, b); // 使用了函数模板。cout a a ,b b endl;CGirl g1, g2;g1.m_rank 1; g2.m_rank 2;Swap(g1, g2); // 使用了超女类的具体化函数。cout g1.m_rank g1.m_rank ,g2.m_rank g2.m_rank endl;
}
编译器使用各种函数的规则
具体化优先于常规模板普通函数优先于具体化和常规模板。如果希望使用函数模板可以用空模板参数强制使用函数模板。如果函数模板能产生更好的匹配将优先于普通函数。
#include iostream // 包含头文件。
#includestring
using namespace std; // 指定缺省的命名空间。void Swap(int a, int b) // 普通函数。
{cout 使用了普通函数。\n;
}template typename T
void Swap(T a, T b) // 函数模板。
{cout 使用了函数模板。\n;
}template
void Swap(int a, int b) // 函数模板的具体化版本。
{cout 使用了具体化的函数模板。\n;
}int main()
{Swap(1,2); // 会调用普通函数Swap(c, d); // 会调用函数模板因为不用进行隐式转换Swap(1,2); // 用空模板会强制调用函数模板的具体化版本
}
1.13.3. 函数模板分文件编写
记住下面两点就可以了
函数模板只是函数的描述没有实体创建函数模板的代码放在头文件中。函数模板的具体化有实体编译的原理和普通函数一样所以声明放在头文件中定义放在源文件中。
1.13.4. 函数模板高级
1.13.4.1. decltype关键字
语法decltype(expression) var;
作用用于分析表达式的数据类型
#include iostream // 包含头文件。
#includestring
using namespace std; // 指定缺省的命名空间。template typename T
auto Swap(T a, T b) // 函数模板。
{decltype(a b) temp a b;cout temp temp endl;return temp;
}int main()
{auto res Swap(c, d);cout res endl;
}
#include iostream // 包含头文件。
#includestring
using namespace std; // 指定缺省的命名空间。int func()
{cout 调用了func函数 endl;return 3;
}int main()
{decltype(func()) f func(); // 函数返回值的数据类型cout f endl;decltype(func) *f func; // 函数类型f(); // 调用func函数
}
1.13.4.2. typename 的用法
template typename T
struct MakeUniqueResult {using scalar std::unique_ptrT;
};template typename T, typename... Args
typename MakeUniqueResultT::scalar make_unique(Args ... args) { // NOLINTreturn std::unique_ptrT(new T(std::forwardArgs(args)...)); // NOLINT(build/c11)
}
上面代码中编译器无法自动区分 MakeUniqueResultT::scalar 是一个类型还是一个成员变量。为了明确告诉编译器 scalar 是一个类型我们使用 typename 关键字。没有 typename编译器会产生错误因为它不能确定 scalar 的含义。
1.14. 类模板
1.14.1. 语法
template class T
class 类模板名
{类的定义;
};
1.14.2. 注意事项
在创建对象的时候必须指明具体的数据类型。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。template class T1, class T2
class AA
{
public:T1 m_a; // 通用类型用于成员变量。T2 m_b; // 通用类型用于成员变量。AA() { } // 默认构造函数是空的。// 通用类型用于成员函数的参数。AA(T1 a, T2 b) :m_a(a), m_b(b) { }// 通用类型用于成员函数的返回值。T1 geta() // 获取成员m_a的值。{T1 a 2; // 通用类型用于成员函数的代码中。return m_a a;}T2 getb(); // 获取成员m_b的值。
};// 模板类的成员函数可以在类外实现。
templateclass T1, class T2
T2 AAT1, T2::getb()
{return m_b;
}
int main()
{AAint, string* a; // 在创建对象的时候必须指明具体的数据类型。AA a 是错误的。
}
使用类模板时数据类型必须适应类模板中的代码。类模板可以为通用数据类型指定缺省的数据类型。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。template class T1, class T2string // 指定通用数据类型的数据类型
class AA
{
}
模板类的成员函数可以在类外实现。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。template class T1, class T2
class AA
{
public:T1 m_a; // 通用类型用于成员变量。T2 m_b; // 通用类型用于成员变量。AA() { } // 默认构造函数是空的。// 通用类型用于成员函数的参数。AA(T1 a, T2 b) :m_a(a), m_b(b) { }T2 getb(); // 获取成员m_b的值。
};// 模板类的成员函数可以在类外实现。
templateclass T1, class T2
T2 AAT1, T2::getb()
{return m_b;
}
可以用new创建模板类对象。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。template class T1, class T2string
class AA
{
public:T1 m_a; // 通用类型用于成员变量。T2 m_b; // 通用类型用于成员变量。AA() { } // 默认构造函数是空的。// 通用类型用于成员函数的参数。AA(T1 a, T2 b) :m_a(a), m_b(b) { }// 通用类型用于成员函数的返回值。T1 geta() // 获取成员m_a的值。{T1 a 2; // 通用类型用于成员函数的代码中。return m_a a;}
};int main()
{AAint, string *b new AAint, string();
}
在程序中模板类的成员函数使用了才会创建。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。template class T1, class T2string
class AA
{
public:T1 m_a; // 通用类型用于成员变量。T2 m_b; // 通用类型用于成员变量。AA() { } // 默认构造函数是空的。// 该成员函数并不会被调用因此也不会报错T1 gethaha(){return m_a.hahaha();}// 通用类型用于成员函数的参数。AA(T1 a, T2 b) :m_a(a), m_b(b) { }
};int main()
{AAint, string* a; //在程序中模板类的成员函数使用了才会创建。
}
1.14.3. 类模板的具体化重点
可以部分具体化也可以完全具体化具体化程度高的类优先于具体化程度低的类具体化的类优先于没有具体化的类。
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。/
// 类模板
templateclass T1, class T2
class AA { // 类模板。
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y) :m_x(x), m_y(y) { cout 类模板构造函数。\n; }void show() const;
};templateclass T1, class T2
void AAT1, T2::show() const { // 成员函数类外实现。cout 类模板x m_x , y m_y endl;
}
/
// 类模板完全具体化
template
class AAint, string {
public:int m_x;string m_y;AA(const int x, const string y) :m_x(x), m_y(y) { cout 完全具体化构造函数。\n; }void show() const;
};void AAint, string::show() const { // 成员函数类外实现。cout 完全具体化x m_x , y m_y endl;
}
/
// 类模板部分具体化
templateclass T1
class AAT1, string {
public:T1 m_x;string m_y;AA(const T1 x, const string y) :m_x(x), m_y(y) { cout 部分具体化构造函数。\n; }void show() const;
};templateclass T1
void AAT1, string::show() const { // 成员函数类外实现。cout 部分具体化x m_x , y m_y endl;
}
/int main()
{// 具体化程度高的类优先于具体化程度低的类具体化的类优先于没有具体化的类。AAint, string aa1(8, 我是一只傻傻鸟。); // 将使用完全具体化的类。AAchar, string aa2(8, 我是一只傻傻鸟。); // 将使用部分具体化的类。AAint, double aa3(8, 999999); // 将使用模板类。
}
1.14.4. 类模板与继承
模板类继承普通类
#includestring
#includeiostreamusing namespace std;// 普通类
class AA
{
public:int m_a;AA(int a) :m_a(a) { cout 调用了AA的构造函数。\n; }void func1() { cout 调用了func1()函数m_a m_a endl;; }
};// 模板类
templateclass T1, class T2
class BB:public AA
{
public:T1 m_x;T2 m_y;BB(const T1 x, const T2 y, int a) :AA(a), m_x(x), m_y(y){cout 调用了BB的构造函数。\n;}// 常函数void func2() const{cout 调用了func2()函数x m_x , y m_y endl;}
};int main()
{BBint, string bb(8, 我是一只傻傻鸟, 3);bb.func2();}
普通类继承模板类的实例化版本
#includestring
#includeiostreamusing namespace std;// 模板类
templateclass T1, class T2
class AA
{
public:T1 m_x;T2 m_y;// 构造函数AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout 调用了AA的构造函数。\n; }// 常函数void func1() const{cout 调用了func1()函数x m_x , y m_y endl;}
};// 普通类
class BB:public AAint,string
{
public:int m_a;BB(int a, int x, string y) : AA(x, y), m_a(a) { cout 调用了BB的构造函数。\n; }void func2() { cout 调用了func2()函数m_a m_a endl;; }
};int main()
{BB bb(3, 8, 我是一只傻傻鸟。);bb.func1();bb.func2();
}
// 28行代码 AA(x, y)
普通类继承模板类
#includestring
#includeiostreamusing namespace std;// 模板类
templateclass T1, class T2
class AA
{
public:T1 m_x;T2 m_y;// 构造函数AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout 调用了AA的构造函数。\n; }// 常函数void func1() const{cout 调用了func1()函数x m_x , y m_y endl;}
};// 普通类
templateclass T1, class T2
class BB :public AAT1, T2
{
public:int m_a;BB(int a, const T1 x, const T2 y) : AAT1, T2(x, y), m_a(a) { cout 调用了BB的构造函数。\n; }void func2() { cout 调用了func2()函数m_a m_a endl;; }
};int main()
{BBint,string bb(3, 8, 我是一只傻傻鸟。);bb.func1();bb.func2();
}// 关键代码在29行 AAT1, T2(x, y)需要指定泛型
模板类继承模板类
#includestring
#includeiostreamusing namespace std;templateclass T1, class T2
class BB
{
public:T1 m_x;T2 m_y;BB(const T1 x, const T2 y) : m_x(x), m_y(y) { cout 调用了BB的构造函数。\n; }void func2() const { cout 调用了func2()函数x m_x , y m_y endl; }
};templateclass T, class T1, class T2
class CC :public BBT1, T2 // 模板类继承模板类。
{
public:T m_a;CC(const T a, const T1 x, const T2 y) : BBT1, T2(x, y), m_a(a) { cout 调用了CC的构造函数。\n; }void func3() { cout 调用了func3()函数m_a m_a endl;; }
};int main()
{CCint, int, string cc(3, 8, 我是一只傻傻鸟。);cc.func3();cc.func2();
}
模板类继承模板参数给出的基类
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。class AA {
public:AA() { cout 调用了AA的构造函数AA()。\n; }AA(int a) { cout 调用了AA的构造函数AA(int a)。\n; }
};class BB {
public:BB() { cout 调用了BB的构造函数BB()。\n; }BB(int a) { cout 调用了BB的构造函数BB(int a)。\n; }
};class CC {
public:CC() { cout 调用了CC的构造函数CC()。\n; }CC(int a) { cout 调用了CC的构造函数CC(int a)。\n; }
};templateclass T
class DD {
public:DD() { cout 调用了DD的构造函数DD()。\n; }DD(int a) { cout 调用了DD的构造函数DD(int a)。\n; }
};templateclass T
class EE : public T { // 模板类继承模板参数给出的基类。
public:EE() :T() { cout 调用了EE的构造函数EE()。\n; }EE(int a) :T(a) { cout 调用了EE的构造函数EE(int a)。\n; }
};int main()
{EEAA ea1; // AA作为基类。EEBB eb1; // BB作为基类。EECC ec1; // CC作为基类。EEDDint ed1; // EEint作为基类。// EEDD ed1; // DD作为基类错误。
}
1.14.5. 类模板与函数
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。templateclass T1, class T2
class AA // 模板类AA。
{
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }void show() const { cout show() x m_x , y m_y endl; }
};// 采用普通函数参数和返回值是模板类AA的实例化版本。
AAint, string func(AAint, string aa)
{aa.show();cout 调用了func(AAint, string aa)函数。\n;return aa;
}// 函数模板参数和返回值是的模板类AA。
template typename T1, typename T2
AAT1, T2 func(AAT1, T2 aa)
{aa.show();cout 调用了func(AAT1, T2 aa)函数。\n;return aa;
}// 函数模板参数和返回值是任意类型。
template typename T
T func(T aa)
{aa.show();cout 调用了func(AAT aa)函数。\n;return aa;
}int main()
{AAint, string aa(3, 我是一只傻傻鸟。);func(aa);
}
1.14.6. 类模板与友元
非模板友元
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。templateclass T1, class T2
class AA
{T1 m_x;T2 m_y;
public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }// 非模板友元友元函数不是模板函数而是利用模板类参数生成的函数只能在类内实现。friend void show(const AAT1, T2 a){cout x a.m_x , y a.m_y endl;}/* friend void show(const AAint, string a);friend void show(const AAchar, string a);*/
};//void show(const AAint, string a)
//{
// cout x a.m_x , y a.m_y endl;
//}
//
//void show(const AAchar, string a)
//{
// cout x a.m_x , y a.m_y endl;
//}int main()
{AAint, string a(88, 我是一只傻傻鸟。);show(a);AAchar, string b(88, 我是一只傻傻鸟。);show(b);
}
约束模板友元
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。// 约束模板友元模板类实例化时每个实例化的类对应一个友元函数。
template typename T
void show(T a); // 第一步在模板类的定义前面声明友元函数模板。templateclass T1, class T2
class AA // 模板类AA。
{friend void show(AAT1, T2 a); // 第二步在模板类中再次声明友元函数模板。T1 m_x;T2 m_y;public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};templateclass T1, class T2
class BB // 模板类BB。
{friend void show(BBT1, T2 a); // 第二步在模板类中再次声明友元函数模板。T1 m_x;T2 m_y;public:BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};template typename T // 第三步友元函数模板的定义。
void show(T a)
{cout 通用x a.m_x , y a.m_y endl;
}template // 第三步具体化版本。
void show(AAint, string a)
{cout 具体AAint, stringx a.m_x , y a.m_y endl;
}template // 第三步具体化版本。
void show(BBint, string a)
{cout 具体BBint, stringx a.m_x , y a.m_y endl;
}int main()
{AAint, string a1(88, 我是一只傻傻鸟。);show(a1); // 将使用具体化的版本。AAchar, string a2(88, 我是一只傻傻鸟。);show(a2); // 将使用通用的版本。BBint, string b1(88, 我是一只傻傻鸟。);show(b1); // 将使用具体化的版本。BBchar, string b2(88, 我是一只傻傻鸟。);show(b2); // 将使用通用的版本。
}
非约束模板友元
#include iostream // 包含头文件。
using namespace std; // 指定缺省的命名空间。// 非类模板约束的友元函数实例化后每个函数都是每个每个类的友元。
templateclass T1, class T2
class AA
{template typename T friend void show(T a); // 把函数模板设置为友元。T1 m_x;T2 m_y;
public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};template typename T void show(T a) // 通用的函数模板。
{cout 通用x a.m_x , y a.m_y endl;
}template void show(AAint, string a) // 函数模板的具体版本。
{cout 具体int, stringx a.m_x , y a.m_y endl;
}int main()
{AAint, string a(88, 我是一只傻傻鸟。);show(a); // 将使用具体化的版本。AAchar, string b(88, 我是一只傻傻鸟。);show(b); // 将使用通用的版本。
}
1.14.7. 成员类模板
#includeiostreamusing namespace std;templateclass T1, class T2
class AA
{
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y):m_x(x), m_y(y){cout 调用AA的构造函数\n;}void show(){cout m_x m_x m_y m_y endl;}templateclass Tclass BB{public:T m_a;T1 m_b;BB() {};void show();};BBstring m_bb;templatetypename Tvoid show(T tt);
};// 顺序不能写反
templateclass T1, class T2
templateclass T
void AAT1, T2::BBT::show()
{cout m_a m_a m_b m_b endl;
}// 顺序不能写反
templateclass T1, class T2
templatetypename T
void AAT1, T2::show(T t)
{cout tt t endl;cout m_x m_x m_y m_y endl;m_bb.show();
}
int main()
{AAint, string a(88, 我是一只傻傻鸟。);a.show();a.m_bb.m_a 我有一只小小鸟。;a.m_bb.show();a.show(你是一只什么鸟);
}
1.14.8. 类模板做参数
#includeiostreamusing namespace std;templateclass T, int len
class LinkedList
{
public:T* m_head; // 链表头节点int m_len len; // 链表长度void insert() { cout 向链表中插入了一条记录。\n; }void m_delete() { cout 向链表中删除了一条记录。\n; }void update() { cout 向链表中更新了一条记录。\n; }
};template class T, int len
class Array // 数组类模板
{
public:T* m_data; // 数组指针int m_len len; // 数组长度void insert() { cout 向数组中插入了一条记录。\n; }void m_delete() { cout 向数组中删除了一条记录。\n; }void update() { cout 向数组中更新了一条记录。\n; }
};//核心代码 templateclass , int class 表示模板类参数
templatetemplateclass , int class table_type, class data_type, int len
class LinearList
{
public:table_typedata_type, len m_table;void insert() { m_table.insert(); } // 线性表插入操作。void m_delete() { m_table.m_delete(); } // 线性表删除操作。void update() { m_table.update(); } // 线性表更新操作。void oper() // 按业务要求操作线性表。{cout len m_table.m_len endl;m_table.insert();m_table.update();}
};
int main()
{// 创建线性表对象容器类型为链表链表的数据类型为int表长为20。LinearListLinkedList, int, 20 a;a.insert();a.m_delete();a.update();// 创建线性表对象容器类型为数组数组的数据类型为string表长为20。LinearListArray, string, 20 b;b.insert();b.m_delete();b.update();}
2. 强制转换
2.1. static_cast
用于内置数据类型之间的转换
#include iostream
using namespace std;int main(int argc, char* argv[])
{int ii 3;long ll ii; // 绝对安全可以隐式转换不会出现警告。double dd 1.23;long ll1 dd; // 可以隐式转换但是会出现可能丢失数据的警告。long ll2 (long)dd; // C风格显式转换不会出现警告。long ll3 static_castlong(dd); // C风格显式转换不会出现警告。cout ll1 ll1 ,ll2 ll2 ,ll3 ll3 endl;
}
用于指针之间的转换
#include iostream
using namespace std;void func(void* ptr) { // 其它类型指针 - void *指针 - 其它类型指针double* pp static_castdouble*(ptr);
}int main(int argc, char* argv[])
{int ii 10;//double* pd1 ii; // 错误不能隐式转换。double* pd2 (double*) ii; // C风格强制转换。//double* pd3 static_castdouble*(ii); // 错误static_cast不支持不同类型指针的转换。void* pv ii; // 任何类型的指针都可以隐式转换成void*。double* pd4 static_castdouble*(pv); // static_cast可以把void *转换成其它类型的指针。func(ii);
}
2.2. const_cast 2.3. reinterpret_cast
类似c风格的强制转换
int* p nullptr;
double* b reinterpret_castdouble*(p); //正确但是有风险
2.4. dynamic_cast
主要用在继承结构中可以支持RTTI类型识别的上下转换