C++知识点总结

1、c++介绍
(1)c与c++的关系
 》c++是c的增强版,也称为带类的c
 》c++是完全向下兼容c语言的
 》c++和c的最大区别是编程思想的变化
 》c++更适合编写大型的应用程序
(2)c++的应用领域—很广泛
 》嵌入式
 》游戏
 》服务器
 》等等
(3)c++的三大特性
 》封装 继承 多态

2、命名空间:解决命名冲突的问题
定义:
namespace 命名空间名
{
 变量;
 函数;
 …
}
::–>作用域运算符
命名空间名::成员名;
标准命名空间 std
但是如果在使用某个命名空间中定义的名称之前,加了using namespace ***;那么在使用这个名称时,就可以不用加::了
无名命名空间 — 与c语言中的static类似 只能在本文件内使用

3、标准输入输出流
头文件:iostream    using namespace std;
对流的操作就是对文件的操作
cin:标准输入流—>键盘
cout:标准输出流—>显示屏
控制输出格式:需要加头文件 iomanip
新添加的数据类型:string 头文件:string
int a;
cin >> a; //通过键盘给a赋值
cout << a; //输出a的值到显示屏
注意:
cin和cout在使用时,会自动根据变量的类型来进行匹配

  • 新的类型
    bool:true false
    string:字符串

4、引用:变量的别名
什么是引用?
  1、引用就是变量的别名,对引用的操作就是对变量的操作
  2、不会给引用分配内存空间,引用和被引用的变量共享同一块内存空间
  3、一个变量可以有多个引用,一个引用只能对应一个变量
int a = 10;
给a起别名:
int &k = a; //k是a的别名
定义引用:数据类型 &引用名 = 变量名;
注意:
  1、变量必须存在
  2、在定义引用时,一定要初始化
 (1)对引用的操作就是对变量的操作
 (2)定义引用不会开辟内存空间,和变量共享一块空间
 (3)引用也可以有引用
 (4)引用必须初始化
 (5)引用只能是一个变量的别名
》常引用
const 数据类型 &引用名 = 变量/常量;
const:只读,使用const修饰的变量不能被修改
(1)const修饰的引用不能被修改
(2)const修饰的引用可以是常量的别名,但是非常引用不能是常量的别名
》引用一般用来作为函数的形参或者返回值类型
作用:不用再开辟新的临时的空间,节省资源,减少开销
》&什么时候是取地址,什么时候是引用?
前面有数据类型就是引用,没有数据类型就是取地址
注意:
指针和引用的本质区别:指针是一个定义的变量,需要专门分配内存空间,对于变量的
操作是间接访问,而引用不会专门分配空间,是变量的别名,对变量的操作是直接操作
指针和引用在使用时的区别:
  1、指针占4个字节,引用不占空间
  2、指针可以不用初始化,并且指向可以改变,而引用必须初始化,并且只能是一个变量的引用
  3、指针加1,向地址增大的方向移动了一个数据,引用加+1,就是变量的值+1
  4、指针有二级指针,但是没有二级引用
  5、有void*类型的指针,但是没有void类型的引用
  6、指针只能是字符串常量的地址,而常引用可以是任何常量的引用
  7、有数组指针、函数指针,没有数组引用和函数引用
  8、指针只能是字符串常量的地址,常引用可以常量的别名

5、函数重载
在函数功能类似,参数类型或者个数不同的情况下,函数名可以重名,这就叫函数重载
概念:在同一个作用域内(比同一个命名空间),函数名相同,入参不同(类型不同,个数不同,参数顺序不同),
跟返回值无关,称为函数重载
特点:
  1、函数名相同
  2、参数不同
  3、与返回值无关
》函数重载实现的原理:
函数名在定义时可以重名,但是g++在编译时,其实对每个函数名按照参数进行了重命名
比如:
void fun();—>_Z3_funv,所以本质上函数名还是不一样的
void fun(int a, int b);—>_fun_int_int
void fun(float a, float b);—>_fun_double_double
在函数调用时,编译器会根据函数的实参来匹配调用哪个函数
注意:
如果在调用时,没有与之完全匹配的重载函数,那么就会调用与它相近的函数进行匹配,
如果匹配上多个函数,就会出现歧义,就会报错

