1、函数重载
-
为什么要重载函数?
可以用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强。
-
构成函数重载的条件:在同一个作用域中,可有一组具有相同函数名,不同参数列表的函数,这组函数被称为函数重载。 不同参数列表。
-
函数参数个数不同
-
函数参数类型不同
-
函数参数类型顺序不同
-
-
为什么能函数重载?
c++的函数重载实现的本质是:c++编译器采用了叫做name mangling技术,编译器根据参数类型的不同将函数名改成不同的函数名,也就是底层函数名字不同,然后根据参数类型找到对应的函数进行调用
2、extern C 作用
-
extern c用法
-
extern ”C“ void func() {},修饰函数,编译器将按照c语言方式编译
-
extern "C"修饰声明的函数,函数实现可以不修饰
-
第三方框架:可能是c语言编写,此时c++要调用c语言的代码,而此时是在c++环境,就需要将第三方框架的代码的声明加上extern "C",一般是加在第三方框架的.h文件,但是此时如果.c文件也想使用第三方库就是用不了了,因为此时是.c文件是c环境,根本不认识第三方库的extern "C" 关键字,因此要解决的问题是希望在c++环境中使用extern "c"关键字,在c环境中不使用extern "c"关键字。因为c++文件的第一行代码中有一条隐形的宏
#define __cplusplus
而c文件中没有这条宏,因此,只需要在第三方库中判断此宏是否存在,如果存在就加上extern "C"
不存在就不加,完美解决c与c++代码混合开发。 -
#pragma once
关键字保证头文件内容重复包含 -
头文件的本质就是将头文件的内容原封不同的复制包含头文件的位置,重复包含头文件将增加代码量
-
3、内联函数
-
什么是内联函数?
-
inline
关键字修饰一个函数,此时这个函数将成为一个内联函数
-
-
内联函数本质
-
内敛函数将函数调用直接展开为函数体调用,就是直接将被调用的函数内容复制到被调用的地方,通过汇编代码可以发现内敛与非内敛函数调用的区别。
-
-
内联函数优点
-
函数调用是需要开辟栈空间的,函数结束回收掉栈空间,而变成内敛函数时,将不会调用函数,而是直接将函数的内容复制到被调用的地方直接执行
-
-
内联函数何时使用?
-
代码体积小,而又被频繁使用,少了频繁的开辟栈空间的开销,提升代码运行效率。
-
递归函数不被内敛
-
4、类和对象
-
类的创建
-
c++中的类有两种创建方法,第一种直接使用
class
关键字创建,第二种是struct
关键字,两种区别是前者默认成员变量是public,后者是private,其他别无区别。
-
-
通过类实例化对象
-
类名加对象名
class Player { public: int m_x; int m_y; int m_speed; Player():Player(0,0,0){} Player(int x,int y,int speed): m_x(x),m_y(y),m_speed(speed){} void Move(int x, int y) { m_x += x * m_speed; m_y += y * m_speed; } }; void test() { Player plear;//实例化对象 }
-
-
对象的内存
-
对象的内存大小等于类中成员变量大小,对象的地址值等于对象中第一个成员变量的地址值,与C语言结构体类似,函数所站的内存并不在对象里面,类中的成员函数放在代码区
-
-
this指针
当使用同一个类创建不同的对象,然后通过不同对象调用成员函数时,会隐形的传入进去一个this指针,这个指针指向对象的地址,通过这个地址就能实现不同对象调用同一个函数,函数能够访问到该对象的成员
5、构造函数
-
构造函数创建
-
在类内创建构造函数,直接使用类名,没有返回值
-
在类外创建构造函数
-
-
构造函数用途
-
构造函数在每次创建对象时自动调用,实现将成员变量初始化的操作,构造函数可以重载,构造函数初始化支持初始化列表操作。
-
一旦自定义了构造函数,必须使用一个构造函数来初始化对象。
-
可以创建拷贝构造函数,通过传入对象指针来将另外一个对象的内容拷贝到新的对象
classname (const classname &obj)
-
6、析构函数
-
析构函数作用:
-
对象被销毁时自动调用,完成对象的清理工作,清楚内部new的堆空间
-
-
析构函数特点:
-
不能重载,与类名相同,无返回值
-
-
注意点:
构造函数与析构函数必须为public,因为每当创建一个对象的时候都会调用一次构造函数,如果为private怎么调???
-
析构函数用法
class car { public: car():car(100,"black","tuolaji"){} car(int price, string color, string name) :m_price(price), m_color(color), m_name(name) {} ~car() { std::cout << "car :: ~car(){}" << std::endl; } private: int m_price; string m_color; string m_name; }; class Person { public: Person() { m_age = 21; m_weight = 135; m_name = "tzy"; m_car = new car();//当没有默认构造函数时,不加括号不初始化成员变量,加括号初始化成员变量 } ~Person() { std::cout << "Person :: ~Person(){}" << std::endl; delete m_car;//清理掉对空间 } private: car* m_car; int m_age; int m_weight; string m_name; }; void test() { Person person; }
7、命名空间
-
命名空间作用
-
解决命名冲突
-
命名空间不影响内存布局
-
namespace persim { int g_age;//全局区 class CellularList { public: int m_age; int m_name; }; } class CellularList { public: int m_age; int m_name; }; void test() { CellularList person;//使用的不是命名空间的类 CellularList::Person person;//使用persim空间的CellularList类 using namespace persim;//加上这个代表下面所有的用到persim空间里面的东西的都不用加persim::前缀 CellularList person; }
-
8、继承
-
继承的作用
-
继承可以让子类拥有父类的所有成员变量\函数
-
通过对父类的继承实现各种各样的类,是实现面向对象的重要特性
-
-
继承的使用
-
继承的本质是直接将父类的成员变量\函数拿到自己的类中来使用
-
如果子类继承父类的方式是private,不允许父类指针指向子类对象
-
-
子类内存分布
-
父类的成员变量的地址在子类成员变量的地址之上,从父类继承的成员变量排在最前面
-
-
成员的访问权限
-
父类的成员访问权限与继承父类的方法有关,当继承父类的方法为public,子类可以使用父类中public的成员变量\函数,当继承的方式为private,子类不能访问父类的成员函数与变量,继承方式为protected,子类可以访问父类的成员函数与成员变量。最终还要看父类中成员函数与变量的属性
-
-
父类的构造函数
-
默认情况通过子类创建一个对象时,会先调用父类中的构造函数,然后在调用自己的构造函数
-
如果子类中的构造函数主动的调用了父类中的有参构造函数,则就不会调用父类的无参构造函数。构造函数调用构造函数只能写在初始化列表里
-
-
析构函数
-
子类创建对象被销毁时,先调用子类析构,然后调用父类析构
-
-
多继承
-
一个子类继承多个父类
class worker { public: int m_age; int m_salary; void run(){} } class student { public: int m_age; int m_score; void run(){} } class undergraduates:public worker,public student { public: int m_age; int m_ability; void run(){} } void test() { undergraduates ug; ug.student::run(); ug.worker::run();//同名函数调用父类 ug.student::m_age = 10;//同名变量 ug.worker::m_age = 10; }
-
-
菱形继承
//间接基类A class A{ protected: int m_a; }; //直接基类B class B: public A{ protected: int m_b; }; //直接基类C class C: public A{ protected: int m_c; }; //派生类D class D: public B, public C{ public: void seta(int a){ m_a = a; } //命名冲突 void setb(int b){ m_b = b; } //正确 void setc(int c){ m_c = c; } //正确 void setd(int d){ m_d = d; } //正确 void setd(int d){B:: m_d = d; }//正确 void setd(int d){C:: m_d = d; }//正确 private: int m_d; }; int main(){ D d; return 0; }
-
虚继承
虚继承解决菱形继承带来的不能访问父类成员变量的问题
//间接基类A class A{ protected: int m_a; }; //直接基类B class B: virtual public A{ //虚继承 protected: int m_b; }; //直接基类C class C: virtual public A{ //虚继承 protected: int m_c; }; //派生类D class D: public B, public C{ public: void seta(int a){ m_a = a; } //正确 void setb(int b){ m_b = b; } //正确 void setc(int c){ m_c = c; } //正确 void setd(int d){ m_d = d; } //正确 private: int m_d; }; int main(){ D d; return 0; }
-
虚继承内存分布
虚指针,具体细节不深究了
-
多继承应用
-
9、多态
-
多态理解
-
默认情况下,编译器只会根据指针类型调用相应的函数,不存在多态
-
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
-
在运行时,可以识别出真正的对象类型,调用对应子类中的函数
-
操作同于函数,通过传入不同的对象,执行不同对象的成员变量与函数
-
要想实现多态,必须引入虚函数来实现,通过
virtual
关键字来修饰函数,只要父类里面函数是虚函数,子类重写父类的函数,子类中的函数也是虚函数 -
class Animal { public: Animal():Animal(0,0,0){} Animal(int age,int color,int addr):m_age(age),m_color(color),m_addr(addr) { std::cout << "构造函数" << std::endl; } virtual ~Animal() { std::cout << "Animal::析构" << std::endl; } virtual void run() { std::cout << "Animal run" << std::endl; } virtual void speak() { std::cout << "Animal speak" << std::endl; } int m_addr; private: int m_age; int m_color; }; class cat :public Animal { public: int m_ad; cat() { m_ad = 0; std::cout << "cat::构造" << std::endl; } ~cat() { std::cout << "cat::析构" << std::endl; } void run() { std::cout << "Cat run" << std::endl; } void speak() { std::cout << "Cat speak" << std::endl; } }; void test() { Animal* p = new cat(); p->run();//此时指向的是cat对象中的run, }
-
实现原理:当父类中有虚函数时,子类继承父类时,编译器会自动在子类对象的起始地址添加四个字节,这四个字节存放的是一张虚表的地址,虚表中存放的是子类中重写的父类的函数地址,当父类指针调用子类的成员函数时,编译器会将子类重写父类的函数地址返回(具体细节可查看汇编,总之是根据4字节的地址找到虚表,然后调用虚表中存放的函数地址),因此将会调用到子类对象的成员函数。C++ 通过虚函数的机制可以实现各种巧妙地设计模式,是实现各种框架的核心机制。
-
虚表细节
-
当 new 同一个对象时,这个对象中虚表的地址是一样的,因为成员函数是放在 text 段的,属于代码区 。
-
-
调用父类的成员函数
子类的成员函数调用父类的成员函数直接通过
父类名::父类成员函数名
-
虚析构函数
父类的析构函数加上
virtual
关键字,子类对象销毁时,会先调用自己析构,在调用父类析构 -
纯虚函数
没有函数实现的虚函数,实现由子类来实现
class Animal { public: //Animal(int age, int color) : m_age(age), m_color(color) {} Animal():Animal(0,0,0){} Animal(int age,int color,int addr):m_age(age),m_color(color),m_addr(addr) { std::cout << "构造函数" << std::endl; } virtual ~Animal() { std::cout << "Animal::析构" << std::endl; } virtual void run() = 0;//纯虚函数 virtual void speak() = 0; int m_addr; private: int m_age; int m_color; };
实现由子类来实现,用来定义接口规范,就是相当于声明函数,实现由子类来实现。
-
抽象类
-
含有纯虚函数的类叫做抽象类,不能用抽象类实例化对象,只能通过其子类对象来实例化对象
-
抽象类也可以包含非纯虚函数与成员变量
-
如果父类是抽象类,子类没有完全实现纯虚函数,那么子类依然是抽象类
-
-
-
父类指针指向子类对象
-
父类指针指向子类对象只能访问到父类中的成员变量\函数
-
此操作是安全的,不能用子类指针指向父类对象
-
虚函数实现多态
-
10、静态成员static
-
静态成员:被
static
修饰的成员变量\函数 -
可以通过对象(对象.静态成员)、对象指针(对象->静态成员)、类访问(类名::静态成员)访问
-
静态成员变量存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存
-
可以设定访问权限(public类内外都可访问,private:只有类内可以访问,子类也不能访问,protected:类内与子类可访问)
-
必须初始化,必须在类外面初始化,初始化时不能带static
-
静态成员函数内部没有
this
指针,this
指针存储的是对象地址,静态成员函数内部不能直接访问非静态成员函数\变量,静态成员函数内部可以访问静态成员变量与静态成员函数。 -
本质上都是内存问题,静态都在数据段只有一份,创建类的时候地址已经被固定,可以直接通过类名访问,也就意味着类中的静态成员是在数据段中,并不是在创建对象时才分配内存(全局变量)。到此为止不在深究。。。
-
不管怎么继承,static的成员地址都是独一份的,相当于全局变量,只不过是在类中,访问的时候有限制而已。。
-
用在单例模式
11、拷贝构造
-
拷贝构造细节:
-
Car car1(100,5); Car car2(car1); Car car3 = car2;//浅拷贝 Car car4; car4 = car3;//这里不会调用拷贝构造函数,构造函数是在被创建的时候调用的,car4已经存在,这里仅是拷贝car3的内容 //子类构造函数函数默认调用父类无参构造函数
-
-
浅拷贝:
-
指针类型的变量只会拷贝地址值,两个对象的指针成员变量指向同一个地址(存在风险)
-
编译器默认提供的拷贝是浅拷贝
-
-
深拷贝:
-
自己实现一个拷贝构造函数,拷贝构造函数内使用 new 申请对空间来存储内容
-
12、友元
将外部函数想直接访问类内部成员变量,可将外部函数在类内部声明为友元函数:
friend void run(Person p);
这就允许直接访问类内的成员变量
13、内部类
内部类:在一个类内部又创建了一个类
class Person { public: friend void run(Person p); Person(); ~Person(); class Car { public: int m_price; Car():m_price(0) {} void test() { Person person; person.m_age = 10; m_weight = 10;//直接访问 外部类的static 变量 } void test1(); private: }; private: int m_age; static int m_weight;//在代码区 }; void Person::Car::test1() { } int main() { Person person; std::cout <<"person 占用字节:" << sizeof(person) << std::endl; Person::Car car1; std::cout << "car1 占用字节:" << sizeof(car1) << std::endl; }
-
不影响内存分布,person类的内存分布不受内部类影响
-
成员函数可直接访问外部类的所有成员(不能反过来)
-
成员函数可以在内部类内部声明,在外部定义