参考出处:https://blog.csdn.net/jiange_zh/article/details/71714005
1.多态
- 面向对象的语言有三大特性:继承、封装、多态。其中多态分为两种:即静态多态和动态多态;
- 静态多态可以称为编译期多态,它是在编译期间通过函数重载和运算符重载的方式决定被调用函数;
- 动态多态可以称为运行期多态,它可以通过继承和虚函数来实现。实现时,编译器将在进程运行的过程中动态的捆绑想要调用的函数;
- 多态实现有三个必要条件:继承,父类指针指向子类对象,子类重写父类虚函数。
2.虚函数
首先了解下多态的实现原理 C/C++—— 对多态现象的理解
- 当类声明虚函数时,编译器会在类中生产一个虚函数表,用来存储类成员函数指针,虚函数表是由编译器自动生产与维护的。
- 继承时会继承父类的虚函数表,如果子类没有重写父类虚函数,虚表与父类的虚表无异,如果重写了,子类虚函数在虚表的对应指针的位置会被修改为子类虚函数指针。多继承时子类会有多个表。
- 对于一个实例对象,会有一个虚指针(只有一个) 指向虚表,执行前的查表所用。 有子类时,析构函数必须是虚函数。构造函数不能是虚函数(没有意义,底层相关):网络上还有一个很普遍的解释是这样的:虚函数对应一个指向vtable虚函数表的指针,但是这个指向vtable的指针事实上是存储在对象的内存空间的。假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
- 另一个优点:实现封装。
3.内存分布
C/C++主要有五种内存存储区
全局/静态存储区域:存全局变量,静态变量。程序编译时内存已分配好,并存在于程序整个运行期间,程序结束后由系统统一释放
全局变量和静态变量被分配到同一块内存中。
C 语言中,全局变量又分为初始化的和未初始化的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域。同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放。
在 C++ 里面没有区分,他们共同占用同一块内存区。栈:存放函数的参数值,局部变量,由高到低分配。
- 函数执行结束时会被自动释放。栈内存分配运算内置于处理器的指令集中,效率高,但是容量有限。
- 堆(动态内存分配):通过**new和malloc由低到高分配
- 由delete或free手动释放或者程序结束自动释放**。动态内存的生存期人为决定,使用灵活。缺点是容易分配/释放不当容易造成内存泄漏,频繁分配/释放会产生大量内存碎片。 若程序员不释放,程序结束时可能由OS(操作系统)回收
- 字符/文字常量区: 存放常量字符串,程序结束时由系统释放
- 程序代码区: 存放函数体的二进制代码
4.static
- 隐藏(static函数,static变量均可)
作用:当前文件可用,不同文件可以定义同名函数和同名变量,不必担心命名冲突 ,static函数的作用仅限于隐藏 - 保持变量内推的持久(static变量的记忆功能和全局生存期)
- 默认初始化为0(static变量)
- C++中:
- 属于类不属于对象
- 必须类外定义,且定义的时候去掉static,如:void Father::a = 0;
- static函数无this指针,只能访问static成员,但是非static成员函数因为具有this指针,可以访问static变量或函数,后者因为属于类成员函数,在this管控范围之内
5.程序编译过程
6.智能指针
- 智能指针是一个类,构造函数传入一个普通指针(explicit显式),析构函数释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。主要用来解决new对象忘记delete的危害
- std::auto_ptr:转移所有权。 不支持复制(拷贝构造函数)和赋值( operator =),但复制或赋值的时候不会提示出错。因为不能被复制,所以不能被放入容器中。
- unique_ptr,也不支持复制和赋值,但比 auto_ptr 好,直接赋值会编译出错。实在想赋值的话,需要使用:std::move 或者 右边是一个临时对象。
- shared_ptr,基于引用计数。
- weak_ptr,弱引用, 只引用,不计数。引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。 weak_ptr 不保证它指向的内存一定是有效的,在使用之前需要检查 weak_ptr 是否为空指针。
7.extern
- 外部变量声明
- 多个文件共享const变量
- extern ‘C’ C语言编译,但是重载函数只能有一个
- 模板的控制实例化
8.volatile
- 可能会发生意想不到的改变,直接从内存读取,不要从寄存器缓存中读
9.指针vs引用
- 非空区别:引用必须指向某对象,指针可为空;
- 合法性区别:指针可为空,所以用前要检查;
- 指向可修改区别;
- 指针要解引用;sizeof 结果不同;自增意义不同;引用不分配内存。
10.const vs #define
- Const 有类型检查,安全。
- define在预处理替换,const在编译时确定值
- define不分配内存,用的地方多会发生多次拷贝,内存消耗大,const在静态存储区中分配空间,运行过程中内存中只有一个拷贝。
- C++:const成员变量只能用初始化列表,引用也是。
11.sizeof vs strlen()
- Sizeof是操作符,不是函数,返回的是变量声明后所占的内存数,不是实际长度。其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。当计算数组的size时,数组不会退化成指针。
- Strlen 是函数,strlen(char*)函数求的是字符串的实际长度,直到遇到第一个’\0’,然后就返回计数值,且不包括’\0’,函数的返回值值在运行时确定。参数是指针或字符数组,当数组名作为参数传入时,实际上数组就退化成指针了。
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char arr[20]="hello world!";
cout<<"strlen(arr) : "<<strlen(arr)<<endl; //返回12,一共有12个字符,不包括'\0'
cout<<"sizeof(arr) : "<<sizeof(arr)<<endl; //返回20,因为系统给数组分配了20个字节
cout<<"strlen(\"hello\") : "<<strlen("hello")<<endl; //返回字符串长度5
cout<<"sizeof(\"hello\") : "<<sizeof("hello")<<endl; //返回6,实际的存储空间还包含最后面的'\0'
char *p="hello";
cout<<"strlen(p) : "<<strlen(p)<<endl; //返回字符串长度5
cout<<"sizeof(p) : "<<sizeof(p)<<endl; //返回指针p的内存空间大小4个字节
}
12.内存对齐
- 自身对齐:地址为自身大小的整数倍;
- 整体对齐:为最大成员的整数倍;
13.为什么要有继承
- 清楚定义对象的特性,减少冗余代码;
- 多态性;
- 重用