6、默认参数:函数的形参可以有默认值
void fun(int a, int b, int c = 10);
在定义函数时,可以给不太重要的参数一个默认值,如果传递实参,该形参的值就是实参的值,
如果没有传递实参,形参的值就是默认值
注意:
  1、函数的传参时,是从左往右传,当有多个默认参数时,默认参数的设置必须是从右往左
  2、函数在声明时如果有默认参数,在定义时无需再有默认参数
  3、默认参数和函数重载一起使用,容易产生二义性
总结:
  1、如果只有函数的定义,那么此时默认参数出现在函数定义中
  2、如果既有函数声明又有函数定义,此时默认参数只能出现在函数声明中

7、c和c++的混合编程:c++程序如何使用自定义c库中的函数
因为g++在编译时,会对函数进行重命名,所以在链接c库时,存在找不到要调用的函数的问题
而因为c库是用gcc来编译的,不会对函数名重命名
解决方法:在.h文件中声明函数时告诉g++编译器(extern “C” void fun();),不要对函数重命名,即使用gcc编译
但是:此时c程序就又使用不了这个库啦,那就需要判断是gcc还是g++,采用不要的方式声明函数
怎么区分是g++还是gcc?
g++中有__cplusplus这个宏,而gcc没有
所以区分方法如下:

   #ifdef __cplusplus
          extern "C" void fun();
   #else
         void fun();
   #endif

8、new和delete运算符
new:在堆区申请内存空间
delete:释放堆区的内存空间
使用方法:
new–》申请一个变量的内存空间 :
数据类型 *指针变量名 = new 数据类型;
释放 delete 指针变量名;
申请一个数组的内存空间
数据类型 *指针变量名 = new 数据类型[元素个数];
释放 delete 指针变量名;—>只会释放首元素的内存空间
delete []指针变量名;
注意:
new和delete是运算符,而malloc和free是函数
int *p = (int *)malloc(sizeof(int));
int *p = new int; delete p;
int *p = (int *)malloc(sizeof(int) * 10);
int *p = new int[10]; delete []p; //如果没加[],只是将第一个元素释放了
与malloc和free的区别:
 1、new和delete是运算符,不需要新添加头文件,malloc和free是函数,需要加对应的头文件
 2、new申请的时候已经确定了内存中存放的数据的类型,而malloc不确定,所以需要对返回值进行强制类型转换
 3、malloc所申请的内存空间的大小需要手动计算,而new不需要,只要指定数据类型和元素的个数即可
 4、free在释放时,只需要知道首地址就可以,但是delete需要加[],否则只是释放首元素的内存空间

9、类和对象
》回顾c的结构体
c语言中结构体成员不能是函数
c++中成员可以是函数
类是一个自定义的数据类型,用来描述一类事物的信息(包括属性及行为)
类的定义不会开辟内存空间,但是定义变量是会分配内存空间的
对象:本质就是类这种自定义的数据类型定义的变量
类:是一个抽象的事物
对象:这类事物的真实存在
所以:在c++中,定义类的变量,我们叫做实例化一个对象
类的定义:
class 类名
{
 成员变量;
 成员函数;
};
实例化对象:类名 对象名;
封装:
把实现细节隐藏起来,只暴露出来用户关心的部分,就称为封装
怎么实现封装?—访问限定符:public protected private
public修饰的成员是被暴露出来的成员,既可以在类内部访问,也可以在类外部访问
protected&private修饰的成员时被隐藏的成员,只能在类的内部进行访问
public:可以实现暴露 可以在类外部通过对象来访问该类的成员
private:可以实现隐藏 只能在类的内部通过该类的成员函数来访问该类的成员
protected:受保护的 只能在类内部访问 不能通过对象在类外部访问
注意:c++中的结构体和类的区别:在结构体中,所有的成员默认是公有的,而在类中,
所有的成员默认是私有的
注意:protected与private只有在继承时会出现分化
注意:成员函数既可以在类内部定义,也可以在类外部定义,在类内部声明,在定义时注意需要加上作用域
数据类型 类名::函数名()
{
  …
}

