关于为什么可以不 include string 。。。。 不过还是include的好
https://blog.csdn.net/Matrix990/article/details/107424825
内存四区 1.代码区
2.全局区 全局变量(不在任何函数内) 静态变量(static) 常量(字符串常量 “HelloWorld”、全局常量 const)
3.栈区 函数参数值 局部变量 局部常量
4.堆区 new delete
编译后 程序运行前 可执行程序
1.代码区(共享 只读)
2.全局区
运行后
栈区 堆区
==不要返回局部变量的地址!==
释放 数组 delete [] p
对于基本数据类型 delete[]p 和 delete p 一样
对于自定义数据类型 delete [] p 正确 delete p 会导致内存泄漏! delete[] p 用来释放p指向的内存,!!还逐一调用数组中每个对象的destructor!!
https://www.cnblogs.com/whwywzhj/p/7905176.html
引用 1.引用必须初始化 2. 引用初始化后不可以更改
1.不要返回局部变量的引用! 2.函数的调用可以作为左值
引用的本质是一个指针常量
引用可以修饰函数形参 防止误操作
函数高级 函数的默认参数 1.某个位置有默认值 这个位置后的所有参数都要有默认值 2.函数声明有认默参数,函数实现就不可以有! 两者只能有一个写默认参数
函数的占位参数 即只写参数类型,不写参数名。 可以有默认值。1 2 3 4 void test22 (int =10 ) { cout<<"占位参数!" <<endl; }
函数重载 满足函数重载的条件
1.在同一作用域下 2.函数名相同 3.函数参数 不同(类型,个数,不同类型参数的顺序)
==函数值无法作为重载的条件==
函数重载注意事项 引用作为重载条件
1 2 3 4 5 6 7 8 9 void fun (int & a) { cout<<"no const" <<endl; } void fun (const int &s) { cout<<"const" <<endl; }
fun穿入int 默认是走第一个函数 如果直接传入 10 会走下面的const
函数重载碰到函数默认参数
如果两个重载 的函数 除默认参数不同外,其他参数都相同,调用时给默认参数赋值,即使用默认参数值,则编译器 无法知道具体调用哪一个函数 出现二义性
==函数重载尽量避免写默认参数==
1 2 3 4 5 6 7 8 void test24 (int a, int b=10 ) {} void test24 (int a) {}
类 属性 行为
封装 封装的意义 1.将属性和行为作为一个整体,表现生活中的事物 2.将属性和行为加以权限控制
权限
1.公共权限 2.保护权限 protected (子类可以访问 父类中的保护内容 不可以访问 父类中的私有内容) 3.私有权限 private
struct 和 class 的区别是 默认权限不同
成员函数设置为私有的好处 1.可以自己控制读写权限 2.对于写可以检测数据的有效性
构造函数和析构函数 构造函数 1.没有返回值,不需要写返回值 2.函数名与类名相同 3.构造函数可以有参数,因此可以发生重载 4.程序在调用对象时会自动调用构造,无需手动调用,且只会调用一次
析构函数 1.没有返回值,不需要写返回值 2.函数名与类名相同前面加~符号 3.无参数,不可以发生重载 4.程序在调用对象时会自动调用,无需手动调用,且只会调用一次
构造和析构 一般写在public 下 private 似乎有点难,应该用不到:
https://www.cnblogs.com/kangyoung/articles/2375211.html
构造函数的分类及调用 分类 按照有无参数
1.无参构造(默认构造) 2.有参构造
按照是否用于拷贝 1.普通构造函数 2.拷贝构造函数
调用 调用无参构造初始化不要加括号,否则会认为是一个函数声明
Person(10) 为匿名对象 ==似乎匿名对象单独存在可以调用 无参构造 和 有参构造 但不可以使用拷贝构造?==
因为 Person(p) 等价于 Person p 想当于声明新对象, 此时再调用拷贝构造 就会 重声明 报错 error: redeclaration of ‘Person p’
而且用 Person(p)声明一个对象, 会给出警告1 warning: unnecessary parentheses in declaration of ‘p7’ [-Wparentheses]
告诉你声明时 括号是多余的。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Person1 { int age=0 ; public : Person1 ()匿名 { cout<<"无参构造" <<endl; } Person1 (int a) { cout<<"有参构造" <<endl; } Person1 (const Person1 &p) { this ->age=p.age; cout<<"拷贝构造" <<endl; } int getAge () { return age; } ~Person1 () { cout<<"析构函数" <<endl; } }; void test25 () { cout<<"====1.括号法==========" <<endl; Person1 p1; Person1 p2 (10 ) ; Person1 p3 (p1) ; cout<<"====2.显式法==========" <<endl; Person1 p4; Person1 p5=Person1 (10 ); Person1 p6=Person1 (p1); cout<<"====匿名对象的构造和析构==========" <<endl; Person1 (); Person1 (10 ); cout<<"====3.隐式转换法==========" <<endl; Person1 p7; Person1 p8=10 ; Person1 p9=p1; cout<<"================" <<endl; }
参考 匿名对象和拷贝构造函数
https://blog.csdn.net/weixin_43812622/article/details/100536829 拓展 匿名函数https://www.cnblogs.com/cthon/p/9173472.html
总之 感觉括号法最好用。。。但是记住无参数声明不要加括号 ,否则就是一个函数声明 不过 加括号多此一举 正常人都不会加
拷贝构造函数的调用时机 什么时候拷贝构造函数会被调用呢
1.利用一个已经创建完毕的对象来初始化一个新对象 2.值传递的方式给函数参数传值 3.值方式返回局部对象(gcc 优化掉 因此不显示)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void dowork0 (Person1 p) {} Person1 dowork () { Person1 p; return p; } void test26 () { cout<<"=========拷贝构造=========" <<endl; Person1 p; Person1 p1 (p) ; cout<<"=========值传递参数, 函数返回后 局部变量对象被析构=========" <<endl; Person1 p2; dowork0 (p2); cout<<"=========值返回局部对象,gcc 编译器优化 不输出返回值的构造函数=========" <<endl; Person1 p3; p3=dowork (); cout<<"===========" <<endl; }
构造函数的调用规则 默认 1.默认构造和默认析构 函数体为空 2.默认拷贝构造 对所有属性进行值拷贝
用户提供 拷贝构造 其他构造不在提供 需要手写 用户提供 有参构造 则无参构造不再提供 需要手写
深拷贝和浅拷贝 浅拷贝 会导致堆区内存重复释放 free(): double free detected in tcache 2 已放弃 (核心已转储)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class DeepCopy { public : DeepCopy (int a) { this ->pa=new int (a); } DeepCopy (const DeepCopy & d) { this ->pa=new int (*d.pa); } ~DeepCopy () { if (pa!=NULL ) { delete pa; pa=NULL ; } } void show () { cout<<*pa<<endl; } private : int *pa; }; void deepcopy () { DeepCopy d1 (1 ) ; DeepCopy d2 (d1) ; d1.show (); d2.show (); }
即在拷贝构造函数中使用深拷贝, 先new一块新的地址 然后赋值
初始化列表 。。。 不知道有啥用。。。 类对象作为类成员 先构造成员的类 再构造类 析构顺序与之相反
静态成员 静态成员变量 1.所有对象共享同一个 静态变量 (即不属于任何一个特定的对象 1.通过对象访问 2. 通过类名访问) 2.编译阶段分配内存(全局区) 3.类内声明 类外初始化
静态成员函数 1.所有对象共享同一个函数 2.静态成员函数只能访问静态成员变量
C++ 对象模型和this 指针 成员变量和成员函数分开存储 空对象占用内存空间为1 只有非静态成员函数变量 属于对象
this 指针 1.解决命名冲突 2.返回对象本身 *this 并返回对象相同类型的引用 即可 (可用于==链式编程==)
空指针访问成员函数 1 2 3 4 5 6 void test27 () { Person *p=NULL ; p->sayhello (); }
可以访问成员函数,因为函数并不存储在对象中 如果用指针访问成员函数,因为此时对象的指针置空 实际上并没有指向任何有效的 内存空间 强行访问其成员变量 会出现段错误
拓展 段错误
https://www.cnblogs.com/DirWang/articles/11657490.html https://blog.csdn.net/qq_29350001/article/details/53780697
常函数和常对象 1 2 3 4 5 6 7 8 9 10 11 class Person2 { public : int age; mutable int B; void chagename () const { age=10 ; B=100 ; } }
void chagename() const 为常函数, 本质是在函数内将 this 指针 变为 常量指针常量 所以 age=10 是非法的
如果想在常函数中修改成员函数 需要加 mutable 关键词 如B 就可以修改值
const Person a 即为常对象 常对象的 属性不可修改 (mutable 属性除外)
常对象只能调用常函数 因为普通成员函数可以修改属性
友元 让一个函数或者类 访问另一个类中的私有成员
1.全局函数做友元
2.类做友元
3.成员函数做友元
都是在类内加入== friend 函数声明或者类声明 ==即可1 2 3 4 5 6 7 8 9 10 11 12 13 14 class builing { friend class goodgay ;friend void goodday (builing & b) ;friend void goodgay::visit () ;public : builing (); string sittingroom; private : string bedroom; };
拓展 new 和 delete
https://blog.csdn.net/qq_44554964/article/details/104356169#:~:text=new%E5%92%8Cdelete%E6%98%AF%E4%B8%A4%E4%B8%AAC%2B%2B%E7%9A%84%E8%AF%AD%E6%B3%95%EF%BC%8C%E7%9B%B8%E5%BD%93%E4%BA%8EC%E4%B8%AD%E7%9A%84malloc%E4%B8%8Efree%2C%E4%B8%8D%E5%90%8C%E7%9A%84%E6%98%AFnew%20%E5%92%8Cdelete%E6%98%AF%E4%B8%A4%E4%B8%AA%E6%93%8D%E4%BD%9C%E7%AC%A6%EF%BC%8C%E8%80%8Cmalloc%20%E4%B8%8E%20free%20%E6%98%AF%E4%B8%A4%E4%B8%AA%E5%87%BD%E6%95%B0%20malloc%E7%9A%84%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95%E7%B1%BB%E4%BC%BC%E4%B8%BA%EF%BC%9Aint%20%2Ap%20%3D,%28sizeof%20%28int%29%29%E8%AF%A5%E8%AF%AD%E5%8F%A5%E5%AF%B9%E5%BA%94%E7%9A%84C%2B%2B%E8%AF%AD%E5%8F%A5%E4%B8%BA%20int%20%2Ap%20%3D%20new%20int%20%2810%29
运算符重载 1.成员函数重载
1 2 3 4 5 6 7 fish operator +(fish & f) { fish temp; temp.a= this ->a+f.a; temp.b= this ->b+f.b; return temp; }
1 2 3 4 5 6 fish operator +(fish f) { f.a+=a; f.b+=b; return f; }
全局函数重载 (两个参数)
1 2 3 4 5 6 7 fish operator +(fish f1, fish f2) { f1.a+=f2.a; f1.b+=f2.b; return f1; }
左移运算符重载 1.只能用全局函数重载左移动运算符 2.似乎只有引用的方式才能使用 ostream 。。。
拓展 引用作为函数参数的好处
https://www.cnblogs.com/pengjun-shanghai/p/4914984.html 1.传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标 对象(在主调函数中)的操作。 2.使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的 副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比 用一般变量传递参数的效率和所占空间都好。 3.使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅 读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
为何什么有些情况函数要返回引用
https://www.cnblogs.com/codingmengmeng/p/5871254.html
1 2 3 4 5 6 ostream & operator <<(ostream & cout,fish &f) { cout<<f.a<<" " <<f.b<<endl; return cout; }
递增运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class myInt { friend ostream & operator <<(ostream & cout, myInt & num);public : myInt (int num=0 ) { this ->num=num; } myInt & operator ++() { this ->num+=1 ; return *this ; } myInt & operator ++(int ) { this ->p=new myInt (this ->num); this ->num++; return *this ->p; } ~myInt () { if (p!=NULL ) { delete p; p=NULL ; } } private : int num; myInt* p; }; ostream & operator <<(ostream & cout, myInt & num) { cout<<num.num; return cout; } void test30 () { myInt a; cout<<a<<endl; ++(++a); cout<<a++<<endl; cout<<a<<endl; }
赋值运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class myPerson { public : myPerson (int age=0 ) { p=new int (age); } myPerson & operator +(myPerson & mp) { temp =new myPerson; *temp->p=*this ->p+*mp.p; return *temp; } myPerson & operator =(myPerson & mp) { *this ->p=*mp.p; return *this ; } ~myPerson () { if (p!=NULL ) { delete p; p=NULL ; } if (temp!=NULL ) { delete temp; temp=NULL ; } } void show () { cout<<*p<<endl; } private : int *p=NULL ; myPerson *temp=NULL ; }; void test31 () { myPerson mp1 (1 ) ; myPerson mp2 (2 ) ; myPerson mp3; mp3=mp1+mp2; mp1.show (); mp2.show (); mp3.show (); mp1=mp2=mp3; mp1.show (); mp2.show (); mp3.show (); }
拓展 类指针成员初始化
https://blog.csdn.net/ckanhw/article/details/78033898
关系运算符重载 略
函数调用运算符重载 使得对象可以 像函数一样调用 因此也叫仿函数 不创建对象的仿函数调用: ==className()(参数)== 其中 className()为匿名对象
继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 class dady { public :void sameFunc () { cout<<"dady 调用" <<endl; } void sameFunc (int a) { cout<<"dady 调用 有参重载" <<a<<endl; } dady (){ cout<<"dady的构造函数" <<endl; } ~dady () { cout<<"dady的析构函数" <<endl; } public : int a; int sameName=100 ; static int staticSameName; protected : int b; private : int c; }; int dady::staticSameName=100 ;class son : public dady{ public :void sameFunc () { cout<<"son 调用" <<endl; } son (){ cout<<"son的构造函数" <<endl; } ~son () { cout<<"son的析构函数" <<endl; } public : int s; int sameName=200 ; static int staticSameName; }; int son::staticSameName=200 ;void test32 () { son s; cout<<sizeof (s)<<endl; cout<<s.sameName<<endl; cout<<s.dady::sameName<<endl; s.sameFunc (); s.dady::sameFunc (); s.dady::sameFunc (999 ); cout<<son::dady::staticSameName<<endl; }
继承方式 1.公共继承 父类中的所有==非私有==属性 原封不动的 复制到子类中 2.保护继承 父类中的所有==非私==有属性 全部变为保护 protected 并复制到子类中 3.私有继承 父类中的所有==非私==有属性 全部变为私有 private 并复制到子类中
继承模型 父类中的非静态成员都会被子类继承下去 父类中的私有成员属性 被编译器隐藏 因此访问不到 但确实继承到子类中了
构建 析构 顺序 创建子类对象时 父类对象构造的时机?
构造: 父 子 析构: 子 父
父子同名访问 1.同名属性
2.同名函数
默认访问子类成员, 要想访问父类的成员 需要加作用域符号
父子类静态成员同名 默认访问子类成员, 要想访问父类的成员 需要加作用域符号
并且可以通过类名访问 如 son::dady::父类成员
多继承语法 子类有多个父类 多继承可能引发父类中有同名成员出现 需要==加作用域区分== C++ 实际开发中不建议用多继承
菱形继承
一个爷爷生了两个儿子两个儿子是孙子的共同父亲。。
使用虚继承(: virtual 继承类型 类),2个父类中的继承自爷爷类的同名成员 就会被看做是一个变量 原理: 使用虚继承后,父类从爷爷类继承 虚基类指针 vbptr 而不是直接继承爷爷的成员
虚基类指针 指向 虚基类表 vbtable , 虚基类表中记录了偏移量 虚基类指针根据这个偏移量 就可以找到 对象中公共的成员
萎形继承带来的主要问题是子类继承==两份相同的数据== ,导致资源浪费 而且 毫无意义
利用虚继承可以解决荞形继承问题
多继承 不建议写 但看到别人写 菱形继承 要明白写的是啥。。
多态 1静态多态(地址早绑定 运行前确定函数地址) 重载 2动态多态(地址晚绑定 运行后确定函数地址)派生类和虚函数
动态多态构成条件 1.继承关系 2.子类重写父类中的虚函数
动态多态的使用方法
父类中的指针或者引用 执行子类对象
父类中的虚函数 其实是一个 虚函数指针 vfptr 指向一个虚函数表 子类从父类中继承了 这个虚函数指针, 自然也就继承了虚函数表
一旦子类重写父类的虚函数,那么虚函数表中的记录函数地址(指针)的就是子类的 因此就会调用子类函数
参考 虚函数表
https://cloud.tencent.com/developer/article/1599283 成员函数存储方式https://blog.csdn.net/fuzhongmin05/article/details/59112081
多态的好处 1.组织结构清晰 2.可读性强 3.对于前期和后期拓展和维护性高
(虽然我感觉用不到。。。)
拓展 C/C++ 的效率
https://blog.csdn.net/tanli20090506/article/details/79233255
抽象类 有纯虚函数 (即 virtual 返回值 函数名 (参数列表) =0;) 的类叫抽象类
特点
1.无法实例化对象 2.子类必须重写抽象类中的纯虚函数 否则也属于抽象类
虚析构和纯虚析构
多态使用时 如果子类中有 属性开辟到堆区 父类指针在释放时 无法调用到子类的析构函数 解决方法: 将父类中的析构函数改为 虚析构或者纯虚析构
虚析构和纯虑析构共性: 可以解决父类指针释放子类对象 都需要有其体的函数实现 虚析构和纯虚析构区别: 如果是纯虚析构, 该类属于抽象类,无法实例化对象
不过似乎用不到 因为我连多态似乎都用不到。。。
文件操作 文本文件: ASCII 码 存储
二进制文件: 文本二进制存储
ofstream ifstream fsream (读写)
打开方式
ios::
in out ate app trunc binary
用多种打开方式 如 ios::in | ios::out 即用 | 分割
读文件(感觉第三种比较方便, 把每一行存储在一个 std::string 里,然后可以对每一行的字符进行单独操作)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void read () { fstream fs; fs.open ("./test.txt" ,ios::in); if (!fs.is_open ()) { cout<<"文件打开失败" <<endl; return ; } char c; while ((c= fs.get ())!=EOF) { cout<<c; } fs.close (); }
二进制写入读取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class cat1 { public : cat1 (string name = "" , int age =0 ) { this ->name=name; this ->age=age; } string name; int age; }; void writeB () { ofstream ofs ("cat1.txt" ,ios::out|ios::binary) ; cat1 c ("张三" , 18 ) ; ofs.write ((const char *)&c,sizeof (cat1)); ofs.close (); } void readB () { ifstream ifs ("cat1.txt" , ios::in | ios::binary) ; if (!ifs.is_open ()) { cout<<"文件打开失败!" <<endl; return ; } cat1 c; ifs.read ((char *)&c,sizeof (cat1)); cout<<c.name<<" " <<c.age<<endl; ifs.close (); }
泛型编程 模板 和 STL 函数模板 和 类 模板 使用模板必须确定数据类型 并且能推导出一致的数据类型
普通函数和函数模板的区别 是否支持隐式类型转换
普通函数 支持隐式类型转换
函数模板 1自动推导类型 不支持隐式类型转换 2显示指定类型 支持隐式类型转换
建议 ==显示指定类型==!防止编译器自动推导出错。。。
普通函数和函数模板调用规则 重载 1.优先重载 普通函数 2.可以使用空参数列表来强制调用函数模板 3.函数模板也可以发生重载 4.如果函数模板产生更好的匹配 则优先调用函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 test37 () { int a=10 ; int b=20 ; myPrint (a,b); myPrint<>(a,b); myPrint (a,b,a); char c='c' ; char d='d' ; myPrint (c,d); }
==注意!本节只为展示调用规则 实际写代码 建议不要 函数模板和普通函数 混用!!!==
模板的局限性 自定义类型对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 template <class T>bool myCompare (T&a,T& b) { if (a==b) { return true ; } else { return false ; } } template <> bool myCompare (cat1 &a,cat1 & b) { if ((a.name==b.name) && (a.age=b.age)) { return true ; } else { return false ; } } void test38 () { cat1 a ("a" ,10 ) ; cat1 b ("a" ,10 ) ; bool ret=myCompare (a,b); if (ret){ cout<<"a=b" <<endl; }else { cout<<"a!=b" <<endl; } }
类模板 类模板和函数模板的区别 1.类模板没有自动类型推导的使用方式 2.类模板在模板参数列表中可以有默认参数(函数模板则不可以有)
类模板成员函数的创建时机 模板调用时才被创建
类模板对象 作为 函数参数(有点晕~~~) 通过类模板创建的对象 有三种方式传参
指定传入的类型
类模板参数 模板化
类模板 模板化
1 最常用 2 3 过于复杂 (==类模板== 作为 ==函数模板== 的参数)
类模板与继承 1 2 3 4 5 6 7 8 9 10 11 template <class T >class Base { T m; }; template <class T , class T2 >class BaseSon : public Base<T>{ T2 m; };
类模板中成员函数类外实现时 需要加上模板参数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 template <class T >class AAA { public : AAA (T a); void show () ; int a; }; template <class T >AAA<T>::AAA (T a) { this ->a=a; } template <class T >void AAA<T>::show (){ cout<<this ->a<<endl; } void test39 () { AAA<int >a (10 ); a.show (); }
类模板分文件编写 类模板中成员函数创建时机是在调用阶段,导致分文件编写 链接不到
解决方法
直接包含 cpp 源文件
将声明和实现写到同一个文件中,改后缀名为 .hpp (不强制 只是约定的后缀名)
一般把一个类单独写到一个文件中
拓展:
https://www.zhihu.com/question/270627626 https://blog.csdn.net/u014583317/article/details/108545250
C++为什么要求把类声明和类实现进行分离?又为什么要求模板类的类声明和类实现要都放在头文件而不能分离? - 知乎https://www.zhihu.com/question/270627626/answer/355367882
即 普通类 声明实现 分离 可以起到 隐藏源代码的作用
但 类模板中成员函数创建时机是在调用阶段 所以无法分离声明与实现。
.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #pragma once #include <iostream> using namespace std;template <class T1 , class T2 >class PPP { public : PPP (T1 name, T2 age); void showPerson () ; T1 name; T2 age; }; template <class T1 ,class T2 >PPP<T1,T2>::PPP (T1 name, T2 age) { this ->name=name; this ->age=age; } template <class T1 ,class T2 >void PPP<T1,T2>::showPerson (){ cout<<this ->name<<" " <<this ->age<<endl; }
类模板与友元 (直接跳过。。。。类内实现很简单 类外实现 醉了) 类模板案例 数组类封装 视频
https://www.bilibili.com/video/BV1et411b73Z?p=183&spm_id_from=pageDriver
myArray.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 #pragma once #include <iostream> using namespace std;template <class T >class myArray { public : myArray (int capacity=0 ); myArray (const myArray<T> & ma); ~myArray (); myArray & operator =(myArray & ma); void push_back (T elem) ; void pop_back () ; T & operator [](int index); int getSize () ; int getCapacity () ; void showSize () ; void showCapacity () ; void show () ; private : int size; int capacity; T * p=NULL ; }; template <class T >myArray<T>::myArray (int capacity) { this ->capacity=capacity; this ->p= new T[capacity]; this ->size=0 ; } template <class T >myArray<T>::myArray (const myArray<T> & ma) { this ->capacity=ma.capacity; this ->size=ma.size; this ->p= new T[ma.capacity]; for (int i = 0 ; i < this ->size; i++) { this ->p[i]=ma.p[i]; } } template <class T >myArray<T>::~myArray () { if (this ->p!=NULL ) { delete [] p; p=NULL ; } } template <class T >myArray<T> & myArray<T>::operator =(myArray<T> & ma) { if (this ->capacity>=ma.size){ for (int i = 0 ; i < ma.size; i++) { this ->p[i]= ma.p[i]; this ->size=ma.size; } }else { for (int i = 0 ; i < this ->capacity; i++) { this ->p[i]=ma.p[i]; this ->size=this ->capacity; } } return *this ; } template <class T >void myArray<T>::push_back (T elem){ if (this ->size<capacity) { this ->p[this ->size]=elem; this ->size++; }else { cout<<"容器已满" <<endl; return ; } } template <class T >void myArray<T>::pop_back (){ if (this ->size>0 ) { this ->size--; }else if (size==0 ){ cout<<"容器已经为空!" <<endl; } } template <class T >T & myArray<T>::operator [](int index) { if (0 <=index && index<this ->size) { return this ->p[index]; }else { cout<<"索引超出数组范围:[" <<"0," <<this ->size<<"]" <<endl; return this ->p[0 ]; } } template <class T >int myArray<T>::getSize (){ return this ->size; } template <class T >int myArray<T>::getCapacity (){ return this ->capacity; } template <class T >void myArray<T>::showSize (){ cout<<"大小: " << this ->size <<endl; } template <class T >void myArray<T>::showCapacity (){ cout<<"容量: " <<this ->capacity<<endl; } template <class T >void myArray<T>::show (){ for (int i = 0 ; i < this ->size; i++) { cout << this ->p[i]<<" " ; } cout<<endl; }
测试代码
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include "myArray.hpp" class testPerson { public : testPerson (){}; testPerson (string name, int age) { this ->name=name; this ->age=age; } string name; int age; }; ostream & operator <<(ostream & cout, testPerson & p) { cout<<"(" <<p.name<<" " <<p.age<<")" ; return cout; } void test41 () { myArray<testPerson>array (10 ); testPerson p1 ("A" ,1 ) ; testPerson p2 ("B" ,2 ) ; testPerson p3 ("C" ,3 ) ; testPerson p4 ("D" ,4 ) ; testPerson p5 ("E" ,5 ) ; array.push_back (p1); array.push_back (p2); array.push_back (p3); array.push_back (p4); array.push_back (p5); array.showSize (); array.showCapacity (); array.show (); } int main (int argc, char const *argv[]) { test41 (); return 0 ; }
STL ! stl 6 大组件
容器 算法 迭代器 仿函数 适配器(配接器) 空间配置器
容器(即一些用的比较广泛的 ===数据结构===) 1.序列式容器 2.关联式容器
==算法== 1.质变算法 2.非质变算法
迭代器 链接 容器与算法 类似指针 种类 1.输入 2.输出 3.向前 4.双向 5.随机访问
STL 中大部分是 4 和 5
vector 容器 并不是在原空间之后续接新空间 而是寻找更大的内存空间 将原数据拷贝到新空间 释放原来的空间
迭代器支持随机访问,最强的一种迭代器!
resize 指定长度比原来长 则默认用0填充(容量变大) 如果比原来短 则截断(容量不变)
resize 变小时不会导致容量变化 因此会造成资源浪费
因此可以用 ==匿名对象的拷贝构造+ swap== 收缩内存
map 容器
键-值 排序
自动按照键值排序
关联式容器 底层结构是二叉树
map 不允许有重复的 key 值元素 multimap 允许容器中有重复的 key 值元素
有关排序的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class myCompare1 { public : bool operator () (int a, int b) const { return a>b; } }; void printM1 (map<int , int ,myCompare1> & m) { cout<<"size:" <<m.size ()<<endl; for (auto i:m){ cout<<"key:" <<i.first<<"," <<"value:" <<i.second<<endl; } } void test46 () { map<int ,int ,myCompare1> m; m.insert (make_pair (1 ,10 )); m.insert (make_pair (2 ,10 )); m.insert (make_pair (3 ,10 )); printM1 (m); }
在上面的案例中 仿函数需要加 const 来使得 可以被 常量调用 参考
https://blog.csdn.net/weixin_43734095/article/details/104465307
map容器 插入 insert(make_pair(key,value)) 好处是已有元素不会被覆盖 如果想要查询或修改元素 可以用 at(key) 的方式 也可以用 [] 修改元素 但不建议用 [] 查询 ,因为不存在的key会自动创建新的元素
STL 案例 员工分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 void test47 () { vector<worker>v; v.push_back (worker ("A" ,100 )); v.push_back (worker ("B" ,100 )); v.push_back (worker ("C" ,100 )); v.push_back (worker ("D" ,100 )); v.push_back (worker ("E" ,100 )); v.push_back (worker ("F" ,100 )); v.push_back (worker ("G" ,100 )); v.push_back (worker ("H" ,100 )); v.push_back (worker ("I" ,100 )); v.push_back (worker ("J" ,100 )); printV (v); multimap<string, worker>m; for (auto & w:v){ switch (myrand (0 ,2 )) { case 0 : w.salary=myrand (8000 ,9000 ); m.insert (make_pair ("策划" ,w)); break ; case 1 : w.salary=myrand (6000 ,7000 ); m.insert (make_pair ("美术" ,w)); break ; case 2 : w.salary=myrand (10000 ,12000 ); m.insert (make_pair ("研发" ,w)); break ; default : break ; } } multimap<string, worker>::iterator it; multimap<string, worker>::iterator it1=m.find ("研发" ); multimap<string, worker>::iterator it2=m.find ("策划" ); multimap<string, worker>::iterator it3=m.find ("美术" ); int count; int count1=m.count ("研发" ); int count2=m.count ("策划" ); int count3=m.count ("美术" ); it =it1; count=count1; for (int i=0 ; i < count; i++,it++) { cout<<it->first<<" " <<it->second.name<<it->second.salary<<endl; } it =it2; count=count2; for (int i=0 ; i < count; i++,it++) { cout<<it->first<<" " <<it->second.name<<it->second.salary<<endl; } it =it3; count=count3; for (int i=0 ; i < count; i++,it++) { cout<<it->first<<" " <<it->second.name<<it->second.salary<<endl; } }
函数对象 1.与普通函数几乎一致 参数 返回值 2.可以有自己的属性 3.可以作为函数参数 传递一个函数
仿函数与回调函数:
https://blog.csdn.net/eriol/article/details/84021857
谓词 似乎就是 返回值为 bool 类型的 函数或者是仿函数 参数一个 就叫 一元谓词 参数是两个 就叫二元谓词
内建仿函数 需要头文件
functional
算数仿函数 关系仿函数
应该主要是用于 作 函数的参数 (匿名对象形式) 如1 sort (v.begin (),v.end (),greater<int >());
逻辑仿函数