C++

30小时快速精通C++拾遗

2018-12-10  本文已影响80人  zhouluyao

C语言不支持重载,为什么C++支持重载?

C语言不支持函数重载,编译时函数名加上_或者其他标识

C++为什么能够重载?

使用name decoration或者name mangling 技术,会对函数名结合参数类型个数顺序进行修饰,生成不同的函数名,本质上调用不同的函数,所以能够重载

C++可以调用C语言函数,C语言是不能调用C++的,C语言不支持面向对象怎么调用

参数可以是全局变量,函数指针,函数指针和block差不多.

指向函数的指针,函数指针作为默认参数

void test(){

}

void (*funcPtr)()=test;

函数指针作为参数.png

inline函数

不加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.你作为程序员,你要想想你要做的事情能不能通过写代码去做,怎么窥探内存,怎么用汇编去看代码

上一篇下一篇

猜你喜欢

热点阅读