10、this指针
this指针:当前类对象(谁正在调用非静态的成员函数谁就是当前类对象)的地址
存在于类中所有的非静态成员函数中(编译器自动加的)
本质:this指针实际上是所有的非静态的成员函数的第一个形参

11、构造函数:是用来给类中成员变量初始化的一个函数,是在实例化对象时,系统自动调用的
定义:
  1、函数名和类名相同
  2、没有返回值
  3、可以重载
>>>构造函数的类型
  1、默认的构造函数(系统提供)–无参构造函数
   1、如果一个类没有写任何构造函数,则系统就会生成默认的无参构造函数,函数为空,什么都不做
   2、只要自己定义了构造函数,系统就不会再生成这样的一个默认的构造函数
  2、一般的构造函数(自己定义)
   1、一般构造函数可以有各种参数形式,一个类可以有多个构造函数–(重载)
   2、创建对象时会根据传入的参数不同而调用不同的构造函数
Person person; //调用的是默认的构造函数
》哪些构造函数是默认的构造函数?
  1、如果我们没有定义构造函数时,系统提供的构造函数就是默认的构造函数
  2、我们自定义的无参构造函数也是默认构造函数
  3、我们定义的有参构造函数,并且所有的参数都有默认值
》构造函数的初始化列表
  1、初始化列表先于构造函数执行
  2、初始化列表只能用于构造函数
  3、初始化列表可以同时初始化多个数据成员
初始化列表的必要性:
一个数据成员如果是const的,那么使用构造函数就不能给其初始化,但是初始化列表可以

    Person(string _name, int _age = 10, string _id = "12233"):name(_name),age(_age),id(_id)
	{
		cout << "Person(string,int,string)" << endl;
		//name = _name;
		//this->age = _age; //error 因为age只读
		//this->id = _id;
	}
作用:还是给成员初始化的
存在必要性:因为如果有只读成员变量时,只能通过初始化列表的方式进行初始化
注意:只读的成员变量,必须在构造函数中使用初始化列表对其初始化,否则就会报错

12、拷贝构造函数
拷贝构造函数:
  1、通过一个已有的对象给一个新创建的对象初始化时,系统自动调用
  2、如果自己没有定义拷贝构造函数,系统默认提供
  3、只要我们自己定义了,系统就不提供了
Demo demo2 = demo1; Demo demo2(demo1);
拷贝构造函数的定义:
  1、函数名和类名相同
  2、没有返回值
  3、有且只能有一个形参,而且必须是对象的引用

13、深拷贝和浅拷贝
默认的拷贝构造函数是浅拷贝
浅拷贝:就是系统提供的拷贝构造函数,只是进行赋值
深拷贝:需要自定义拷贝构造函数,需要在该函数new一片空间
》什么情况需要深拷贝(也就是需要自己定义拷贝构造函数)?
当类中存在指针类型的成员,需要在构造函数中动态申请内存空间,此时如果用一个对象给另外一个对象初始化时,
就要自己定义拷贝构造函数?
为什么需要自己定义?
因为如果不自己定义,那么两个对象中的指针成员就会指向同一块动态内存空间,当其中一个对象被销毁时,该动态
内存空间也会被销毁,另外一个对象再去操作时,就会出现非法操作内存
所以:我们自己定义的拷贝构造函数,就要重新new出来内存,这种拷贝,就是深拷贝
总结:深拷贝 堆空间的拷贝
浅拷贝 栈空间的拷贝

14、析构函数:是在对象生命周期结束时,系统自动调用的
作用:在对象的生命周期结束时,系统会自动调用析构函数回收对象的资源
定义:
  1、函数名和类名相同,但是前面要加~
  2、没有返回值
  3、没有参数
注意:
  1、如果对象存在于栈区,那么当作用域结束时,即该对象的生命周期就结束了,就会自动
调用析构函数来回收资源
  2、如果对象存在于堆区,那么只有手动调用delete时,才会调用析构函数
