一、面向对象的四个基本特征:抽象、封装、继承、多态(抽象、继承、封装是多态的基础,多态是抽象、封装、继承的表现)
1、32位机器上,指针sizeof为4个字节,64位机器上,指针sizeof为8个字节
2、空类也会被实例化,所以空类的sizeof为1个字节,非空类的sizeof就涉及到字节对齐问题
3、在没有特殊注明字节对齐时,系统会以默认的字节对齐规则进行字节对齐(常见对齐字节:1、2、4、8、16)
4、#pragma pack(push,A字节)指明字节对齐规则以A字节为准,A也只能为(1、2、4、8、16),#pragma pack(pop)为释放对齐规则(以64位机器为例)
class A{
char a[2];
char *p;
};
sizeof(A对象) 为16
#pragma pack(push,4)
class A{
char a[2];
char *p;
};
#pragma pack(pop)
sizeof(A对象) 为12,如果把4改为2字节对齐,则结果为10
注:在没指明字节对齐规则时,默认对齐方式以最大数据结构所占字节对齐,内存申请按顺序依次开辟空间,所以同样的数据类型不同顺序都会影响内存大小
5、虚函数:在C++中,基类必须将它的两种成员函数区分开来,一种是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通过在函数之前加上virtual关键字将其定义为虚函数。
a. 一旦基类的某个函数被声明成虚函数,则所有派生类中它都是默认为虚函数,不论加没加virtual关键字。
b. 任何构造函数之外的非静态函数都可以是虚函数。(类中的构造函数和静态函数不能为虚函数)
c. 如果派生类没有覆盖其基类中某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。
6、纯虚函数:基类中不能对虚函数给出有意义的实现。为了让虚函数在基类什么也不做,在虚函数体声明后面加上”=0”就是纯虚函数了。
a. 有纯虚函数的类叫抽象类,是不能new对象的。
7、重载:是指在同一个类中
a. 函数名称必须相同。
b. 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
c. 函数的返回类型可以相同也可以不相同。(不能作为重载的判断依据,例如,返回值不同,参数列表相同,则不属于函数重载)
d. 仅仅返回类型不同不足以成为函数的重载。
8、重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 (参数列表可以不同)。
9、重写:也叫覆盖。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。当我们对别人提供好的类的方法感觉不是太满意时,我们就可以通过继承这个类然后重写其方法改成我们需要的逻辑。
a. 重写是子类与父类之间的。
b. 被重写的函数不能是 static 的。
c. 函数三要素(函数名、函数参数、函数返回类型)完全一样
d. 如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这也就是C++中的多态机制
10、抽象:如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
a. 有纯虚函数的类是抽象类
11、封装:就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对象的使用者和设计者分开,以提高软件的可维护性和可修改性。
a. 结合性,即是将属性和方法结合
b. 信息隐蔽性,利用接口机制隐蔽内部实现细节,只留下接口给外界调用
c. 实现代码重用
12、继承:就是新类从已有类那里得到已有的特性。类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类, 产生的新类称为派生类或子类,子类继承基类后,可以创建子类对象来调用基类函数,变量等。
a. 单一继承:只继承一个父类,一般情况尽量使用单一继承,使用多重继承容易造成混乱易出问题
b. 多重继承:继承多个父类,类与类之间要用逗号隔开,类名之前要有继承权限,假使两个或两个基类都有某变量或函数,在子类中调用时需要加类名限定符如c.a::i = 1;
c. 菱形继承:多重继承掺杂隔代继承1-n-1模式,此时需要用到虚继承,例如 B,C虚拟继承于A,D再多重继承B,C,否则会出错
d. 继承权限:继承方式规定了如何访问继承的基类的成员。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限
13、多态:相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。不同类的对象对同一消息作出不同的响 应就叫做多态。就像上课铃响了,上体育课的学生跑到操场上站好,上语文课的学生在教室里坐好一样。C++的 多态性是通过虚函数来实现的, 只有重写了虚函数的才能算作是体现了C++多态性。C++支持两种多态性。
a. 编译时多态性(静态多态性,早绑定):通过重载函数实现
b. 运行时多态性(动态多态性):通过虚函数实现,是指在程序运行时才能确定函数和实现的链接,此时才能确定调用哪个函数,父类指针或者引用能够指向子类对象,调用子类的函数,所以在编译时是无法确定调用哪个函数。
14、引用:是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价。
a. 语法:数据类型 &引用名=目标变量名;
b. &不是求地址运算符,而是起标志作用
c. 引用的类型必须和其所绑定的变量的类型相同(int a=1;double &b=a;不合法。int &b=a;合法)
d. 声明引用的同时必须对其初始化,否则会报错
e. 不能再将已有的引用名作为其他变量或对象的名字或别名
f. 引用不是定义一个新的变量或对象,因此内存不会为引用开辟新的空间存储这个引用
g. 对数组的引用语法:类型 (&引用名)[数组中元素数量]=数组名;(int a[3] = {1,2,3}; int (&b)[3] = a;)
h. 对指针的引用语法:类型 *&引用名=指针名;(int a=10; int *b=&a; int *&c=b;)
i. 引用作为函数的参数
1)当用引用作为函数的参数时,其效果和用指针作为函数参数的效果相当。当调用函数时,函数中的形参就会被当成实参变量或对象的一个别名来使用,也就是说此时函数中对形参的各种操作实际上是对实参本身进行操作,而非简单的将实参变量或对象的值拷贝给形参。
2)通常函数调用时,系统采用值传递的方式将实参变量的值传递给函数的形参变量。此时,系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,也就是说形参变量只是实参变量的副本而已;并且如果函数传递的是类的对象,系统还会调用类中的拷贝构造函数来构造形参对象。而使用引用作为函数的形参时,由于此时形参只是要传递给函数的实参变量或对象的别名而非副本,故系统不会耗费时间来在内存中开辟空间来存储形参。因此如果参数传递的数据较大时,建议使用引用作为函数的形参,这样会提高函数的时间效率,并节省内存空间。
3)使用指针作为函数的形参虽然达到的效果和使用引用一样,但当调用函数时仍需要为形参指针变量在内存中分配空间,而引用则不需要这样,故在C++中推荐使用引用而非指针作为函数的参数
4)如果在编程过程中既希望通过让引用作为函数的参数来提高函数的编程效率,又希望保护传递的参数使其在函数中不被改变,则此时应当使用对常量的引用作为函数的参数。(常引用)
5)数组的引用作为函数的参数:C++的数组类型是带有长度信息的,引用传递时如果指明的是数组则必须指定数组的长度(例: void func(int(&a)[2]); int number[5]={1,2};func(number);)
j. 常引用 语法:const 类型 &引用名=目标变量名;
1)不允许通过该引用对其所绑定的变量或对象进行修改
k. 引用作为函数的返回值 语法:类型 &函数名(形参列表){ 函数体 }
1)引用作为函数的返回值,必须在定义函数时在函数名前加&
2)引用作为函数的返回值,最大的好处是在内存中不产生返回值的副本
15、const:允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。在C中,我们都是习惯用#define来定义常量,C++中提供了const修饰符来定义常量,这种方式更灵活,更安全。主要表现在define常量是没有类型定义的,const常量是类型化的,它有地址,可以用指针指向这个值,但是不能修改它。
a. const 修饰指针
1)const char* p = “name”;
const位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据(p[1]=‘a’;不合法);但指针可以指向新的内存地址(p=“age”;合法);指针指向的数据不能变,指针的指向可以变。
2)char * const p = “name”;
const位于*右侧,表示指针本身是常量,不能指向其他内存地址(p=“age”;不合法);但指针所指的数据可以通过解引用修改(p[1]=‘u’);指针指向的数据可以变,指针的指向不能变。
3)const char * const p = “name”;
两个const,*左右各一个,表示指针和指针所指数据都不能修改。
b. const 修饰成员函数
1)const修饰的成员函数不能修改任何成员变量(mutable修饰的变量除外)
2)const修饰的成员函数不能调用非const成员函数(因为非const成员函数可以会修改成员变量)
c. const 修饰函数返回值
1)如果声明函数的返回值类型为const int fun();则接收该函数的返回值也必须是const int类型(例: const int fun(void); int a=fun()不合法,const int a=fun()合法)
2)如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,所以const对于值传递是没有意义的。
16、static的作用主要有两种:第一个作用是限定作用域;第二个作用是保持变量内容持久化;
a、c语言中static的用法
1)全局静态变量:在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。
内存中的位置:静态存储区,在整个程序运行期间一直存在。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。
2) 局部静态变量:在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存中的位置:静态存储区
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;
3)静态函数:在函数返回类型前加关键字static,函数就定义成静态函数。函数的定义和生命在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用;
b、C++中static的用法
1)类的静态成员
2)类的静态成员函数:实现的时候也不需要static的修饰,因为static是声明性关键字;类的静态函数是该类的范畴内的全局函数,不能访问类的私有成员,只能访问类的静态成员,不需要类的实例即可调用;静态成员函数可以继承和覆盖,但无法是虚函数;
17、内联函数:在函数声明前加上关键字inline;在函数定义前加上关键字inline。
a、和宏函数的区别:宏函数是由预处理器对宏进行替换,而内联函数是通过编译器实现的,因此内联函数是真正的函数,只是在调用的时候,内联函数像宏函数一样展开,所以它没有一般函数的参数压栈和退栈操作,减少了调用开销
b、内联函数也有一定的局限性,就是函数中执行的代码不能怠惰,结构也不能太复杂。如果内联函数的函数体过大,编译器会放弃内联方式,而采用普通的方式调用函数。
c、内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
18、数组指针:一个指向一维或者多维数组的指针。
a、int *b = new int[10]; 指向一维数组的指针b,这个时候释放空间一定要delete []b,
否则会造成内存泄露,b 就成为了空悬指针。
b、int (*p)[10] = new int[10][10]; 这里的p指向了一个二维int型数组的首地址。
p等效于二维数组,但没有指出其边界,即最高维的元素数量
19、指针数组:一个数组里存放的都是同一个类型的指针
a、int *a[10]; 它里边放了10个int * 型变量,由于它是一个数组,已经在栈区分配了10个(int * )的空间,也就是32位机上是40个byte
20、函数指针:
a. 声明格式:返回值 (*函数名)(参数类型 参数1,参数类型 参数2);
b. 例子: void add(int a, int b){ a+b;};
void (*p)(int a, int b);
p = add;或 直接定义void (*p)(int a, int b) = add;
调用: p(1,2);或 (*p)(1,3);都是可以的(这是历史遗留原因)
c. 包含多个函数指针的数组
21、回调函数:把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
a. typedef void (*Fun)(int);//定义一个函数指针类型
Fun p = NULL;//用Fun定义一个变量p,它指向一个返回值为空参数为int的函数
void caller(Fun pCallback) {
p = pCallback;
//达成某一条件后,通过名片(函数指针p),传回结果
int result = 1;
(*p)(result);
}
void callback(int a)//回调函数 {
std::cout << "callback result = " << a << std::endl;
}
caller(callback);
22、友元:C++中的友元机制允许类的非公有成员被一个类或者函数访问,友元按类型分为三种:普通非类成员函数作为友元,类的成员函数作为友元,类作为友元。友元包括友元的声明以及友元的定义。友元的声明默认为了extern,就是说友元类或者友元函数的作用域已经扩展到了包含该类定义的作用域,所以即便我们在类的内部定义友元函数也是没有关系的。
a. 友元不能被继承: B是A的友元类,C是B的子类,推不出C是A的友元
b. 友元不具有传递性: B是A的友元,C是B的友元,推不出C是A的友元
23、友元函数:C++中引入友元函数,是为在该类中提供一个对外(除了他自己以外)访问的窗口;
a. 友元函数声明的一般形式:friend <返回类型> <函数名> (<参数列表>);
b. 类中通过使用关键字friend 来修饰友元函数,但该函数并不是类的成员函数,其声明可以放在类的私有部分,也可放在共有部分。友元函数的定义在类体外实现,不需要加类限定。
c. 一个类中的成员函数可以是另外一个类的友元函数,而且一个函数可以是多个类友元函数。
d. 友元函数可以访问类中的私有成员和其他数据,但是访问不可直接使用数据成员,需要通过对对象进行引用。
e. 友元函数在调用上同一般函数一样,不必通过对对象进行引用。
24、函数模板:把处理不同类型的公共逻辑抽象成函数,函数模板可以声明为inline或者constexpr的,将它们放在template之后,返回值之前即可。
a、例
template
int compare(const T& left, const T& right) {
if (left < right) {
return -1;
}
if (right < left) {
return 1;
}
return 0;
}
compare
b、不仅普通函数可以定义为模板,类的成员函数也可以定义为模板。
c、成员函数模板不能是虚函数