虽说还没学完这本书,但是我想着这件事差不多已经可以着手开始做了,就慢慢整理,慢慢更新,总会写完的。
这篇博客是会更新的,直到把我认为要考的内容写完
有和同学讨论这篇博客啦,因为我真的很想把这篇博客写好,我也确实发现了一些问题,就是我这篇博客是基于你C语言的基础ok,C++愿意照着课本看,却有点疑惑,可以来这里看看我的理解的,当然如果你学的很棒的话,这篇博客没有吊用,相信自己就好了,其实我有想着基础不好的同学也能看下去啦,我有试着放一些我认为又基础,又重要的定义啦,只是很不好意思的是,我有些觉得理所因当的东西,就没有写进去,很不好意思啦,但我还是很真诚的希望这篇博客会对你产生一点点的帮助,还是那句话,如果有看不懂的,或者什么意见或是建议的话,欢迎留言或者私戳我。
首先要说的是C++面向对象程序设计这门课的引言
C语言的特点是简洁,就是特别特别的简洁,而且不论放在那里都能用
而C++是基于C语言而产生的,它主要是为了实现数据的封装,只提供接口实现访问封装的数据,这样就会使得外界无法直接访问你定义的被封装的数据,但是这样的话,原本简洁明了的C语言代码就会变得臃肿不堪,这这也恰恰是C++的特点,无法删去,所以既然不能不写,那就想办法少写几次,于是就有了继承和多态,还有模板。
提一下对象和类的定义,最基础的东西不能混淆啊
#include<bits/stdc++.h> using namespace std; class A { };///这叫定义类 int main() { A a;///这叫声明对象,对象就是a; }
插个东西,关于语法糖的定义www.baidu.com
本册书第一章讲的是基本的输入输出流以及对C语言中的字符串的优化类string的基本描述,这里不多做赘述,只提几点(因为基本不考)
流输入输出具有自动识别数据类型的功能,也可以对自己定义的类的对象直接输出,不过要重载流
string也是一个类,你可以在很多方面就把他当成字符数组,只是相对而言,string很多操作实在是简单了不少
string重载了输入输出流,构造函数,+,+=,==,!=,<,>,等操作运算符,可直接进行字符串的连接,比较,判断,直接+就是连接,直接比较就是通过字典序比较的
这边有个不考,但是想提一下的内容在C语言中用gets读入一行带空格的字符,
在c++中可以用cin.getline(写数组名或string类对象名,读入长度(可不写))
第二章,C++基于C做出的一些改进以及很多语法糖(基本不考,但是某些题可能会用到)
C++11以上新定义的内容是不考的,而且都是语法糖
函数缺省参数:在定义函数的时候你可以给形参表中的成员赋默认值(第一个赋默认值后的所有成员都得赋默认值)调用函数时根据你输入的参数从左往右进行匹配,若无默认值则传入,有默认值则覆盖
#include<bits/stdc++.h> using namespace std; void int f(int a,int b=0,int c=0){}///第一个有默认值的右边成员均得有默认值 int main() { f(1);///a=1,b=0,c=0; f(1,2);///a=1,b=2,c=0; f();///错误 }
左值引用: 给某个单一变量起了个别名 ,就比如int &a=b;对a进行的操作就是对b的
右值引用: 给一个表达式起了个别名 int &&a=b+c;把以后所有出现的所有a看做b+c就好
(这两个引用定义时就得赋值)
(和C中的宏定义很像,但是安全监测机制是不会检查宏定义的)
new和delete
为指针分配内存空间,p32,p33书上介绍的很明白,不多赘述,只是得记下用法,并且注意下数组的释放内存和单个数据的释放内存的区别。
卡死人的const位置不同表示不同意义(const定义的常量必须初始化,但是有在类的内部有两种初始化方式1.定义时赋值2.列表初始化)(常量的意思就是一经定义就不可以修改,只可以使用的东西啊)
#include<bits/stdc++.h> using namespace std; int main() { int *const p=0;///指针p是常量指针,即指向一经定义无法修改,但是指向的变量的可以改的,可指向常数,不可指向常量 int const *p=0; const int *p=0;///这两种是一个意思,不同表达, ///指针p是变量可指向不同的常量,居然可以指向变量,然后强制修改为了常量,即通过该指针不可修改访问地址的值,但是可以直接修改变量值 const int const *p=0; ///指针p是常量指向的也是常量(都不能改) }内联函数的意思就是一个取代,使用该函数时用函数内部语句替换,理论上是可以加快点速度的(真的就一点点)
函数返回类型前加关键词inline
函数重载和二义性(比较重要)
int f(int a){} 1 int f(float a){} 2 float f(int a){} 3 int f(double a){} 4 /*三个函数都是f函数的重载形式,可以通过改变形参表或返回类型的方式定义一个函数的重载类型, 调用时程序会自动根据最匹配的情况选择调用那个函数,在这个例子下1和3就会发生二义性问题 即你传入一个int类型的值后,两个函数都能匹配上,或者只有2,4函数的时候传入int类型值也会产生二义性*/
作用域与生命周期(对象,函数,==),找定义时所在的大括号,该大括号结束,生命周期结束,该大括号内部都是作用域
文件流输入输出,参考课设前置技能那一篇,有文件流基本运用,也就够了
其他几个我感觉不重要,那个底层const和顶层const还有constexpr,我不会,当然你如果觉得有什么建议的话,可以留言或是私戳我。
第三章
抽象与封装:将一个客观事物抽象为各种数据成员的集合,放入一个类中,就是抽象为类,比如课设实验报告的第一条就是写如何抽象的,封装就是将这些数据保护起来,外部不可直接访问只能通过你定义的接口访问,eg:这些数据就是private,接口就是public。
每个类在定义的时候就会自动编译一堆无参函数eg:构造函数,析构函数,拷贝构造函数等;
这些都是可以重载的,书上都有例子,我再写一遍意义不大, 所以书上的例子要尽力看懂呀都,我就写一下我对这几个函数的认识
重载构造函数:就是在声明对象的时候可以直接赋值给该对象私有成员,不用去调用接口赋值,方便一点,这个直接赋值呢,又分为在构造函数内部(函数实现部分,大括号内)对每个私有数据成员赋值,还可以用列表初始化的方式赋值(这点比较重要,在继承的时候还会再拓展的),书上例子都有,我在这说的意思就是你自己好好去看看书上的例题代码。
指针悬挂问题:第一个对象的私有成员指针指向了这块内存区域,然后将该对象赋值给第二个对象,执行第二个函数的析构函数时,第一个对象的指针本来指向该内存区域,但是被释放了,就会变成一个野指针,擅自修改或调用就会出现未知bug,这就是指针悬挂问题,所以需要重载拷贝构造函数以及重载赋值运算符,在赋值的时候给第二个对象也开辟一块新的内存空间将字符串复制过去(关于如何重载,书上有例子)
#include<bits/stdc++.h> using namespace std; class A { }; int main() { A a; A b=a;///此处调用拷贝构造函数 A c;c=a;///此处调用赋值运算符函数 }
分清了这几个函数之后,就得记下他们的用法和如何重载,以及这三个函数的特点
析构函数,在该类的对象的生命周期结束时调用,只要是用于若构造函数声明了内存空间的话,释放掉,析构函数的调用次序是对象的声明顺序的逆序,因为这些对象存入的地方是栈,栈具有先进后出的特点
移动函数不考!就是一个调用的时候免去一点点复制一遍的操作,压缩了真丶一点点时间,语法糖(还是烦得要死的那种)
静态数据成员以及静态成员函数:在声明类数据成员或是函数的时候在前面加上关键词static,就是静态的了,静态数据成员是对于该类所有对象而言的,只有一种初始化方式,在类外初始化;静态成员函数只允许访问静态成员函数或是静态数据成员,而普通函数可以随便访问;
用处就是当做计数器,对于该类的对象而言它就是个全局变量,在该类的第一个对象声明时声明静态的所有东西,其余时间不再重复声明,书上p-118 例3-21写挺好的,但是太麻烦了,静态成员同样受公开私有保护限制,比如类外部只能访问公开静态数据成员,都是全局的,我举个用作计数器的例子吧
#include<bits/stdc++.h> using namespace std; class A { static int a; public: A(){a++;} ~A(){a--;} void display(){cout<<"当前数量为"<<a<<endl;} ///这样就实现了每定义一个对象或是删去的时候自动计数了 }; int A::a=0;///静态数据成员赋值方式, int main() { }this指针:一个类对象隐藏的指针指向这个对象本身
在该章主要是用于鉴别变量名与类数据成员同名情况,以后还有其他更大更重要的作用在重载运算符中,举例解释
#include<bits/stdc++.h> using namespace std; class A { int num; public: void setnum(int num) { ///这个时候你直接写num=num就会产生二义性而混淆,但你可以用this this->num=num; } };
友元函数:一个后门,声明该函数为该类的友元函数后,可直接访问这个类的私有成员和保护成员;
该章用处不大,在重载操作运算符时很重要,但就考试而言的话,了解基本用法和特点即可。不具有传递性和逆向性,
还有友元类,即该类可直接访问其私有成员和保护成员。
该章到此结束,这一章还是蛮重要的,需要学会重载三种函数了解基本用法和特点,用静态数据成员做计数器,首次接触friend和this,主要是书上那几个例子确实要好好看懂,我就没放代码,这一章就看起来很短,但是真的蛮重要的!
第四章 继承
继承主要是为了实现代码的复用,减少代码的重复度,可以把共有的东西一起用
继承产生的叫做派生类(子类),被继承的叫做基类(父类)
前面没有讲public,private,protected三者区别这里提一下,在不使用继承的情况下,保护和私有是一样的,类外不可直接访问这两种,只可访问共有部分,所以共有的东西叫做接口,私有和保护的叫做被封装。
继承就是把基类的东西都变为自己的,继承分为三种情况,共有,私有,保护,主要用的就是公有继承,所以这里会着重讲这个
保护继承:基类的保护成员和公有成员都变为派生类的保护成员,基类的私有成员依旧是私有成员(但是这是基类的私有成员,你这个派生类内部是不可以直接访问基类的私有部分的(这很重要))
私有继承:基类的共有,私有,保护都会变为派生类的私有部分(于是就有了一个很有意思的推论,A私有继承B,C私有继承A那么C无法访问B中的任何东西)
公有继承:基类数据成员在派生类中访问权限保持不变(用的最多)(在强调一遍,基类的私有成员还是不能在派生类内部直接访问,但是保护成员可以)
关于using关键词
#include<bits/stdc++.h> using namespace std; class A { public: void f(){cout<<"A1"<<endl;} void f(int a){cout<<"A2"<<endl;} }; class B:public A { public: void f(){cout<<"B"<<endl;} ///using A::f;///表示继承基类的所有f同名函数,形参表一样的还是会被覆盖 private: using A::f;///A中f名函数没被覆盖的都会被改为私有成员 ///可以这样修改基类的共有和保护(因为可以直接访问)不可以修改私有成员的 }; int main() { B a; a.f(); a.f(3);///这种调用没加using的话就是错误的,因为派生类定义的f函数隐藏的基类的所有f的重载 }再提一次友元:友元函数不具有传递性,所以基类的友元不是派生类的,派生类的也不是基类的,带上继承了之后,派生类的友元函数能访问他新定义的数据成员或是成员函数还有基类继承来的共有和保护部分(感觉就是声明一个友元函数就说明这函数具有类内部的权限,也就是前一句的意思)
静态成员:基类的静态成员在整个继承体系中都是公用的;(真没了,就这么一句)
关于虚继承:即B继承A,C继承B和A,类A就被重复继承了,类A有的成员函数,在C中就不知道是继承B从A继承过来的一部分,还是直接从A继承过来,所以引入虚继承这一概念,在继承方式eg:public前加virtual,表示该类的所有成员只复制一次,虚继承,调用次序在普通继承前(不看相对位置)
(重点)如果一个基类定义了 含参(无初始值)构造函数的话,他的派生类就必须重载构造函数,并用列表初始化的方式给基类构造函数赋值(举个例子吧!这个又重要又爱考,麻烦记记吧)(多继承的话派生类就得一 一用列表初始化的方式给这些基类赋值了,而他们的构造函数的优先级就是虚继承最优,然后是class B:后面的次序,然后是成员类(如果有的话),最后是该派生类构造函数的函数体内部语句,析构函数的次序就按构造函数次序反着就好(因为是栈保存))
class A { public: A(int a){} }; class B:public A { public: B(int a):A(a){}///正确格式,这就是必须重载的情况 ///如果这么定义是对的 A a;那么B就可以不重载构造函数 };派生类继承基类构造函数不考(不讲)
重要的构造函数,析构函数调用次序问题
栈的思想,我也举不出什么简单的例子了,就是派生类的构造函数在后执行,派生类的析构函数在前面执行
书上p160 例4-12多看看,一遍不懂两遍,一定要看懂呀
派生类的赋值,复制,移动操作(移动应该是不考的)
#include<bits/stdc++.h> using namespace std; class A { public: A &operator=(A &o)///重载了赋值运算符 {cout<<"A"<<endl;} }; class B:public A { public: B &operator=(B &o) { ///A::operator=(o); cout<<"B"<<endl; } }; int main() { B b1,b; b=b1;///调用了类B的赋值运算符,基类的完全不用管 ///这份代码输出的只有B,也就是说B的赋值运算符重载需要包括吧基类赋值运算法重载也写了 ///次序问题,就是第14,15行你自定义的,拷贝构造函数,移动函数同理 ///这块讲的很差,但是就是那么个意思,总是表达不出来,就很烦 }
派生类可以给基类赋值或是初始化(也就是调用拷贝构造函数),就是把继承的那部分赋值给基类对象,中间有一个隐式变化,就是把这个派生类隐式转化为基类对象,暂时性的忽略派生类新增部分(后面有一个函数可以吧隐式转换为基类的派生类又转化为派生类)然后赋值给基类对象
(该处之前有错误,现已修正)
多继承所导致的二义性:一个派生类可以从多个基类中继承而来,那么着多个基类中若是有同名函数和形参表但函数体不同的话,派生类调用该函数时该如何取舍呢?
派生类对象调用该同名函数时加上类名(麻烦)(但是只有这个方法了,就算两个类的同名函数形参表不同也不行)
#include<bits/stdc++.h> using namespace std; class A { public: int f(){cout<<"A"<<endl;}; }; class B { public: int f(int x){cout<<"B"<<endl;}; }; class C:public A,public B { }; int main() { C ooo; ooo.f();///这样都是错的 }插一个我掉坑的题(输出程序运行结果)
#include<iostream> using namespace std; class A{ public: A(int anInt=0 ):i(anInt){ cout<<"A::A()"<<endl; } A(const A& anA){ cout<<"A::A(const A&)"<<endl; i=anA.i; } int getI() const{ return i; } ~A(){ cout<<"A::~A()"<<endl; } private: int i; }; class B{ public: B(){ cout<<"B::B()"<<endl; } B(const A& anA):a(anA){ cout<<"B::B(const A&)"<<endl; } virtual void f(){ cout<<"B::f()"<<endl; cout<<a.getI()<<endl; } virtual ~B(){ cout<<"B::~B()"<<endl; } private: A a; }; class D:public B{ public: D(){ cout<<"D::D()"<<endl; } D(const A& anA):a(anA){ cout<<"D::D(constA&)"<<endl; } void f(){ B::f(); cout<<"D::f()"<<endl; cout<<a.getI()<<endl; } ~D(){ cout<<"D::~D()"<<endl; } private: A a; }; int main (){ A a(10); B *pB=new D(a); pB->f(); delete pB; } ans: A::A() A::A() B::B() A::A(const A&) D::D(constA&) B::f() 0 D::f() 10 D::~D() A::~A() B::~B() A::~A() A::~A() 我少写了个第四行输出,在类D列表初始化的时候,给其私有成员类A a赋值,调用的是类A的拷贝构造函数突然还想到一个点,就是如果一个函数的形参表不是传类对象的引用,而是传类对象的值,传值的时候就会调用一次类的拷贝构造函数
第四章结束,别问组合,我也没看,我也没打,问就自杀
第五章 多态
同一基类的不同派生类继承的同一函数可以实现不同的版本(即执行不同的功能)
关键词virtual 在第四章中 virtual 是在继承时候使用(叫虚继承),第五章多态是在函数前加关键词virtual表示该函数可以在派生类中重写(叫虚函数),执行不同的功能,同时也可以用基类的指针或引用(必须是指针和引用),绑定派生类对象也可以调用或实现重写的虚函数(请记住那三个实现多态的必要条件,可能会考定义吧)
这里有一个很重要的虚函数表的概念,当定义了(或继承了)一个虚函数后,该类就会产生一个虚函数表,派生类继承的时候就会继承该虚函数表,并用重写的虚函数覆盖虚函数表中的同名同形参表函数,在基类指针(或引用)绑定派生类对象时,绑定的还是对象那块内存区域,调用虚函数时,会从该派生类的虚函数表中找,就是这样实现多态的。
但我个人还是喜欢定义一个虚函数,派生类新定义不同的版本,然后直接定义派生类对象就好,用基类指针或引用绑定派生类对象实现多态就无法访问派生类新增的数据成员或是函数
override和final函数:这两个函数都是对于虚函数而言的,不是虚函数的话调用这两个函数就是错的
final关键词:将该虚函数定义为最终版本,即该类的派生类只能继承这个函数,不能重定义(重写)也可以理解成final把这个虚函数又定义了普通函数;
override关键词:我更愿意理解为这是一个安全监测机制,如果该函数不是重写的虚函数程序就会报错
具体用法看书吧,上面就是一点我的理解
虚函数的特性:一个类定义了虚函数,即使他的派生类没有重写该函数,继承的该函数仍旧是虚函数,重写该虚函数,前加不加virtual关键词无区别;
虚析构函数:构造函数无法定义为虚函数,析构函数可以,和普通函数定义为虚函数用法一毛一样
(重点)纯虚函数与抽象类,纯虚函数的定义方式就是虚函数的大括号改为=0;表示定义了一个纯虚函数,拥有纯虚函数的类被称为抽象类,抽象类是不能定义对象的,他的派生类如果没有重写纯虚函数的话,也就是一个抽象类(没了,少不代表不重要)
(请记住这些,会考概念题)静态联编和动态联编都能实现多态,静态实现的多态叫做静态多态性(在编译时就确定了要调用的函数,执行速度快)动态实现的多态叫做动态多态性(在执行程序是从多个同名函数中匹配调用,(一般通过继承和虚函数实现)执行效率低,但是提供了更多的灵活性,问题的抽象性和程序的可维护性)课本p192
dynamic_cast是一个十分煞笔的强制类型转换操作符(我看的也蒙蔽了很久才看懂)在前面有讲过强制类型转换,向上转换,即派生类指针或引用可默认转换为基类指针和引用(虽然只能访问继承的部分,基类原有的部分)~~~~~~~~~~~~~~~~~~~~~~~~多态是通过基类的指针或引用绑定派生类对象就可以调用派生类的虚函数表从而实现一个函数多种实现,该操作符就是把这个绑定了派生类对象的指针或引用向下强制转换为派生类的指针或引用。课本p212
typeid(exp)返回exp的类型,在你使用了基类指针或引用绑定派生类对象时,也许对象太多,从而引发指针或引用的不确定性,这个函数就会返回绑定对象的类型(int,double。。。还有自定义的类)但是如果基类没有虚函数的话,基类指针就算绑定了派生类对象,返回的还是基类
补充个知识点,自己敲代码时发生的,就是基类重载输出流后,基类指针绑定派生类对象可以直接输出*p,但是如果不重载基类输出流,只重载派生类输出流的话,直接输出该基类指针是错误的,然后说下我的想法,就是指针保存的是内存地址,指向的是派生类的地址,所以才能调用派生类的虚函数表啊,*p应该是个派生类啊,可是为什么重载基类的输出流可以直接输出而重载派生类的不行呢??? (基类指针就是基类,只有使用虚函数的时候,才会调用派生类的虚函数表,也就是说想要重载派生类输出流直接输出派生类,你可以向下强制转换dynamic_cast吧基类指针转换为派生类指针,然后直接输出,虽然很有点脱裤子放P的嫌疑,但是不妨碍这操作骚啊,骚的一批)
第五章没写什么代码上去,多态不难的,而且那几个关键词啊,函数用的也不多,但是会考啊,关于虚函数表的概念多是理解,实在是不懂也行,虚函数的特性则是很重要的考试内容
第六章重载运算符(到了很有用的地方了,从这开始,就变得很难了呀)
先提下基本概念吧,就是你可以自定义各种操作符的规则,从而实现+是-的意思哟(例子而已)主要是为了实现自定义类的基本运算
举个sort快排结构体(类)的例子,自定义cmp排序规则(普通函数),还有类内部重载运算符后可以直接用sort,友元函数重载
#include<bits/stdc++.h> using namespace std; ///方法一 自定义sort自带的cmp排序规则(就是普通函数重载) const int N=2e5+7; struct node { int x,y; }a[N]; bool cmp(node a1,node a2) { return a1.x<a2.x; } int main() { int n;cin>>n; for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y; sort(a,a+n,cmp); } #include<bits/stdc++.h> using namespace std; ///方法二 类内部重载 const int N=2e5+7; struct node { int x,y; bool operator<(node &b) { return this->x<b.x; } }a[N]; int main() { int n;cin>>n; for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y; sort(a,a+n); } #include<bits/stdc++.h> using namespace std; ///方法三 友元函数重载 const int N=2e5+7; struct node { int x,y; friend bool operator<(node a1,node a2); }a[N]; bool operator<(node a1,node a2) { return a1.x<a2.x; } int main() { int n;cin>>n; for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y; sort(a,a+n); }
这个地方友元函数和普通函数很像,那是因为我用的是结构体,默认的成员为public,但是你要知道友元函数是可以直接访问类内部私有成员的,就算是类内部重载<号,this指针可以直接访问该对象的私有成员,但是要比较的那个呢?还得用接口,多麻烦,这也就是我推崇友元函数重载操作符的原因了
操作符分为一元操作符和二元操作符,根据需要几个操作数划分,([ ],()....)一元,(+,-,=....)二元
重载操作运算符分为三种方式(类内部重载,友元函数重载,普通函数重载)
其中友元函数和普通函数会比类内部重载操作符多一个操作数,因为类内自带一个this指针指向该对象所以少一个操作数。举个例子说明下吧,我更推崇的是友元函数重载,普通函数还要调用接口,太麻烦
#include<bits/stdc++.h> using namespace std; class node { int x,y; public: node(int a,int b):x(a),y(b){}///重载构造函数 int get_x(){return x;}///接口 int get_y(){return y;} node operator+(node s)///类内部重载 { this->x+=s.x; this->y+=s.y; return *this; } friend node operator+(node a1,node a2); } node operator+(node a1,node a2)///友元函数重载 { node a(a1.x+a2.x,a1.y+a2.y); return a; } node operator+(node a1,node a2)///普通函数重载 { node a(a1.get_x()+a2.get_x(),a1.get_y()+a2.get_y()); return a; }
这里的友元函数和普通函数一毛一样,但是请记住友元函数是可以直接访问类内部的,而
非类成员重载二元操作运算符
比如你重载了类对象的+规则,但是你还想让类对象直接加常数,C++会自动把该常数隐氏转换为类临时对象,然后使用该规则,其中使用类内部重载和其他方式重载还不一样,举例说明(用的就是书上的例子)
#include<bits/stdc++.h> using namespace std; class node { int x,y; public: node(int a=0,int b=0):x(a),y(b){}; int get_x(){return x;} int get_y(){return y;} node operator+(node &s) { this->x+=s.get_x(); this->y+=s.get_y(); return *this; } }; int main() { node a,b(1,1); a=b+2;///这是对的,2被隐式转换为了临时对象node(2,0)然后执行这个操作 a=2+b;///这是错的,但也只有类内部重载操作符它才是错的,因为第一个必定为this指向的,而类内部不会自动调用隐式转化 ///但是我的CB不支持这种隐式转换,毕竟连临时对象都不支持 }
重载一元操作符///感觉书上例子挺好的啊
提一点后面修改后程序运行结果发生变化的原因吧,就是自增,自减之类的操作符,用友元函数的话,因为是针对于该对象(重点)而言的,所以请使用传引用和返回引用的友元函数,顺便在这里建议下,学会用引用,写函数多用引用,毕竟引用比传值少了个赋值的时间啊
重载++,--特殊运算符(这是个难点,因为要测出所有东东很麻烦,我在测出来后,会放这部分)
其实并没什么难的,只是我自己老是分不清前自增和后自增的区别,结果就有点蒙蔽
书上用了友元函数重载和类内部重载两种方式实现了前自增和后自增,其中类内部重载和友元函数重载的区别还是少一个操作数(因为this指针的原因)其中为了区别前自增和后自增,在形参表中多加了个(,int)这个int并不需要传入参数,只是用于标记这是后自增,前自增表示先值增加后调用,所以在函数体内直接改值就可以了,但要记得使用引用传值和返回,而后自增则是先调用后改值,所以要在函数体内新建一个对象返回,然后传值得是引用,然后修改引用,返回新建对象的引用或者值都可以(这里无伤大雅了)请多想想为什么这样就实现了前自增和后自增之间的区别。所以就能得出后自增无论是返回引用或是值都可以的结论了。
还有++i和i++,i++多了一个新建对象的时间呢,就是这样,所以在一个循环内,++i确实是比i++快一丢丢的
先提下赋值吧,其实就是第三章的重载赋值运算符,没有的话类会自动合成一个赋值运算符,但是可能造成的问题就是指针悬挂(详情请看第三章整理的),其实这块第三章都有讲过,还有就是引用就是起了个别名,传值就是复制一份新的,所以很多时候可以自己多试试,多体会,理解下这二者的区别
[ ]运算符是实现下标查找功能的,只可在类内部定义,可出现在赋值=号左右两侧(左边赋值,右边调用呗),其实我们的数组就是重载了[ ]号的,我对其的理解是a[i]=*(a+i*sizeof(int)),map映射肯定也是重载过[ ]实现key值查找键值,而是自定义排序规则后还是二分查找呢,扯远了
书上举得例子就是映射下person类的名字和工资,然后通过遍历person类指针数组找匹配的名字,找到则返回,没找到则新增对象,挺简单的没什么说的,反而是我实验三的代码其中的那个重载[ ]我就觉得相当的有意思了,还有其中函数调用自己的重载函数,有兴趣看看那一篇博客中Report.h的实现,有丶东西的好伐
()类型转换操作符,前面有提到通过类的构造函数,可以把一个int类型的对象通过建立临时对象隐式转换为一个临时类,这里呢就可以重载()运算符把一个类可以临时转换为一种其他的数据类型进行赋值或是运算(建议多重定义的话为了避免二义性每次调用都加个(type数据类型)强制类型转换一个)感觉也不难啊,对了也只能类内部定义,基本用法就是operator type(){return type类型数据}
第七章(模板部分)想了想还是把第七章知识点放了进来,但是只有放模板部分啦,因为STL的东西实在是多的,背不过。
其实就是我们平时定义和使用函数和类的方式,就是加了个template关键词,改了下数据类型,就会出现很多种数据类型不同组合的函数
template关键词 使用方法:template<class a>要用几种类型的数据,尖括号内就放几种class(该处的class和typename是一样的,可相互替换) 我对这句话的理解是 声明一个模板, 其中会出现几种类型的数据,根据实际情况 模板实例化然后调用类或是函数
同类型值比较书上的例子比较简单和易懂,我提一下不同类型数据比较和返回auto类型数据的好处
template<class a1,class a2> a1 min1(a1 a,a2 b) { return (a<b)?a:b; }///刚开始我们容易写成上面这中格式,如果传入的是一个(int,double)类型的数据 ///double较小,因为返回类型此处是int,就会强制类型转化double为int丧失精度 template<class a1,class a2> auto min1(a1 a,a2 b) { return (a<b)?a:b; }///如果用auto作为返回类型的话,会自动返回精度最高的一种数据类型
当然模板类型参数也可以传入固定类型的参数,称之为非类型模板参数(用class或是typename限定的是类型模板参数)
非类型模板参数不可以传递变量,但是调用函数的时候可以向函数形参表中传入变量,因为那个时候是值传递,
这里我也没有看懂的是,为什么书上的自定义的选择排序sort为什么要用显示调用,而且是必须显示调用模板函数,我测试了下不含非类型模板参数的模板,运行ok,也就是说含非类型模板参数的模板只能用显示调用也就是<>存数据类型()放函数形参表
作用域找所在的大括号啊!!!
一个类的私有成员可以使不同类型的数据,即书上的stack栈的例子,有着存放不同类数据的栈,其中所有函数和成员除了类型或是返回类型不同其余都一样,就可以使用类模板函数了
和模板函数一样template表示声明一个模板类其中尖括号内表示存在多少种不同的数据类型,之后就和你写类的时候一样了,只不过不同是,把你常用的int,double什么之类的改成T(自己声明的数据类型)
几个不同的点
其中类模板的定义和实现不可以像普通类一样分开实现(就是我们写项目的时候,一个在.h一个在.cpp)
模板类成员函数在类外定义的时候(普通类是 类名::函数名)tepmlate<class T> 返回类型 类名<T>::函数名(){}
模板函数只有在调用的时候回根据你传入的数据类型生成实例化模板,就是调用函数->判断传入类型->找是否有已存在的实例化模板->有就调用没有就新建一个
类模板也是只有在调用的时候才实例化,有一点不同的是,该类中若是有模板函数,也是调用的时候才实例化,不用的模板函数还是模板(不可以指定为虚函数)
template<>尖括号内不加自己声明的类型,然后函数中直接使用限定的类型就是模板函数特化(完全不如普通函数)
就是书上的例子,既然都要重写一遍,写个普通函数不就完事了么,补充的普通函数和模板函数特化之间唯一的区别就是少了个template<> (所以说很蠢啊)
在需要调用该模板函数的时候就会沿着普通函数->模板函数特化->模板函数实例化的次序去找
对了,类模板调用的时候也是用的尖括号,不清楚为什么呀
内联函数真的只是在返回类型前加个inline关键词,意思就是调用该函数的时候直接用函数体去替换这个函数(据说能快一丢丢,谁知道呢)
第八章 异常处理
在学习这章之前,我以为这个可以检测很多错误,然后自定义处理类型,然而并不是
他只是对我们知道可能会发生的一些错误捕获然后自定义处理方式,我感觉就像是另一种if else结构
系统自带的也有异常处理机制,它的方法就比较简单粗暴,就是终止程序运行,并显示错误信息
异常处理的一般写法
#include<bits/stdc++.h> using namespace std; int main() { int n; cin>>n; try { if(n==0) throw 10;///抛出异常10 } catch(int)///捕获异常 {cout<<"???"<<h<<endl;} }有几点需要注意的,抛出的异常可以使任意数据的表达,包括类对象,必须至少要有一个catch异常捕获语句,否则程序无法运行,还得有所有抛出的异常处理,不然程序运行就会出错。还有类型匹配问题,只支持三种转换,1.常量转换为变量。2.派生类对象转换为基类对象。3.数组转换为指针(这个是真没差啦,都是地址储存的)
(我敲代码的时候发现支持10传值到n,但是不支持字符串传值到 char *p,必须要是char const *p;表示指针指向的常量)
异常捕获的处理机制是从上到下,只找完美匹配的,遇见后就执行处理语句,然后退出该try语句。
异常也可以重复抛出,就是在catch语句中最后加上一个throw就相当于将该异常重复抛出,从这个catch的下一个语句开始执行,如果没有匹配的catch语句,程序运行出错
比如上面那个例子,吧catch后面的int改为double CB不会报错,但是程序运行就会输出terminate called after throwing an instance of 'int',说没有int类型数据的异常处理
把int改为...的话表示该异常处理机制可以捕获任意的异常,一般放所有catch语句最后面,因为异常捕获机制是遇见就跳出
这个异常抛出调用函数的话,没差吧和普通函数,看看书就OK我觉得,而且也没什么要注意的点啊
noexcept应该是不考的,就像是多态里面的final overidle,这种关键词一样,放语句后,大括号前,表示不会抛出异常,这个关键词可以规避掉C++的自动检测机制,就像#define一样(所以后面出现了typename,左右值引用等等),所以就会有些不安全。
异常的嵌套使用,比我想象中的要复杂一些,目前刷的题也没见考这个的,我也不保证自己写的就很全面,下面只是我的一点点自己的理解,也就是该层抛出的异常如果没被处理,就会返回上一层函数内找catch语句,如果一直没有,程序就会报错,如果有就会执行该层函数内部语句,不继续执行最里面的那层函数接下来的语句部分,8.4的例子挺好的,也可以自己敲下戴氏随便乱试。
异常类
就是类也可以作为异常抛出,前面有说过,什么数据类型都可以抛出,当然包括自定义类和结构体了
书上的第一个例子用的是简单的类表示类可以作为异常抛出,第二个例子就是异常类里面加了点数据成员可以接收异常数据,捕获异常的时候就像函数一样,分为传值和传引用,
派生异常类的处理
就是前面提到的,派生类对象(无论是否是临时对象)可以匹配基类对象,这就是那一节书上例子要讲的,所以要注意异常捕获的语句次序问题,最内层的派生类放到最上面,
再提一下派生类继承基类同名函数,会直接覆盖所有同名函数(包括重载的),所以接下来书上就说,你可以用多态来实现这个函数,就不用多考虑次序问题了,因为所有的类都有唯一的一个虚函数表
就这些没了,这章我一点点感觉也没有,一点点想法也没有,我本以为可以自定义很多种东西的,既然写的时候就能想到这些错误,直接if-else语句就好了啊!就这样吧,这篇博客可能可以继续给学弟学妹们看呢,我会继续修改,微调,补充的