特点:
 1、有默认的析构函数,如果没有自定义的析构函数,就会调用默认的析构函数
 2、如果有自定义的析构函数,将只会调用自定义的析构函数
 3、不需要用户主动调用,而是在对象生命周期结束时,会自动调用
 4、用户可以主动调用析构函数,但是此时只是执行其函数体,不会释放内存,只有当对象的生命周期结束时,才会释放
 5、如果对象在栈中存放,先构造的后析构
 6、如果对象在堆中存放,delete时调用析构函数
 7、如果在构造函数中或者其他的成员函数中使用new申请了内存,就需要在析构函数中使用delete将其释放掉

15、对象的大小
对象的大小: 所有的成员变量所占的内存空间之和就是对象的大小,但是注意字节对齐
注意:
如果一个类中只有成员函数,没有成员变量,此时对象的大小为1,目的是为了区分
由该类实例化的不同的对象

16、静态成员:使用static修饰的成员;属于类,而不属于某个对象
c语言中,静态变量在编译阶段就会分配好内存空间,也就是在程序运行开始前空间已被分配好
局部变量,是在程序运行时,系统自动分配
c++中什么时候使用static?
需要类中的一个成员变量为整个类,而非某个对象服务时,同时又不破坏类的封装性,即要求该成员对外不可见
怎么使用?
静态成员变量,只能在类内部使用static进行声明,而在main函数外部进行定义,并且定义时需要加作用域
比如:int Demo::a = 0;
static修饰的变量在编译阶段分配内存的
  1、静态成员变量:
   1、编译阶段分配内存,所以只能在类内部使用static声明,在类外部定义,定义时注意加上作用域
   2、静态成员变量属于整个类,不属于某一个对象,这个类的所有对象共享
  2、静态成员函数
   1、属于整个类,调用时可以不用实例化对象,直接类名::函数名();调用
注意:
  1、非静态的成员函数可以使用静态成员
  2、静态的成员函数不能使用非静态的成员
  3、静态的成员函数可以使用静态的成员变量

17、const
const int a = 10; a = 100; error
const int p = &a; p不能被修改
int const p = &a; p不能被修改
1、const修饰成员变量:不能被修改,只能通过初始化列表的方式进行初始化
2、const修饰的成员函数:放在函数后
  1、在该函数中,所有的成员变量都只读
原因:因为非静态的成员函数都隐含了一个this指针,而const修饰的成员函数
this指针的类型为:const 类名

只读的成员函数不能调用非只读的成员函数
原因:只读的成员函数 this指针的类型为const 类名
,但是非只读的成员函数的this指针
为类名
,所以传参时,类型不匹配
本质:const其实修饰的不是函数,而是this指针,因为this指针我们无法加const,
而this指针又只出现在所有的非静态的成员函数中,所以给成员函数加const
修饰,本质是给this加了const修饰
  2、只能调用常成员函数
  3、const修饰的对象:
    1、不能作为左值
    2、不能修改任何成员变量的值
    3、只能调用常成员函数
总结:
1、不管是常对象还是常成员函数都不能修改任何成员变量的值
2、不管是常对象还是常成员函数都只能调用常成员函数
常引用和常指针
const Demo &bb = aa; 只能调用常成员函数
总结:
常成员函数、常对象、常引用都只能调用常成员函数
常成员函数不能修改任何成员变量的值

18、mutable:易变的、可变的
使用mutable修饰的成员变量,在const修饰的成员函数中可以被修改

19、友元:打破了封装,可以访问类的私有成员
友元函数:类外部的函数,没有this指针
友元类:在A类中声明B类是它的朋友,B类所有的成员函数都可以访问A类的私有成员,但注意A类的成员函数不能访问B类的 私有成员
类的成员函数作为友元,B类中的某个成员函数是A类的友元,只有这个成员函数可以访问A类的私有成员
注意:
B类必须定义在A类之前,A类需要在B类之前声明,B类中的友元成员函数必须定义在类的外部,而且要在A类之后
如果在类外部的某个函数中,想要访问这个类的私有的成员,就要将这个函数声明为该类的友元就可以访问了
友元破坏了类的封装性,要谨慎使用
使用时,要让一个函数或者一个类成为某个类的友元,那么需要在该类的内部进行声明,并且要以friend开头,声明可以出现类内部的任何地方

