没啥可以讲的,避免了类名字,方法,成员变量的冲突。
变量可以边定义边用。
int a; int a=8;会报错了,不像c 不会报错。
c++的struct 类似于class ,唯一的不同是class定义的函数默认是private 的,但是struct 定义的函数是public 的。而且,c不认为这是一种新类型,仍然需要struct AAA,进行定义,但是c++则不需要,直接使用AAA 进行定义对象。即认为是一种新的类型。
如下代码在c语言是OK的,但是在c++就不行咯。
f(i) { printf("i = %d\n", i); } g() { return 5; }新增bool 类型,返回值为true 和false
返回的是引用,而不是值,所以可以对其进行赋值。
可以分清输入与输出的参数。带const往往是输入。在c语言中,const 是冒牌货。 c++的const由编译器处理,提供类型检查与作用于检查。 #define由预处理器处理,单纯的文本替换。 c++的const 可能分配空间,也可能不分配空间。 当const 常量是全局的,并且需要在其他文件使用。会分配空间。 当使用&操作符。取const 地址,会分配空间。 const int &a=10; const 修饰引用,也会分配空间。
Type& name = var; 引用的规则。 1.引用没有定义,是一种关系声明,声明和他原有的某一个变量关系,故不分配 内存,与被引用变量有着同样的指向地址。 2.声明的时候,必须得初始化,一经声明,不能改变。 引用的本质
#include <iostream> int main() { int a = 10; int &b = a; // 注意: 单独定义的引⽤用时,必须初始化。 b = 11; printf("a:%d\n", a); printf("b:%d\n", b); printf("&a:%p\n", &a); printf("&b:%p\n", &b); return 0; }引用在c++中的内部实现是一个常指针,即一个指向不能改变的指针。 Type& name <===> Type* const name c++在编译过程中。使用常指针作为内部实现。因此,引用所占用的空间与指针大小相同。 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这个是c++为了实用性,而做出的细节隐藏。
void func(int &a) { a = 5; } void func(int *const a) { *a = 5; }间接赋值的3各必要条件 1定义两个变量 (一个实参一个形参) 2建立关联 实参取地址传给形参 3*p形参去间接的修改实参的值. 引用的实现上。只不过是把,间接赋值成立的三个条件的后面2个合二为一了。当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工。取了一个实参的地址,传给了一个常引用。(常量指针)
引用作为函数的返回值(引用当左值) 当函数返回值为引用时候。若返回栈变量,不能成为其他引用的初始值。(不能作为左值引用。) 当函数返回值为引用时,如果返回静态变量或全局变量,可以成为其他引用的初始值。可以作为右值,也可以成为左值。
什么是引用当左值?
int& fun(){ int a=10; return a; } fun()=10; //如下的只是值拷贝。 int x=fun();const对象的引用必须是const的,将普通引用绑定到const 对象是不合法的。如下。
const int a=1; int &b=a; //常引用在初始化其实变成了2个步骤 int temp = val; const int &ref = temp; 这也是为什么 int &a=1;//是错误的 const int &a=1;//却是正确的的原因。结论: 1.const int &a;相当于const int * const a; 2.普通引用相当于int * const e; 3.当使用const对应用初始化的时候,c++会为常量值分配空间,并将引用名作为这段空间的别名。 4.使用字面量对const引用初始化后,将生成一个只读的变量。
可以想是宏函数的拓展,内联的特点。 1.没有函数调用的开销。(压栈,跳转,返回) 2.内联函数由编译器处理,直接将编译后的代码插入调用的地方。宏片段由预处理器处理,进行简单的文本替换,没有任何的编译过程。 同时存在着一定的限制。
不能存在任何形式的循环语句不能存在过多的条件判断语句函数体不能过于庞大不能对函数进行取址操作函数内联声明必须在调用语句之前当函数体执行的开销远远大与压栈,跳转,返回的开销,内联将失去意义
C++利用 name mangling(倾轧)技术,来改名函数名,区分参数不同的同 名函数。 实现原理:用 v c i f l d 表示 void char int float long double 及其引 用。 每一个方法都有一个特殊的Symbol 符号。
Test t1(10); Test t2=t1;
Test t1(10); Test t2(t1);//使用对象t1初始化对象t2
1.函数返回值是一个元素(复杂类型),返回的是一个新的匿名对象(所以会调用匿名对象的copy构造函数)
2.有关匿名对象的去和留 如果匿名对象去初始化另外一个同类型的对象,匿名对象被扶正 如果用匿名对象去赋值给另外一个同类型的对象,匿名对象被析构
注意,new 与delete 返回的都是指针类型。是一块内存地址。不能够用普通对象直接接收,与malloc delete 有着同样的功能,唯一的不同是,malloc free在初始化类对象的时候,是不会自动的帮我们调用构造函数,但是new 与delete 是会自动的帮我们调用。
普通成员变量,存储于对象中,与struct变量有着相同的内存布局和字节对齐方式。 静态成员变量,存储于全局数据区 成员函数,存储于代码区
很多对象共用一块代码,代码如何区分具体对象是那? 如下的图应该算是很经典的一张图了。
c++的普通成员函数都隐含一个包含当前对象的this指针 静态成员函数不包含指向具体对象的指针。普通成员函数含有一个指向具体对象的指针。
友元声明以关键字friend开始,只能在类定义中出现,因为不是授权类的成员,所以他不受类声明的public private 和protected的影响 利弊:可以直接访问类中的私有成员,破坏了类的封装性和隐蔽性。但是提高了编码的灵活性。 注意事项 友元关系无法继承 友元关系无法交换 友元关系不具有传递性
执行重载的时候,记得对参数加const
双目运算符重载
//使⽤用: L#R operator#(L,R); //全局函数 L.operator#(R); //成员函数 Complex& operator+=(const Complex &c) { this->_x += c._x; this->_y += c._y; return * this; }重载规则请务必记住
//重载+号 (全局) friend const Complex operator+(const Complex &c1,const Complex &c2); //重载+号(成员) const Complex operator+(const Complex &another); //a+=b;对于a应该返回的还是原来的a 所以不能为const Complex& operator+=(const Complex &c) //a-=b;同样的是返回可以修改的内容。 friend Complex& operator-=(Complex &c1, const Complex & c2); //前置++ 注意返回的是引用 friend Complex & operator++(Complex& c); friend const Complex operator++(Complex &c,int); friend ostream & operator<<(ostream &os, const Complex & c); friend istream & operator>>(istream &is, Complex &c); //赋值运算符重载 返回的是引用,不能用const 修饰,其目的是连等式 A& operator=(const A& another) //数组符号的重载 类型 类 :: operator[] ( 类型 ) ;结论: 1,一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函 数定义为谁的友元,谁的成员问题。 2,一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左 操作数)。 3,一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右 操作数)。
重载&& 和||会丢失短路特性。
class 派⽣生类名:[继承⽅方式] 基类名 { 派⽣生类成员声明; };
private成员在子类中依然存在,但是却无法访问到。不论何种方式继承 基类,派生类都不能直接使用基类的私有成员 。
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生 类 中,而基类的私有成员不可访问。
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成 员身 份出现在派生类中,而基类的私有成员在派生类中不可访问。
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生 类中 不变,而基类的私有成员不可访问。
不论是什么类型的继承,在基类中不能使用的,只有私有成员,其余就算因为私有继承而退化为私有的,子类也是依然可以进行使用 的。
1.子类对象在创建时会先调用父类构造函数 2.父类构造函数执行结束后,执行子类构造函数 3.当父类构造函数有参数时,需要子类初始化列表中显式调用 4.析构函数的调用顺序与构造函数刚好相反。
先构造父类,再构造成员变量,最后再构造自己 先析构自己,再析构成员变量,最后析构父类
1、当子类成员变量与父类成员变量同名时 2、子类依然从父类继承同名成员 3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基 类的同名成员,显式地使用类名限定符) 4、同名成员存储在内存中的不同位置
cout<<c.age<<endl; cout<<c.Parent::age<<endl; cout<<c.Child::age<<endl;基类定义的静态成员,将被所有派生类共享
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具 有不同的访问性质 (遵守派生类的访问控制)
派生类中访问静态成员,用以下形式显式说明: 类名::成员、对象名 . 成员
静态成员变量,无论使用父类名,还是子类名都可以直接调用得到。或者是实例,也可以调用。
cout<<c.count<<endl; cout<<Parent::count; Parent::count++; cout<<Child::count; Child::count++; cout<<Object::count; Object::count++; cout<<Object::count;一般情况下,如果基类与子类有着同名的方法,即子类重写了父类的方法。如果使用父类指针去承接子类对象,那么调用被重写的方法的时候,是执行父类的方法,而不是子类的方法。 如何实现,根据对象实际类型去打印?virtual 可以解决。通过使用virtual 修饰函数。可以视作是java中的abstract 抽象方法,不过这个virutal方法在是可以有方法体的。
virtual int fun()多态成立的3个条件。 1.要有继承 2.要有虚函数重写 3.要有父类指针(父类引用)指向子类对象。
静态联编,由于程序没有运行,所以不可能知道父类指针指向的具体对象是父类对象还是子类对象,从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译结果为调用父类的成员函数。
多态是发生在动态联编,是在程序执行的时候,判断父类指针应该调用的方法。
虚析构函数用于指引delete操作符正确析构动态对象。 在手动delete obj时候,如果是父类指针,没有写virutal 析构,那么只会执行父类的析构函数代码,而不会执行子类的析构函数。
函数重载 必须在同一个类中进行,子类无法重载父类的函数,父类同名函数将被名称覆盖,重载是在编译期间根据参数类型和个数决定函数调用
//以下都是重载 void a(); void a(int) void a(char) void a(int,char)函数重写
必须发生在父类与子类之间 ,并且父类与子类必须有完全相同的原型 使用virtual声明之后能够产生多态(如果不使用virutal ,那就叫重定义)
virtual void fun(); --> void fun();//多态如果没有virutal 那就是重定义1.当类声明虚函数时,编译器会生成一个虚函数表 2.虚函数表是一个存储类成员函数指针 的数据结构 3.虚函数表是 编译器自动生成和维护的。 4.virtual 成员函数会被编译进入虚函数表中。 5.存在虚函数的时候,每个对象中都有一个指向虚函数表的指(vptr指针。)
编译器确定func 是不是虚函数 1)不是,编译器可直接确定被调用的成员函数(静态联编,根据父类类型决定)
2) 是,编译器根据对象的vptr指针,所指向虚函数表查询到func 函数并且调用。注意,查找与调用是在运行的时候执行
由于虚函数是在运行的时候动态进行调用。效率低是一个问题。构造函数无法实现多态,父类构造的时候,vptr 指向父类的虚函数表,子类构造时候,指向子类。
base(){ this->print()///调用的是父类的print方法 } child(){ this->print()//调用的是子类的print 方法 }virtual 类型 函数名(参数表) = 0;类似于java 的抽象方法。主要特点是无方法体。子类继承必须实现,否则子类也是抽象类’
1.含有纯虚函数的类,称为抽象基类,不可实列化。即不能创建对象,存在 的意义 就是被继承,提供族类的公共接口。 2,纯虚函数只有声明,没有实现,被“初始化”为 0。 3,如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在 派生类中仍然为纯虚函数,派生类仍然为纯虚基类。
class Triangle:public Shape{ protected: double w; double h; public: Triangle(double w,double h):w(w),h(h){} virtual double getArea() { return w*h/2; }; }; class Rectangle:public Triangle{ public: Rectangle(double w,double h):Triangle(w,h){ } double getArea(){ //可以调用到父类方法,只不过需要加上作用范围。 return this->Triangle::getArea()*2; } };c 语言通过函数指针实现面向接口编程
如果单独把代码写在h文件里面,此时需要注意的细节会比较少,但是如果需要分文件,即使h cpp 如下只是重载了<<运算符。
friend ostream& operator<< <T>(ostream &o,Animal<T> t); //此时,在h文件头我们需要做如下声明 template<class T> class Animal; template<class T> ostream& operator<<(ostream &o,Animal<T> t); //同时,我们需要在cpp文件里面完善代码。 template<class T> ostream& operator<<(ostream &o,Animal<T> t){ o<<t.t; return o; } //最后,在main里面,别忘记了,要引入我们的cpp文件。所以这也是为什么我们对于这些模板类的编写,一般就用一个文件hpp,我们不把代码分离开,因为如果分开用户在使用的时候,还需要手工引入hpp,多麻烦啊。所以干脆写在一个文件里面算了。
一般性结论 friend ostream & operator<< <T> (ostream &os, Complex<T> &c); //在模板类中 如果有友元重载操作符<<或者>>需要 在 operator<< 和 参数列表之间 //加⼊入 //滥⽤用友元函数,本来可以当成员函数,却要⽤用友元函数 //如果说是⾮非<< >> 在模板类中当友元函数 //在这个模板类 之前声明这个函数 friend Complex<T> mySub <T>(Complex<T> &one, Complex<T> &another); //最终的结论, 模板类 不要轻易写友元函数, 要写的 就写<< 和>> 。
传说中的hpp
C++风格的类型转换提供了 4 种类型转换操作符来应对不同场合的应用。 static_cast 静态类型转换。如 int 转换成 char reinterpreter_cast 重新解释类型 dynamic_cast 命名上理解是动态类型转换。如子类和父类之间的多态类型转换。 const_cast, 字面上理解就是去 const 属性。
由于二次编译,模板类在.h在第一次编译之后,并没有最终确定类的具 体实现,只是编译器的词法校验和分析。在第二次确定类的具体实现后,是 在.hpp文件生成的最后的具体类,所以main函数需要引入.hpp文件。 综上:引入hpp文件一说也是曲线救国之计,所以实现模板方法建议在同 一个文件.h中完成
捕捉万能异常?
//设置未知异常处理回调函数 set_terminate(my_tm_h); //typedef void (*terminate_handler)(); //编写回调函数 void my_tm_h(){ cout<<"error occure with clear reason!"<<endl; }统一的异常处理机制,将让我们捕捉异常的时候,更加方便。能够进行统一的控制操作。
cin.get() //一次只能读取一个字符 cin.get(一个参数) //读一个字符 cin.get(三个参数) //可以读字符串 cin.getline() //获取缓冲区的一行 cin.ignore() //跳过缓冲区几个字符 cin.peek() //查看缓冲区有没有数据,阻塞 cin.putback() //塞进去缓冲区 ctrl-z 将产生一个EOF,mac 测试无效。
控制符,可以直接cout<<控制符,也就是追加
控制符作用endl不做解释dec设置数字基数为10进制hex16进制oct8进制setfill(’*’)填充字符,一般与setw 配合使用,一次性用,endl作废setprecision(n)设置浮点数字的输出精度,多次有效setw(12)设置字段宽度,本次有效。仅仅数字不足才补足,一旦设置setprecision将优先填充到precision 的位数setiosflags(ios::a|ios::b…)可以设置标志位resetiosflags(ios::a|ios::b…)重置io标志如下方法属于cout成员,使用cout.method进行调用即可。
函数功能precision(n)设置精度width(n)设置字符宽度fill©设置填充字符setf()设置ios标志unsetf()取消设置ios标志引入头文件
#include <fstream>文件的输入输出方式控制
常见的容器有如下的几种 vector list deque set map stack queue
如何写一个标准的替换字符串函数
string str="jflkasfijfamsfkajsaf"; string find="j"; string rep="*"; int pos=0; while((pos=str.find(find,pos))!=-1){ str.replace(pos,find.length(),rep); pos+=rep.length(); }与vector 不同在于,deque 可以push_front,即双端操作。pop_front()可以取除第一个元素
支持push 与pop ,通过top 获取元素
队列,pop时候,只会把队头元素抛出。支持front 与end
不可随机存储读写,但是支持插入?支持push pop back front
总结一句话,能够用下标获取,其底层机构一定是数组,如果无法,但是可以通过不断的++ ,那他一定是一条链路。 各个容器的使用正确时机。
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头 端的快速移除,尾端的快速添加。如果采用 vector,则头端移除时,会移动大量的 数据,速度慢。
vector 与 deque 的比较:一:vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置却 是不固定的。二:如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。三:deque 支持头部的快速插入与快速移除,这是 deque 的优点。list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实 位置元素的移除插入。set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的 顺序排列。map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用 户。二叉树的查找效率,这时就体现出来了。如果是 vector 容器,最坏的情况下可 能要遍历完整个容器才能找到该用户。