c++学习笔记 Posted on 2022-11-29 00:00:00 2022-12-06 00:00:00 by Author 摘要 c++学习笔记,入门级别笔记 引用的作用:起别名 - 引用必须初始化 - 引用一旦初始化后,就不可以改变了 - 函数传参时,可以利用引用的技术让形参修饰实参 - 可以简化指针修改实参 - 引用的本质就是指针常量 - 引用必须引一块合法的内存空间 - int& ref =10; 是不行的 const int& ref=10是可以的:编译器内部修改为:int temp =10; const int&ref =temp; 对象的深拷贝和浅拷贝 - 浅拷贝简单的赋值操作 - 在堆区重新分配空间,进行赋值 - 析构的代码,将堆区开辟的数据做操作,释放操作 ```c++ #include<iostream> using namespace std; class person { public: int m_Age; int* m_height; person() { } person(int age,int height) { m_Age = age; m_height = new int(height); cout << "m_age=" << m_Age << endl; } //自己实现拷贝函数,实现深拷贝 person(const person& p) { cout << "" << endl; m_Age = p.m_Age; //m_height = p.m_height 编译器默认实现就是这行,不太行 //以下就深拷贝 m_height = new int(*p.m_height); } ~person() { //析构的代码,将堆区开辟的数据做操作,释放操作 delete m_height; m_height = NULL; cout << "Person的析构函数调用" << endl; } }; int main() { person p(18,165); cout << "p1的年龄:" << p.m_Age << " 身高为: " << *p.m_height << endl; person p2(p); cout << "p1的年龄:" << p2.m_Age << " 身高为: " << *p2.m_height << endl; system("pause"); return; } ``` 构造函数和析构函数、 - 构造函数是一个拼接过程,现有零件才能拼接整体,故先生成零件的对象,才能构造整体 - 析构函数相反,先把整体拆解了才能释放每个零件,所以先释放整体空间,在释放零件的空间 初始化列表: - 构造函数():属性1(值1),属性2(值2),属性3(值3){} ```c++ class person{ public: int m_A; int m_B; int m_C; }; person():m_A(10),m_B(20), m_C(30){} //也可以这样 person(int a,int b,int c):m_A(a),m_B(b), m_C(c){} ``` ```c++ void showValue(const int &value){ val =100; //是错误的,只能读,不能改,即为只读操作 cout<<"val="<<vla<<endl; } int main(){ int a=10; showValue(a); } ``` this指针指向的是被调用的成员函数所属的对象 this是一个指针,*this解引用这个指针即为解析成这个对象了 返回对象本身用*this ```c++ class Person{ public: int age; Person(int age){ this->age = age; } Person& PersonAddage(Person &p){ this->age += p.age; retrun *this; } }; int main(){ Person p1(10); Person p2(20); p2.PersonAddage(p1).PersonAddage(p1).PersonAddage(p1); count<<"p2的年龄:"<<p2.age<<endl; } ``` 2.空指针调用成员函数 ```c++ class Person{ public: int m_age; void showPersonAge(){ if(this==NULL){ return; } count<<"age="<<this->m_age<<endl; } void showClassName(){ count <<"this is person class"<<endl; } } int main(){ Person *p =NULL; p.showPersonAge(); p.showClassName(); //以上两种方式均可正常调用 } ``` 常函数: - 成员函数后加const我们称为常函数 - 常函数内不可以修改成员属性 - 成员属性声明时加关键字mutable后,在常函数依然可以修改 常对象: - 声明对象前加const称该对象为常对象 - 常对象只能调用常函数 ```c++ #include <iostream> using namespace std; class person{ public: int m_A; mutable int m_B; //this指针的本质是 指针常量,指针的指向是不可以修改的 // const Person* const this //在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改 void showPerson() const{ this->m_A = 100;//是错的 this->m_B = 100;//是对的 } } int main(){ const person p; p.m_A =100; //这是错的。常对象不能修改 o.m_B =100; //这是可以的 p.showPerson() system("pause"); return; } ``` 友元 - 友元目的就是让一个函数或者类访问另一个类的私有成员 - 全局函数做友元 - 类做友元 - 成员函数做友元 ```c++ #include<iostream> #include<string> using namespace std; class Building{ //全局函数做友元 //goodGay全局函数是Building好朋友,可以访问Building中私有成员 friend void goodGay(Building *building); public: string m_sittingRoom; Building(){ m_sittingRoom="客厅"; m_bedRoom="卧室"; } private: string m_bedRoom; }; //全局函数 void goodGay(Building *building){ cout<<"好基友全局函数,正在访问:"<<building<< building->m_sittingRoom<<endl; cout<<"好基友全局函数,正在访问:"<<building<< building->m_bedRoom<<endl; } int main(){ system("pause"); return; } ``` ```c++ #include<iostream> #include<string> using namespace std; class Building; class GoodGay{ public: Building* building; void visit(); }; Building:: Building(){ m_sittingRoom="客厅"; m_bedRoom="卧室"; } GoodGay::GoodGay(){ //创建一个建筑物的指针 building = new Building; } void GoodGay::visit(){ cout<<"好基友正在访问"<< building->m_sittingRoom<<endl; cout<<"好基友正在访问"<< building->m_bedRoom<<endl; } class Building{ //告诉编译器,goodGay类是Building类的好朋友,可以访问到Building类中私有内容 friend class GoodGay; public: string m_sittingRoom; private: string m_bedRoom; }; int main(){ GoodGay gg; gg.visit(); system("pause"); return; } ``` ```c++ //成员函数做友元 #include<iostream> #include<string> using namespace std; class Building; class GoodGay{ public: GoodGay(); void visit();//让visit可以访问Builing中的私有成员 void visit2();//让visit2不可以访问Building中私有成员 Building* building; }; GoodGay::GoodGay(){ building = new Building; } Building::Building(){ m_sittingRoom="客厅"; m_bedRom="卧室"; } void GoodGay::visit(){ cout<<"visit函数正在访问:"<<building->m_sittingRoom<<endl; cout<<"visit函数正在访问:"<<building->m_bedRoom<<endl; } void GoodGay::visit2(){ cout<<"visit函数正在访问:"<<building->m_sittingRoom<<endl; cout<<"visit函数正在访问:"<<building->m_bedRoom<<endl;//报错 } class Building{ friend void GoodGay::visit(); public: string m_sittingRoom; private: string m_bedRoom; }; int main(){ system("pause"); return; } ``` 运算符重载 ```c++ #include<iostream> #include<string> using namespace std; Person operator+(Person& p1, Person& p) { Person temp; temp.m_A = p1.m_A + p.m_A; temp.m_B = p1.m_B + p.m_B; return temp; } class Person { public: /*Person operator+(Person& p) { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; }*/ int m_A; int m_B; }; int main() { Person p1; Person p2; p1.m_A = 10; p1.m_B = 10; p2.m_A = 10; p2.m_B = 10; Person p3 = p1 + p2; cout << "p3.m_A" << p3.m_A << endl; system("pause"); return; } ``` 左移运算符重载 ```c++ #include<iostream> #include<string> using namespace std; //iostream的引用是为了全局只能有一个 ostream& operator<<(ostream& cout, Person& p) { cout << "m_A=" << p.m_A << "m_B" << p.m_B; return cout; } class Person { public: int m_A; int m_B; }; int main() { Person p; p.m_A = 10; p.m_B = 10; cout << p; system("pause"); return 0; } ``` 递增与算法符重载 ```c++ #include<iostream> #include<string> using namespace std; ostream& operator<<(ostream& cout, myInteger myint) { cout << myint.m_num; return cout; } //重载递增运算符 class myInteger { friend ostream& operator<<(ostream& cout, myInteger myint); public: myInteger() { m_num = 0; } //重载前置递增 myInteger& operator++() { m_num++; return *this; } myInteger operator++(int) { myInteger temp = *this; m_num++; return temp; } //后置递增 private: int m_num; }; int main() { myInteger m; cout << m; system("pause"); return; } ``` 赋值运算符 ```c++ #include<iostream> #include<string> using namespace std; class Person { public: Person(int age) { this->age = new int(age); } Person& operator=(Person& p) { if (age != NULL) { delete age; age = NULL; } age = new int(*p.age); return *this; } int* age; ~Person() { if (age != NULL) { delete age; age = NULL; } } }; int main() { system("pause"); return 0; } ``` 关系运算符 ```c++ #include<iostream> #include<string> using namespace std; class Person { public: Person(string name, int age) { m_age = age; m_name = name; } bool operator==(Person& p) { if (this->m_age == p.m_age && this->m_name == p.m_name)return true; else return false; } bool operator!=(Person& p) { if (this->m_age == p.m_age && this->m_name == p.m_name)return false; else return true; } string m_name; int m_age; }; int main() { Person p1("tom", 16); Person p2("tom", 16); if (p1 == p2) { cout << "p1和p2是相等的" << endl; } Person p1("tom", 16); Person p2("jack", 16); if (p1 != p2) { cout << "p1和p2是不相等的" << endl; } system("pause"); return 0; } ``` 函数运算符重载 - 函数调用运算符() 也可以重载 - 由于重载后使用的方式非常像函数调用,因此称为仿函数 - 仿函数没有固定写法,非常灵活 ```c++\ #include<iostream> #include<string> using namespace std; class Myprint { public: void operator()(string test) { cout << test << endl; } }; void print(string test) { cout << test << endl; } int main() { Myprint myprint; myprint("hello world");//由于使用起来非常像函数调用,所以为仿函数 print("hello world"); system("pause"); return 0; } ``` #### 继承 -  - 语法: class 子类:继承方式 父类(class Java : public BasePage)继承方式有:public ,protected,private - 子类也叫派生类,父类也叫基类 -  - cl /d1 reportSingleClassLayout类名 模板文件.cpp (报告单个类的布局) 父类和子类的构造和析构顺序谁先谁后 继承中的构造和析构顺序如下: - 先构造父类,在构造子类,析构的顺序,先析构子类,在析构父类---------栈 ```c++ #include<iostream> #include<string> using namespace std; class Base{ public: Base() { cout << "这是base的构造函数" << endl; } ~Base() { cout << "这是base的析构函数" << endl; } }; class son : public Base { public: son() { cout << "这是son的析构函数" << endl; } ~son() { cout << "这是son的析构函数" << endl; } }; int main() { Base b; son s; system("pause"); return 0; } ``` 类和对象同名成员函数和属性处理 - 子类对象可以直接访问到子类同名函数和属性 - 子类对象加作用域可以访问到父类同名成员 - 当子类与父类拥有相同的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。 ```c++ #include<iostream> #include<string> using namespace std; class Base { public: int m_A; Base() { m_A = 100; } }; class Son : public Base { public: Son() { m_A = 200; } int m_A; }; int main() { Son s; cout << s.m_A << endl; //如果想要访问父类的同名成员属性,需要加父类的作用域 cout << s.Base::m_A << endl;///Base::这是作用域 system("pause"); return 0; } ``` 同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名) 菱形继承 ```c++ #include<iostream> #include<string> using namespace std; //利用虚继承,解决菱形继承法 //公共的父类Animal称为虚基类 class Animal { public: int m_Age; }; class Sheep :virtual public Animal {}; class Tuo :virtual public Animal {}; class SheepTuo :public Sheep, public Tuo {}; int main() { SheepTuo st; st.Tuo::m_Age = 18; st.Sheep::m_Age = 28; system("pause"); return 0; } ``` - 菱形继承带来主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。 - 利用虚继承可以解决菱形继承问题。--vbptr(virtual base pointer) - <img src="C:\Users\ke_xianqun\AppData\Roaming\Typora\typora-user-images\image-20221129150622731.png" alt="image-20221129150622731" style="zoom:100%;" /> #### 多态  静态多态与动态多态的区别: - 静态多态的函数地址早绑定-编译阶段确定函数地址 - 动态多态的函数地址晚绑定-运行阶段确定函数地址 动态多态满足条件: - 有继承关系 - 有重写函数(不是重载)重写是函数返回值类型,函数名,参数类别完全相同 动态多态的使用: - 父类的指针或者引用指向子类对象 ```c++ #include<iostream> #include<string> using namespace std; class Animal { public: //虚函数 virtual void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; //执行说话的函数 //运行为:动物在说话 //地址早绑定,在编译阶段确定函数地址 //如果想要执行让猫说话,那么这个函数地址就不能提取绑定,需要在运行阶段进行绑定,地址晚绑定 void doSpeak(Animal& animal) { animal.speak(); } int main() { Cat cat; doSpeak(cat); system("pause"); return 0; } ``` 多态的底层原理: -  - 多态的好处 - 开闭原则,对扩展进行开放,对修改进行关闭 - 多态的好处: - 组织结果清晰 - 可读性强 - 对于前期和后期维护性高 ```c++ #include<iostream> #include<string> using namespace std; //开闭原则,对扩展进行开放,对修改进行关闭 //多态的好处: //组织结果清晰 //可读性强 //对于前期和后期维护性高 class AbstractCalcuator { public: virtual int getResult() { return 0; } int m_num1; int m_num2; }; class AddCalculator :public AbstractCalcuator { public: int getResult() { return m_num1 + m_num2; } }; class SubCalculator :public AbstractCalcuator { public: int getResult() { return m_num1 - m_num2; } }; class MulCalculator :public AbstractCalcuator { public: int getResult() { return m_num1 * m_num2; } }; class Calculator{ public: }; int main() { AbstractCalcuator* abc = new AddCalculator; abc->m_num1 = 10; abc->m_num2 = 10; abc->getResult(); delete abc; system("pause"); return 0; } ``` 纯虚函数:通常父类中的虚函数实现毫无意义,主要都是调用子类重写的内容 纯虚函数写法: - virtual 返回值类型 函数名(参数列表)=0;(virtual void fun() = 0;) - 这个类只有一个纯虚函数,这个类称为抽象类 - 抽象类无法实例化对象的 - 抽象类的子类,必须重写父类的纯虚函数,否则无法实例化,即也为抽象类 ```c++ #include<iostream> #include<string> using namespace std; class Base { public: //这个类只有一个纯虚函数,这个类称为抽象类 //抽象类无法实例化对象的 //抽象类的子类,必须重写父类的纯虚函数,否则无法实例化,即也为抽象类 virtual void fun()=0; }; class son :public Base { public: void fun() { } }; int main() { son s; s.fun(); system("pause"); return 0; } ``` 多态的实例 ```c++ #include<iostream> #include<string> using namespace std; class AbstractDrink { public: virtual void Boil() = 0; virtual void Brew() = 0; virtual void PourInCup() = 0; virtual void PutSomeThing() = 0; void makeDrink() { Boil(); Brew(); PourInCup(); PutSomeThing(); } }; class Coffee :public AbstractDrink { public: virtual void Boil() { cout << "煮水" << endl; } virtual void Brew(){ cout << "冲泡咖啡" << endl; } virtual void PourInCup() { cout << "倒入杯中" << endl; } virtual void PutSomeThing() { cout << "加入糖和牛奶" << endl; } }; class Tea :public AbstractDrink { public: virtual void Boil() { cout << "煮水" << endl; } virtual void Brew() { cout << "冲泡茶叶" << endl; } virtual void PourInCup() { cout << "倒入杯中" << endl; } virtual void PutSomeThing() { cout << "加入枸杞" << endl; } }; void doWork(AbstractDrink* abc) { abc->makeDrink(); delete abc; } void test01() { doWork(new Coffee); doWork(new Tea); } int main() { test01(); system("pause"); return 0; } ``` 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码怎么办: 解决方法: 将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性: - 都可以解决父类指针释放子类对象 - 都需要具有具体的函数实现 虚析构和纯虚析构区别: - 如果是纯虚析构,该类属于抽象类,无法实例化对象 虚析构语法: `virtual ~类名(){}` 虚析构语法: `virtual ~类名()=0;` ```c++ #include<iostream> #include<string> using namespace std; class Animal { public: virtual void speak() = 0; //利用虚析构可以解决,父类指针释放子类对象不干净的问题 /*virtual ~Animal() { }*/ //纯虚析构函数,需要声明也需要实现。 //有了纯虚析构函数,这个类也属于抽象类,无法实例化对象 virtual ~Animal() = 0; }; Animal:: ~Animal() { cout << "纯虚析构函数调用" << endl; } class Cat :public Animal { public: Cat(string name) { m_name = new string(name); } virtual void speak() { cout << *m_name<<"小猫在说话" << endl; } ~Cat() { if (m_name != NULL) { delete m_name; m_name = NULL; } } string *m_name; }; int main() { Animal* animal = new Cat("Tom"); animal->speak(); delete animal; system("pause"); return 0; } ``` 总结: - 虚析构或者纯虚析构就是用来解决通过父类指针释放子类对象 - 如果子类中没有堆区数据,可以不写虚析构或纯虚析构 - 拥有纯虚析构函数的类也为抽象类 #### 文本文件 1. 包含头文件 #include<fstream> 2. 创建流对象 ofstream ofs; 3. 打开文件 ofs.open("文件路径",打开方式); 4. 写数据 ofs<<"写入的数据"; 5. 关闭文件 ofs.close(); | 打开方式 | 解释 | | ----------- | -------------------------- | | ios::in | 为读文件而打开文件 | | ios::out | 为写文件而打开文件 | | ios::ate | 初始位置:文件尾部 | | ios::app | 追加的方式写文件 | | ios::trunc | 如果文件存在先删除,在创建 | | ios::binary | 二进制方式 | 文件打开方式可以配合使用,可以利用|操作符 例如:利用二进制方式写文件:`ios::binary| ios::out` ```c++ #include<iostream> #include<string> #include<fstream> using namespace std; void test01() { ofstream ofs; ofs.open("../封装.cpp", ios::out); ofs << "写入数据,写入数据,写入数据" << endl; ofs.close(); } int main() { test01(); system("pause"); return 0; } ``` #### 读文件 读文件步骤如下: - 包含头文件 `#include<fstream>` - 创建流对象 `ifstream ifs;` - 打开文件并判断文件是否打开成功 `ifs.open("文件路径",打开的方式)` - 读数据 - 关闭文件`ifs.close()` - ```c++ #include<iostream> #include<fstream> #include <string> using namespace std; int main() { ifstream ifs; ifs.open("../tes.txt", ios::in); if (!ifs.is_open()) { cout << "文件打开失败" << endl; } char buf[1024] = { 0 }; /** *第一种方式 while (ifs >> buf) { cout << buf << endl; } **/ /** * 第二种方式 while (ifs.getline(buf, sizeof(buf))) { cout << buf << endl; } ***/ /* 第三种 */ string buf; while(getline(ifs,buf)) { cout << buf << endl; } /* 第四种 */ char c; while ((c = ifs.get()) != EOF) {//EOF end of file cout << c; } ifs.close(); system("pause"); return 0; } ``` #### 二进制文件 打开方式:ios::binary 二进制写文件主要利用流对象调用成员函数write; 函数原型:ostream& write(const char* buffer,int len); 参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数 ```c++ #include<iostream> #include<string> #include<fstream> using namespace std; class Person { public: char m_Name[64]; int m_Age; }; void test01() { //1 包含头文件 //ofstream ofs("person.txt", ios::out | ios::binary) ofstream ofs; //2创建流对象 //打开文件 ofs.open("person.txt", ios::out | ios::binary); Person p = { "张三",18 }; ofs.write((const char*)&p, sizeof(p)); ofs.close(); // } int main() { system("pause"); return 0; } ``` #### 二进制读文件 函数原型:`istream& read(char*buffer, int len)` 参数解释: 字符指针buffer指向内存中一段存储空间,len是读写的字节数 ```c++ #include<iostream> #include<string> #include<fstream> using namespace std; class Person { public: char m_Name[64]; int m_Age; }; int main() { ifstream ifs; ifs.open("person.txt", ios::in | ios::binary); if(!ifs.is_open()){ cout << "文件打开失败" << endl; return; } Person p; ifs.read((char*)&p, sizeof(Person)); cout << "name=" << p.m_Name << "\t age=" << p.m_Age << endl; system("pause"); return 0; } ``` #### 函数模板 - c++另一种编程思想就是泛型编程,主要利用的技术就是模板 - c++提供两种模板机制:函数模板和类模板 ##### 函数模板语法 ```c++ templater<typename T> 函数定义或者声明 ``` ```c++ #include<iostream> #include<string> using namespace std; //声明一个模板,告诉编译器后面的代码中紧跟着T不要报错 //T是一个通用的数据类型 template <typename T> //交换两个整型的函数 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void swapInt(int& a, int& b) { int temp = a; a = b; b = temp; } //交换两个浮点型的函数 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; swapInt(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; //1.自动类型推导 mySwap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; //2.显示指定类型 mySwap<int>(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; } ``` - 函数模板利用关键子template - 使用函数模板两种方式:自动类型推导,显示指定类型 - 模板的目的是为了提高复用性,将类型参数化 ##### 函数模板的注意事项 - 自动类型推导,必须推导出一致的数据类型T,才可以使用 - 模板必须要确定T的数据类型,才可以使用 ```c++ #include<iostream> #include<string> using namespace std; template<class T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } template<class T> void func() { cout << "func调用" << endl; } void test01() { int a = 10; int b = 20; double c = 30.0; mySwap(a, b); } int main() { func<int>(); test01(); system("pause"); return 0; } ``` 函数模板案例: ```c++ #include<iostream> #include<string> using namespace std; template<class T> void mySwap(T &a, T &b) { T temp = a; a = b; b = temp; } template<class T> void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i; for (int j = i + 1; j < len; j++) { if (arr[max] < arr[j]) { max = j; } } if (max != i) { mySwap(arr[i], arr[max]); } } } template<class T> void printArrat(T arr[],int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } void test01() { char charArr[] = "badcef"; int a[]={1,23,345,245,43,2354,345} ; int num = sizeof(charArr) / sizeof(char); int num2 = sizeof(a) / sizeof(int); mySort(charArr,num ); printArrat(charArr, num); mySort(a,num2 ); printArrat(a, num2); } int main() { test01(); system("pause"); return 0; } ``` 普通函数和函数模板区别: - 普通函数调用可以发送隐式类型转化 - 函数模板用自动类型推导,不可以发送隐式类型转化 - 函数模板用显示指定类型, 可以发生隐式类型转化 ```c++ #include<iostream> #include<string> using namespace std; //模板函数 template<class T> T myAdd02(T a, T b){ return a+b; } //普通函数 int myAdd01(int a ,int b){ return a+b; } void test01(){ int a=10; int b = 20; char c= 'c' ; cout<<myAdd01(a,c)<<endl; } void test02(){ int a=10; int b=20; char c= 'c' ; //自动推导,不会发送隐式转化 cout<<myAdd02(a,b)<<endl; } void test03(){ int a=10; int b=20; char c= 'c' ; //显示指定类型,会发送隐式转化 cout<<myAdd02<int>(a,c)<<endl; } int main() { test01(); test02(); test03(); system("pause"); return 0; } ``` 总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用数据类型T #### 普通函数与函数模板的调用规则 调用规则: - 如果函数模板和普通函数都可以实现,优先调用普通函数 - 可以通过空模板参数列表来强制调用函数模板 - 函数模板也可以发生重载 - 如果函数 模板可以产生更好的匹配,优先调用函数模板 ```c++ #include<iostream> #include<string> using namespace std; //模板函数 void myPrint(int a,int b) { cout<<"调用普通函数"<<endl; } template<class T> void myPrint(T a,T b) { cout<<"调用模板函数"<<endl; } template<class T> void myPrint(T a,T b,T c) { cout<<"调用重载模板函数"<<endl; } void test01(){ //优先调用普通函数 myPrint(10,20); //通过空模板参数列表,强制调用函数模板 myPrint<>(10,20) ; //函数模板也可以发生重载 myPrint(10,20,30); char c1='a' ; char c2='b' ; //产生更好的匹配,调用模板函数 myPrint(c1,c2); } int main() { test01(); system("pause"); return 0; } ``` 总结:既然提供了函数模拟最好不要提供普通函数,否则容易出现二义性。 #### 模板的局限性 - 模板的通用不是万能的 - 比如void(T a ,T b){ a=b} 如果传入一个数组,无法实现,或者自定义数据类型也就无法实现。 - c++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体的模板。 ```c++ #include<iostream> #include<string> using namespace std; class Person{ public: Person(string name,int age){ this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; template<class T> bool myCompare(T &a,T &b) { if(a==b)return true; else return false; } template<> bool myCompare (Person &a, Person &b){ if(a.m_Age==b.m_Age && a.m_Name==b.m_Name){ return true; }else{ return false; } } void test02(){ Person p1("tom",10); Person p2("tom",11); //出错 bool ret = myCompare(p1,p2); cout<<ret<<endl; } void test01(){ int a = 10; int b =20; bool ret = myCompare(a,b); cout<<ret<<endl; } int main() { test01(); test02(); system("pause"); return 0; } ``` 总结: - 利用具体化的模板,可以解决自定义类型的通用化 - 学习模板并不是为了写模板,而是在STL模板能够运用系统提供的模板 #### 类模板 类模板语法 ```c++ template<typename T>` 类 ``` ```c++ #include<iostream> #include<string> using namespace std; template<class NameType,class AgeType> class Person{ public: Person(NameType name,AgeType age){ this->m_age = age; this->m_name =name; } void showInfo(){ cout<<"name:"<<this->m_name<<endl; cout<<"age:"<<this->m_age<<endl; } string m_name; int m_age; }; void test01(){ Person<string,int>p1("孙悟空",999); p1.showInfo(); } int main() { test01(); system("pause"); return 0; } ``` ##### 类模板和函数模板的区别 - 类模板没有自动类型推导的使用方式 - 类模板在模板参数列表中可以有默认参数 ```c++ #include<iostream> #include<string> using namespace std; //类模板在模板参数列表中可以有默认参数 AgeType有默认参数 template<class NameType,class AgeType= int> class Person{ public: Person(NameType name,AgeType age){ this->m_age = age; this->m_name =name; } void showInfo(){ cout<<"name:"<<this->m_name<<endl; cout<<"age:"<<this->m_age<<endl; } NameType m_name; AgeType m_age; }; void test01(){ //错误的,无法自动推导 //Person p("孙悟空",1000); //对的 Person<string,int> p("孙悟空",1000); p.showInfo(); } void test02(){ Person<string> p("猪八戒",1000); p.showInfo(); } int main() { test01(); test02(); system("pause"); return 0; } ``` 总结:类模板使用只能用显示指定类型方式 类模板的模板参数列表可以有默认参数 ##### 类模板中成员函数创建时机 - 普通类中的成员函数一开始就可以创建 - 类模板中的成员函数在调用时才创建 ```c++ #include<iostream> #include<string> using namespace std; class Person1{ public: void showPerson1(){ cout<<"Person1 show"<<endl; } }; class Person2{ public: void showPerson2(){ cout<<"Person2 show"<<endl; } }; template<class T> class MyClass{ public: T obj; void func1(){ obj.showPerson1(); } void func2(){ obj.showPerson2(); } }; void test01(){ MyClass<Person1>m; m.func1(); MyClass<Person2>m1; m1.func2(); } int main() { test01(); system("pause"); return 0; } ``` 总结:类模板中的成员函数并不是一开始就创建,而是在调用的时候才创建 #### 类模板与继承 当类模板碰到继承,需要注意: - 当子类继承的父类是一个类模板时候,子类在声明时候,需要指出父类中T的类型 - 如果不指定,编译器将无法给子类分配内存 - 如果想灵活指定父类T的类型,子类也需要变为类模板 ```c++ #include<iostream> #include<string> #include<typeinfo> using namespace std; template<class T> class Base{ T m; }; class Son:public Base<int>{ }; template<class T1,class T2> class Son2:public Base<T2>{ public: Son2(){ cout<<"T1的数据类型为:"<<typeid(T1).name()<<endl; cout<<"T2的数据类型为:"<<typeid(T2).name()<<endl; } T1 obj; }; void test02(){ Son2<int ,string>s2; } int main(){ test02(); system("pause"); return 0; } ``` 总结:如果父类是类模板,子类必须指定父类中T的数据类型 #### 类模板分文件编写 - 问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时候链接不到 - 解决:直接包含.cpp源文件 - 解决2:将声明和实现写道同一文件,并更该后缀名为.hpp ##### 类模板案例---数组类封装 ```c++ ///.hpp 文件 #include<iostream> #include<string> using namespace std; template<class T> class MyArray{ public: MyArray(int capacity){ cout<<"有参构造"<<endl; this->m_Capacity = capacity; this->m_Size=0; this->pAddress = new T[this->m_Capacity]; } MyArray(const MyArray& array){ cout<<"拷贝构造"<<endl; this->m_Capacity = array.m_Capacity; this->m_Size = array.m_Size; this->pAddress = new T[array.m_Capacity]; for(int i=0;i<this->m_Size;i++){ this->pAddress[i] = array.pAddress[i]; } } MyArray& operator=(const MyArray& array){ cout<<"==operator构造"<<endl; if(this->pAddress!=NULL){ delete[] this->pAddress; this->pAddress = NULL; this->m_Capacity = 0; this->m_Size = 0; } this->m_Capacity = array.m_Capacity; this->m_Size = array.m_Size; this->pAddress = new T[array.m_Capacity]; for(int i=0;i<this->m_Size;i++){ this->pAddress[i] = array.pAddress[i]; } return *this; } //尾插法 void pushBack(const T& value){ //判断容量是否等于大小了 if(this->m_Capacity==this->m_Size){ return; } this->pAddress[this->m_Size] = value; this->m_Size++; } void Pop_Back(){ //用户访问不到最后一个元素,尾删除 if(this->m_Size==0){ return; } this->m_Size--; } //通过下标方式访问数据 T& operator[](int index){ return this->pAddress[index]; } //返回数组容量 int getCapacity(){ return this->m_Capacity; } int getSize(){ return this->m_Size; } ~MyArray(){ cout<<"析构构造"<<endl; if(this->pAddress!=NULL){ delete[] this->pAddress; this->pAddress=NULL; } } private: T *pAddress; int m_Capacity; int m_Size; }; ``` ```c++ //测试用例 #include<iostream> #include<string> #include"MyArray.hpp" void test01(){ MyArray<int>arr(5); for(int i=0;i<5;i++){ arr.pushBack(i); } cout<<"arr[1]="<<arr[1]<<endl; cout<<"arr的容量="<<arr.getCapacity()<<endl; cout<<"arr的大小="<<arr.getSize()<<endl; MyArray<int>arr2(arr); arr2.Pop_Back(); cout<<"arr2的容量="<<arr2.getCapacity()<<endl; cout<<"arr2的大小="<<arr2.getSize()<<endl; } class Person{ public: Person(){}; Person(string name,int age){ this->m_Age = age; this->m_Name = name; } string m_Name; string m_Age; }; void printPersonArray(MyArray<Person>& arr){ for(int i=0;i<arr.getSize();i++){ cout<<"姓名: "<<arr[i].m_Name<<" 年龄:"<<arr[i].m_Age<<endl; } } void test02(){ MyArray<Person> arr(10); Person p1("孙悟空",99); Person p2("韩信",50); Person p3("李元芳",25); arr.pushBack(p1); arr.pushBack(p2); arr.pushBack(p3); printPersonArray(arr); cout<<"arr的容量="<<arr.getCapacity()<<endl; cout<<"arr的大小="<<arr.getSize()<<endl; } int main(){ //test01(); test02(); system("pause"); return 0; } ``` ##### STL - STL-------Standard Template Library 标准模板库 - STL从广义上分为:容器,算法,迭代器 - 容器和算法之间通过迭代器进行无缝连接 - STL几乎所有的代码都采用模板类或者模板函数 ##### STL 六大组件 - 容器,算法,迭代器,仿函数,适配器,空间配置器 - 容器:数据结构 vector,list, deque,set,map - 算法,sort,find,copy,for_each(Algorithms:质变算法和非质变算法) - 迭代器:扮演了容器和算法之间的胶合剂 - 仿函数:行为类似函数,可以作为算法的某种策略 迭代器种类: | 种类 | 功能 | 支持运算 | | -------------- | ------------------------------------ | ---------------------------------- | | 输入迭代器 | 对数据支持只读操作 | 只读 ++,==,!= | | 输出迭代器 | 对数据支持只写操作 | 只写,++ | | 向前迭代器 | 读写操作,并能向前推进迭代器 | 读写,++,==,!= | | 双向迭代器 | 读写操作,并能向前向后操作 | 读写,支持++,-- | | 随机访问迭代器 | 读写操作,可以跳跃的方式访问任意数据 | 读写,支持++,--,[n],-n,<,<=,>=,> | vector存放自定义数据类型: ```c++ #include<string> #include<iostream> #include<vector> #include<algorithm> using namespace std; class Person{ public: Person(string name,int age){ this->m_age = age; this->m_name = name; } string m_name; int m_age; }; void test01(){ vector<Person> v; Person p1("aa",10); Person p2("bb",20); Person p3("cc",40); Person p4("ff",30); Person p5("ee",50); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //遍历容器的数据 for(vector<Person>::iterator it=v.begin();it!=v.end();it++){ cout<<"姓名:"<<(*it).m_name<<"年龄:"<<(*it).m_age<<endl; cout<<"姓名:"<<it->m_name<<"年龄:"<<it->m_age<<endl; } } //存放自定义数据类型的指针 void test02(){ vector<Person*> v; Person p1("aa",10); Person p2("bb",20); Person p3("cc",40); Person p4("ff",30); Person p5("ee",50); v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); v.push_back(&p5); for(vector<Person*>::iterator it=v.begin();it!=v.end();it++){ cout<<"姓名:"<<(*(*it)).m_name<<"年龄:"<<(*(*it)).m_age<<endl; cout<<"姓名:"<<(*it)->m_name<<"年龄:"<<(*it)->m_age<<endl; } } int main(){ test02(); system("pause"); return 0; } ``` 容器嵌套容器 ```c++ #include<string> #include<iostream> #include<vector> #include<algorithm> using namespace std; void test01(){ vector< vector<int> > v; vector<int> v1; vector<int> v2; vector<int> v3; for(int i=0;i<4;i++){ v1.push_back(i); v2.push_back(i+4); v3.push_back(i+8); } v.push_back(v1); v.push_back(v2); v.push_back(v3); for(vector<vector<int> >::iterator it=v.begin();it!=v.end();it++){ for(vector<int>::iterator vit=(*it).begin();vit!=(*it).end();vit++){ cout<<*vit<<" "; } cout<<endl; } for(vector<vector<int> >::iterator it=v.begin();it!=v.end();it++){ for(vector<int>::iterator vit=it->begin();vit!=it->end();vit++){ cout<<*vit<<" "; } cout<<endl; } } int main(){ test01(); system("pause"); return 0; } ``` ##### string - 本质:string是c++风格的字符串,而string本质是一个类 - string类内部封装了许多成员方法 - string管理char*所分配的内存,不用担心复制越界和取值越界等,由内部进行负责。 ```c++ #include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; void test01(){ string s1; const char * str ="hello world"; string s2(str); cout<<"s2= "<<s2<<endl; string s3(s2); cout<<"s3= "<<s3<<endl; string s4(10,'a'); cout<<"s4= "<<s4<<endl; } int main(){ test01(); system("pause"); return 0; } ``` ##### string 赋值操作 - string& operator=(const char* s); - string& operator=(const string &s); - string& operator=(char c); - string& assign(const char* s); - string& assign(const char* s,int n); - string& assign(const string &s); - string& assign(int n,char c); ```c++ #include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; void test01(){ string str1; //string& operator=(const char* s); str1="hello world"; cout<<"str1="<<str1<<endl; string str2; //string& operator=(const string &s); str2 = str1; cout<<"str2="<<str2<<endl; //string& operator=(char c); string s3; s3='a'; cout<<"s3="<<s3<<endl; //string& assign(const char* s); string str4; str4.assign("hello c++"); cout<<"str4="<<str4<<endl; //string& assign(const char* s,int n); string str5; str5.assign("hello c++",5); cout<<"str5="<<str5<<endl; //string& assign(const string &s) string str6; str6.assign(str5); cout<<"str6="<<str6<<endl; //string& assign(int n,char c) string str7; str7.assign(7,'a'); cout<<"str7="<<str7<<endl; } int main(){ test01(); system("pause"); return 0; } ``` ##### string字符串拼接 - string& operator+=(const char* str);//重载+=操作符 - string& operator+=(const char c);//重载+=操作符 - string& operator+=(const string& str);//重载+=操作符 - string& append(const char* s);//重载+=操作符 - string& append(const char* s,int n);//重载+=操作符 - string& append(const string& s);//重载+=操作符 - string& append(const string& s,int pos, int n);//重载+=操作符 ```c++ #include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; void test01(){ string str1="我"; str1+="爱中国"; cout<<str1<<endl; str1+=':'; cout<<str1<<endl; string str2=" La"; str2+=str1; cout<<str2<<endl; string str3="I"; str3.append(" love"); str3.append(" china!!",7); cout<<str3<<endl; } int main(){ test01(); system("pause"); return 0; } ``` ###### string find - rfind 和find的区别:rfind从右往左找,find从左往右找; string replace - ```c++ string s1="abcdefghijk"; s1.replace(1,3,"1111"); cout<<s1<<endl; 结果为:a1111efghijk ``` ##### string 字符串的比较 - int compare(const string &s) - int compare(const char*s); - ```c++ string s1="zello"; string s2="hello"; cout<<s1.compare(s2)<<endl; ``` - 如果相等返回0,如果大于返回1,小于返回-1; ##### string 字符的存取 - ```c++ string s1="zello"; //两种方法 cout<<s1[0]<<endl; cout<<s1.at(1)<<endl; ``` ##### string 字符的插入和删除 - string& insert(int pos ,const char*s);//插入字符串 - string& insert(int pos,const string& str);//插入字符串 - string& insert(int pos, int n, char c);//指定位置插入n个字符c - string& erase(int pos,int n=npos);//删除从pos开始的n个字符 ```c++ string s1="abcde"; s1.insert(1,"111"); cout<<s1<<endl;//a111bcde s1.erase(1,3); cout<<s1<<endl;//abcde ``` ##### string 字串 - string substr(int pos,int n) ;//返回由pos开始的n个字符组成的字符串 - ````c++ string s1="abcde"; string s2 = s1.substr(0,3); cout<<s1<<endl;//a111bcde cout<<s2<<endl;//abc ```` -
{{ item.content }}
{{ child.content }}