20、运算符重载—就是给原有的运算符赋予新的功能(功能是类似的,而且原有的优先级不变)
比如+ -这样的运算符,计算基本数据类型,是完全ok的
但是对于类这样的自定义的数据类型+、-等等运算符是不支持的
但是我们就想demo1+demo2,怎么办?
我们可以对原有的运算符进行重载
运算符重载函数的定义:
  1、可以作为类的成员函数
  2、可以作为类的友元函数 区别在于:友元没有this指针
定义形式:
返回值类型 operator 要重载的运算符(形参。。。)
{
  函数体;
  …
}
可以通过友元函数(没有this)以及成员函数(有this)来实现
  1、一元运算符的重载
例如:-的重载
++的重载
  2、二元运算符的重载 +
<< 只能通过友元函数
[] 成员函数
不能被重载的运算符:sizeof、::
函数的返回值是引用:表示返回的值为实参本身

21、继承
C++知识点总结_第1张图片
继承:父类 子类 基类和派生类
继承方式:public private protected
怎么继承?
class Base
{
  …
}
class Inherit:public Base
{
  …
}
(1)在实例化子类对象时,系统先会调用父类的构造函数对子类中从父类继承来的成员变量
初始化,再调用子类的构造函数。而析构的顺序正好构造的顺序相反(先构造的后析构)
(2)在子类的构造函数中,如果没有指定要调用父类的哪一个构造函数,就会调用父类的默认
构造函数
原有的类称为父类(基类) ,新定义的类称为子类(派生类)
子类可以继承父类的成员变量及成员函数,可以减少代码量以及节省时间
比如:Person类:姓名 年龄 eat();
工人类:
怎么实现继承?
继承分为public protected private
复习:访问限定符
public:公有成员在任何地方都可以被访问
protected:在类内部可以访问,友元可以访问,在类外部不能被访问
private:在类内部可以访问,友元可以访问,在类外部不能被访问
公有继承
class Worker:public Person
{
 。。。。。。。
};
实例化子类时:
构造时:先基类 再子类
析构时:先子类 再基类
注意:
子类在构造自己时,一定会先调用父类的构造函数初始化从父类继承而来的部分
protected与private在作为访问限定符时,没有区别
但是在继承时,如果是protected成员通过继承(public、protected),在子类中是可以被访问的,但是private成员被继承后,在子类中
变成了不可访问
保护继承
多层继承
多重继承:一个子类继承多个父类
动物-》狗-》长毛狗
车辆-》汽车-》越野车
电器-》冰箱-》智能冰箱
人-》工人-》教师

多重继承:

      动物
      /  \
     羊  驼
      \  /
      羊驼

羊驼对象中会出现动物类数据,调用时就会出现调用不明确的问题,解决方法:
1、指定作用域–》但是还是有两份
2、虚继承–》只有一份

ass Migrant:public Worker,public Farmer
{

}
C++知识点总结_第2张图片
继承中的关系 is-a 子类对象就是基类对象
 1、子类对象可以给父类对象赋值 --》只会将父类中有的成员变量的值赋值给父类对象,子类的部分会自动舍弃掉
 2、父类指针指向子类对象 但是只能访问父类中有的部分,其他的访问不了
注意:不能通过子类指针指向父类对象(不能使用父类对象给子类对象赋值)
但是:如果父类指针指向new出来的子类对象,那么当delete时,只会调用父类的析构函数,怎么解决?
虚析构函数
has-a 包含 私有继承
隐藏:当子类中的成员函数与父类中的成员函数重名,此时通过子类对象调用重名函数时,就会将父类中的重名函数隐藏起来
如何调用父类中的重名函数:worker1.Person::eat();
如果子类的成员函数与父类的重名,但参数不同,此时会形成重载,但是还是会将父类的隐藏起来

22、多态
1、什么是多态—》多种状态
调用同一接口,表现出不同的结果
2、静态多态与动态多态(早绑定和晚绑定)
绑定:将函数体和函数调用关联起来
早绑定:在编译阶段,就确定要调用哪个函数----函数重载
晚绑定:在运行阶段才能够确定要调用哪个函数(多态)
总结:通过父类指针指向不同的子类对象,调用同一接口,所呈现出的结果不同(即:调用同一接口,表现出不同的结果)
这就叫做动态多态 注意:多态必须以封装和继承为前提
隐藏:当子类中出现和父类同名的函数时,如果通过子类对象调用该函数,调用的是子类自己的,
因为子类的函数将父类的隐藏了,如果想要调用父类的同名函数,就要加作用域
多态通过虚函数来实现
virtual使用的限制:
(1)virtual不能修饰普通函数,必须是修饰某个类的成员函数(普通成员函数和析构函数)
(2)不能修饰构造函数
(3)不能修饰静态成员函数

