30小时快速精通C++拾遗
C语言不支持重载,为什么C++支持重载?
C语言不支持函数重载,编译时函数名加上_或者其他标识
C++为什么能够重载?
使用name decoration或者name mangling 技术,会对函数名结合参数类型个数顺序进行修饰,生成不同的函数名,本质上调用不同的函数,所以能够重载
C++可以调用C语言函数,C语言是不能调用C++的,C语言不支持面向对象怎么调用
参数可以是全局变量,函数指针,函数指针和block差不多.
指向函数的指针,函数指针作为默认参数
void test(){
}
void (*funcPtr)()=test;
函数指针作为参数.pnginline函数
不加inline会开辟一段栈空间给函数调用,加上inline会直接展开成函数体代码,减少函数调用的内存开销,代码的体积会增大
内联函数与宏定义的区别?
1.内联函数会做语法检测和函数特性(传递参数)
2.给函数分配的内存空间都是栈空间
面向对象3大特点:封装,继承,多态
封装:
不要把对象的成员变量直接暴露出去,让别人修改,所谓封装就是成员变量私有化,提供公共的getter和setter给外界去访问成员变量
应用场景:
比如:一个Person对象,里面有一个age属性,如果外面直接给他赋值一个负数怎么办?年龄哪有负数的,在面向对象里面成员变量直接被修改是不太好的一种做法,所以要封装把一些细节屏蔽起来不让别人知道
-(void)setAge(int age){
//增加一些过滤
if(age<0) return;
this->m_age =age;
}
//地址就是用指针存储的,释放的是内存地址,不是指针指向的内容
int *p=(int *)malloc(4);
free(p);
Person *p =new Person();
//能直接写成Person p =new Person();这样吗?
//不行,右边返回的是一块内存的地址,左边是存放的对象,内存地址不能直接赋值给对象
//申请字节
int *p1 =new int;
delete p1;
//数组
int *p2 =new int[10];
delete[] p2;
申请内存空间的时候,要做初始化,防止访问垃圾数据
int size =sizeof(int);
int *p=(int *)malloc(size);
memset(p,0,size);
//另外的初始化方法:new一块内存的时候要加上(),对内存空间进行初始化
int *p2 =new int(); //加上(),初始化为0
int *p3 =new int[3]; //数组未被初始化
int *p4 =new int[3]{}; //数组元素被初始化为0,数组的特点只要你写{},就会被初始化
全局区
//全局变量:写在函数之外就是全局变量
Person g_person; //(成员变量会初始化为0)
栈空间
Person p; //栈空间
Person person;//默认不会初始化
堆空间
Person *p=new Person();//在堆空间也会调用构造函数
Person *p1 = new Person;//成员变量没有初始化
Person *p2=new Person(); //成员变量有初始化
结论:如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认不会被初始化,需要开发人员手动初始化
重写:子类重写父类方法,原封不动拿过来
重载:函数名相同,参数类型,个数,顺序不同,重载目的是增加一些功能
构造函数
1.构造函数在对象创建的时候自动调用
2.一旦自定义了构造函数,必须用其中一个自定义的构造函数进行初始化
3.构造和析构要写上public,才能正常使用
初始化列表中的参数初始化顺序,只和成员变量的声明顺序有关,先声明先赋值
参数列表与变量声明顺序.png构造函数和构造函数之间的调用必须在初始化列表中才能成功!
不然在构造函数中,会生成一个临时对象,而不会传入this,只有在参数列表才会同一个this
[图片上传失败...(image-5b3821-1544432210272)]
语法:
默认参数只能写在函数的声明中
Person(int age=0,int height =0);
//如果函数的声明和实现是分离的,构造函数的初始化列表只能写在实现中
Person::Person(int age,int height) :m_age(age),m_height(height){
}
子类构造函数默认要调用父类的无参构造函数
构造函数调用构造函数要写在参数列表中.png调用父类的构造函数的目的就是要初始化父类里面的一些东西
子类构造函数调用父类构造函数,必须在初始化列表中调用
1.子类的构造函数默认就会调用父类的无参构造函数
2.如果子类的构造函数显示的调用父类的有参构造函数,就不会默认调用父类的无参构造函数
3.如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
多态:
1.同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
2.在运行时识别真正的对象类型, 叫做多态
3.父类的指针可以指向子类的对象,子类必须Public继承父类,才能用父类指针指向子类对象
C++中的多态通过虚函数(virtual function)来实现
1.虚函数:被virtual修饰的成员函数
2.只要父类声明为虚函数,子类中的重写函数也自动变成虚函数(也就是说子类可以省略virtual)
3.你想从哪个父类指针实现多态,就在哪个父类的函数开始使用virtual
虚函数表怎么实现的?
1.虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址
2.不是虚函数,汇编直接按照编译时的类型调用函数
3.一旦子类继承的父类有虚函数,子类的内存就会增加4个字节
4.牺牲内存空间实现的多态,每个对象前面4个字节放一张虚表,不就是牺牲内存空间吗,类是代码而已,不占空间,所以虚表地址是放在对象中,而不是类中
5.所有的Cat对象(不管在全局区,栈,堆)共用一张虚表,(对象中的4个字节不过是存储着虚表的地址而已,同一个类的对象虚表只有一张 )
6.虚函数表一般放在内存的只读区域,是不能修改的,存在全局区域
7.没有虚函数就不会有虚函数表,虚函数表里面存储着最终要调用的虚函数地址,非虚函数不会放在里面
8.如果子类对象没有重写父类的某个虚函数的话,那么子类的虚函数表里面存储的就是父类的那个虚函数地址,子类和父类的虚函数表指向两个不同的内存区域,没有继承关系
Animal *cat =new Cat();
cat->speak();
//cat存储着cat对象的地址值,根据cat对象地址值,找到存储空间,根据最前面4个字节的地址值,找到自己的虚表,找到子类对象调用的地址值,存在虚函数就要从虚函数表里面去找,具体去哪个虚表里面去找,就看指向哪个对象
每个类一张虚表,虚表没有继承,能够继承了还怎么实现多态
虚函数表.png纯虚函数
1.纯虚函数:没有函数体,初始化为0的虚函数.用来定义接口规范,规范就是参数,返回值
2.含有纯虚函数的类叫做抽象类,抽象类不能创建对象,被继承才有意义,只定义父类有哪些行为
3.抽象类也可以有非虚函数
4.如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类,有具体实现就不能抽象了
虚继承可以解决菱形继承问题,Person称为虚基类
![虚继承的内存结构.png](https://img.haomeiwen.com/i1062649/3f7166a89189a1f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)只要有菱形继承就要使用虚基类
多继承:
多继承:一个类实现多个接口(java)
一个类遵守多个协议(OC)
一个类继承于多个抽象类(C++)
static变量:
1.储存在全局区,只有一份内存
2.用static变量是为了控制它的作用域,只想在类中访问的全局变量,可以用static
3.必须初始化,不能在构造函数内部初始化,因为全局变量可以通过类名来访问,构造函数可能根本就不会调用,必须在类外初始化,并且不能带static
Class Car{
Public:
static int m_price;
}
//静态变量的初始化:
int Car::m_price =0;
//static是有作用域的,属于某个类的成员变量,如果是全局变量,其他地方会修改变量值
//普通成员变量的内存放在对象中,不能在静态成员函数中访问普通成员变量
静态成员的3中访问方式:
1.对象.成员变量
2.指针->成员变量
3.类名::成员变量
静态成员函数:
1.内部不能使用this,可以通过类名调用,不存在this
2.不能是虚函数,虚函数是实现多态用的,一个父类指针指向一个子类对象,再通过右边对象去调用相应函数,既然是虚函数就必须有对象,所以不能修饰虚函数
3.静态成员函数不能访问非静态成员函数,不能访问非静态成员变量,成员函数,成员变量本质上也是通过this指针作为参数在调用,通过类名调用静态成员函数时,不存在this指针
4,静态成员函数只能访问静态成员函数,静态成员变量
5.构造析构不能是静态,当声明和实现分离,实现部分不能带static
单例模式:
为什么单例模式中的实例对象不初始化为对象?
初始化成指针,在x86下只占4个字节,如果初始化为对象,对象有多大就占多少个字节,如果是静态变量会在全局区
1.构造函数私有化,保证外面无法创建对象
2.静态成员变量私有化,防止在外面创建(定义一个私有的成员变量指针,用于指向单例的对象)
3.提供一个公共静态成员函数,因为不允许别人创建对象,只能用类名调用,所以要提供静态函数
4.单例模式:要禁止掉复制构造函数
Rocket(const Rocket &rocket){} //私有化复制构造函数,防止生成多个实例
5.单例模式:要禁止掉赋值行为
void operator=(const Rocket &rocket){} //私有化赋值行为
class Rocket
{
//1.构造函数私有化
//2.定义指针而不是对象,定义静态成员变量,内存只有一份
//3.提供公有静态函数,返回一个指针
public:
static Rocket * shareRocket(){
if(ms_rocket==NULL)
{
ms_rocket = new Rocket();
}
return ms_rocket;
}
private:
static Rocket *ms_rocket;
Rocket() {
}
Rocket(const Rocket &rocket){} //私有化复制构造函数,防止生成多个实例
void operator=(const Rocket &rocket){} //私有化赋值行为
};
const成员变量:
必须初始化(类内部初始化),可以在声明的时候直接初始化赋值
非static的const成员变量还可以在初始化列表中初始化
const int m_price;
Car::m_price(0){
}
//不能在函数体内部修改当前调用对象的成员变量
void test1() const{
}
1.内部只能调用const成员函数和static成员函数,调用非静态成员函数
2.不能调用非静态成员函数的原因是:在非静态成员函数中修改成员变量
const Car car; //常量对象,不能修改成员变量
静态变量不能调用非静态成员函数,如果能调用的话意味着可能在非静态成员函数中修改成员变量,这和常量对象不能修改成员变量矛盾了
引用类型成员必须初始化(不考虑static情况)
1.在声明的时候初始化
int age;
int &m_price =age;
2.通过初始化列表初始化
Car(int &price) :m_price(price){}
拷贝构造函数(copy constructor)
利用已经存在的对象创建一个新对象(类似于)copy,就会调用新对象的拷贝构造函数进行初始化
拷贝构造函数(格式是固定的)
Car(const Car&car){
}
Car car2;
Car car3(car2); //利用car2对象创建一个car3对象出来,会调用car3对象的拷贝构造函数进行初始化
Car car3(car2); 等价于Car car3 =car2;
//非拷贝构造函数
Car car3;
car3 =car2; //赋值操作,不是拷贝构造函数,两个对象都不是新对象,拷贝构造是利用已存在的对象创建一个新对象
编译器默认提供的拷贝是浅拷贝,这样存在多次free的问题
1.将一个对象中所有成员变量的值拷贝到另一个对象中
2.如果某个成员变量是指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
3.这样可能会导致堆空间多次free的问题
如果需要实现深拷贝,就需要自定义拷贝构造函数
1.将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
隐式构造函数:
//C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数
Person(int age);
Person p =20;隐式构造,等同于Person person(20);
//可以通过关键字explicit禁止调用隐式构造函数
explicit Person(int age);
编译器自动生成的构造函数:
编译器在以下几种情况下,会给类生成无参的构造函数
1.成员变量在声明的同时进行了初始化
2.有定义虚函数
3.虚继承其他类
4.包含了对象类型的成员,且每个成员有构造函数
5.父类有构造函数
总结:对象创建后需要做一些额外的操作(比如内存操作,函数调用),编译器一般会为其生成构造函数
友元函数:
一旦一个函数声明为一个类的友元函数,那么这个函数就能访问,对象的成员变量
Class Point{
friend Point add(const Point &,const Point &); //友元函数可以声明在类里面的任何地方,访问权限Public,Private不对友元函数产生影响
}
不能只挑选某个类的某个函数成为指定类的友元函数,但可以让某个类成为指定类的友元类
friend Math::Point add(const Point &,const Point &); //不能挑选Math类的某个函数成为友元函数
friend class Math;
友元破坏了面向对象的封装性,但某些频繁访问成员变量的地方可以提高性能
基础知识:
1.在Java的世界里,先有类,再有函数,可以有多个main函数,在C++里面可以直接写函数没有类,只能有一个main函数
2.cd :change directory
3.VS提示时敲回车不会直接补全,敲tab键才会
4..hpp //.h+ .cpp 既可以放头文件,又可以放实现文件
5.像java,oc,调用父类的函数用super关键字,C++直接SuperClass::method();
6.回收内存的意思是,这一块内存可以被重新利用了,交出使用权限,这块内存的数据并没有被清除
7.Class默认是私有继承,成员变量私有访问权限,Struct默认是Public继承,成员变量Public访问权限
8.C++:静态成员函数相当于Java,OC:类方法
封装心法:
1.写完代码要检查自己的类名,方法名,参数类型,看看哪一块还有没有可以重构的地方,别人怎么用着方便
2.你作为程序员,你要想想你要做的事情能不能通过写代码去做,怎么窥探内存,怎么用汇编去看代码