virtual:
 1、可以修饰普通的成员函数
 2、不能修饰静态成员函数
 3、可以修饰析构函数
 4、不可以修饰构造函数
 5、不可以修饰类外部的函数

3、虚函数的实现原理
函数指针:
只要一个类中有虚函数或者虚析构函数,那么一个类就会对应有一个虚函数表(所有这个类的对象共享的)
虚函数表中存放的是这个类中所有的虚函数的入口地址(存放的顺序与函数在类中声明的顺序一致)
4、覆盖、隐藏与重载的区别
覆盖:
(1)不同的作用域 (分别位于基类和派生类)
(2)函数名、参数、返回值相同
(3)基类函数必须是虚函数
(4)重写函数的访问权限限定符可以不同
重载:
(1)相同作用域
(2)函数名相同、参数不同,与返回值无关
(3)virtual关键字可有可无
隐藏:
(1)不同的作用域 (分别位于基类和派生类)
(2)函数名相同,参数不同时,不论有无virtual都会将父类的同名函数隐藏
(3)函数名相同,参数相同,返回值相同时,只有当没有virtual时,才会隐藏,否则就是覆盖
5、纯虚函数与抽象类
纯虚函数:一个虚函数,只有声明没有函数体
怎么声明:virtual void fun() = 0;
抽象类:包含纯虚函数的类
注意:
(1)、抽象类不能被实例化
(2)、抽象类天生就是被继承的
(3)、抽象类的子类也可以是抽象类
(4)、如果抽象类的子类不是抽象类,说明子类对父类中的纯虚函数重写了
接口类:如果一个类中没有成员变量,只有成员函数,且所有的成员函数都是纯虚函数,此时该类就称为接口类
在实际应用中,接口类更多的是用来表达一种能力或协议
6、多态的作用
(1)接口复用,方便调用
(2)提高代码的可扩展性和可维护性

23、explicit关键字
作用:
(1)只能修饰构造函数
(2)阻止单参数的构造函数隐式类型转换(基本数据类型转换成类的类型)
所以:建议单参数的构造函数前加explicit ,析构函数前加virtual

24、final关键字
(1)被final修饰的类不能被继承
(2)被final修饰的虚函数不能被重写
override:帮助检查:子类中重写的函数防止出现与父类中的虚函数参数不一致
因为:编译器不会报语法错误,而加上override就会报语法错误

25、inline关键字
被inline修饰的函数称为内联函数
把频繁调用的简短函数定义成内联函数,可以避免函数调用的开销,内联函数在编译阶段就会展开
注意:
(1)内联函数只是对编译器的一个建议,是否能真正内联,要看编译器
(2)inline必须和函数的定义一起使用,如果和函数的声明放在一起使用,则不起作用

26、转换函数
转换函数:如何将一个对象转换成基本数据类型
int a = demo;
转换函数的定义:只能是成员函数
operator 类型名()
{
。。。。
}
标准转换函数:
reinterpret_cast:不同类型的指针及引用之间的转换,还可以将指针类型转换成整数类型
const_cast:可以将常指针或常引用转换成非常指针或常引用
static_cast:(1)基本数据类型之间的转换(2)具有继承关系的指针或引用之间的转换
dynamic_cast:具有继承关系间的指针或引用的转换(多态)

 int *p;
 char *q = (char *)p;  //char *q = reinterpret_cast(p);

static_cast与dynamic_cast区别?
(1)只有类中含有虚函数时,才能使用dynamic_cast
(2)dynamic_cast具有类型检查的功能,比static_cast更安全
(3)dynamic_cast是运行时转换,static_cast是编译时转换

27、智能指针
智能指针:自动回收内存空间
char *p = new char[100];
只要p的生命周期结束,就会自动释放new出现的空间
让智能指针的生命周期和堆内存的生命周期保持一致,释放智能指针的时候,自动把堆内存空间释放
实质:智能指针是一个特殊的类模板 对-> 进行了重载
三种:
(1)shared_ptr 共享型智能指针 可以允许多个指针共享一块堆空间,当所有的智能指针被释放,堆空间才会被释放
(2)unique_ptr 独享型智能指针 同一时刻只能有一个独享性智能指针指向一块堆内存
(3)week_ptr 弱型智能指针 不能单独存在,必须要和共享型智能指针一起使用,弱型智能指针的增加和消亡并不影响
堆内存的释放
本质是类模板,但是对
和->进行了重载

28、虚函数表
C++知识点总结_第3张图片
在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。
这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

29、模板 函数模板 类模板 标准模板

int add(int a, int b)
{
 return a+b;
}  

float add(float a, float b)
{
 return a+b;
}

以上的代码重复的地方太多,唯一的区别在于类型不同
参考生活:我们可以搞一个函数的模板过来,我需要用
的时候只需要把类型作为参数传给模板,给我生产出来
相应的函数,然后我们调用就可以啦
杯子模具–》生产出来杯子–》用杯子
函数模板–》生成函数–》调用函数
泛型编程:以一种独立于任何特定类型的方式编写代码
模板作用:减少重复代码,提高编程效率
函数模板:typename class —>表示数据类型
template //声明一个函数模板
T add(T a, T b)
{
 return a+b;
}
怎么使用?
int sum = add(100, 200);
int sum = add(100,200);
当我们调用函数时,计算机会根据模板实例化一个对应的模板函数
注意:在计算机中,如果仅仅只是写出了函数模板而没有使用它,那么计算机是不会产生任何代码数据的,
因为它不知道要产生什么样的代码数据,只有当我们要使用这个函数模板时,计算机才知道你要实例化
一个什么样的模板函数,这个时候才会产生真正的代码,才会参与逻辑的运行
 1、函数模板可以是一个参数也可以是多个参数
 2、变量作为模板参数
思考:由函数模板生成的函数,之间是什么关系? 重载
而且不同的函数模板生成的模板函数也可以形成重载
类模板:
注意:
 (1)如果成员函数定义在类的外部,就要写成如下形式:
template
void ARR::show()
{
   for (int i = 0; i < size; i++)
    {
     cout << arr[i] << endl;
    }
}
 (2)由IDE环境的影响,有些环境不能将类模板的定义以及成员函数的实现分开,如果分开,就会出错
标准模板:STL(标准模板库)–》类–》容器类
(1)向量—本质:就是对数组的封装
初始化vector对象的方式:
1、vector v1;
2、vector v2(v1);
3、vector v3(n,i); v3包含n个值为i的元素
4、vector v4; v4包含有值初始化元素的n个副本
常用函数:
empty(); 判断向量是否为空
front(); 第一个元素
back(); 最后一个元素
size(); 元素个数
push_back(elem); 将数据插入向量尾部
pop_back(); 删除向量尾部元素
begin(); 返回向量迭代器首元素
end();返回向量迭代器末元素的下一个元素
迭代器:复杂的指针–》可以访问标准模板库中的每一个元素

vector::iterator iter = v2.begin();
for (; iter != v2.end(); iter++) 
{
 cout << *iter << endl;
}

(2)链表 list
list1.push_back(100);//尾插
list1.push_front(300);//头插
list1.pop_back();//尾删
list1.pop_front();//头删

list::iterator iter = list1.begin();
for (; iter != list1.end(); iter++)
{
cout << *iter << endl;
}

中间插
iter = list1.begin();
iter++;
iter++;
list1.insert(iter, 999);
(3)映射(map)
映射存储的数据都是成对出现的,我们把它标记为key(键)和value(值)
访问:通过键找到值

30.静态方法调用的三种方式:
 1、new xx().静态(); //使用对象调用,不推荐
 2、xx.静态(); //类名调用,正规调用方法,推荐
 3、静态(); //本类的静态方法在本类调用,直接调用

你可能感兴趣的:(C++,C++)