1.抽象类
带有纯虚函数的类称为抽像类,不可实例化,只能作为接口使用
作用:可定义指向抽象类的指针,此指针可指向其派生类,进而实现多态
2.纯虚函数
一个在基类中声明的虚函数,在基类中没有定义具体的操作内容,要求各派生类根据需要定义自己的版本。
virtual function_type function_name(arguments) = 0;
3.构造函数的delete和default
myClass() = default; //默认存在构造函数
myClass() = delete; //删去
- 一旦程序员实现了这些函数的自定义版本,编译器将不会再生产默认版本
- 限制默认构造函数生成,典型的是静止使用拷贝构造函数
- 可以避免不必要的隐式类型转换(调用的是整形构造函数)
- delete可以删去operator new
void * operator new(std::size_t) = delete; - 析构函数也可被delete——限制自定义类型在栈上或静态储存区的构造
4.Override/Overload/Overwrite 覆盖/重载/重写
override
标识符,非关键字;只有在虚函数的环境中才有意义;不同的作用域(覆盖)
- 向代码读者展示了这是一个虚拟的方法,它覆盖了基类的虚拟方法
- 强制编译器检查这个函数是否正确的覆盖了基类的函数
overload
重载:相同的作用域;函数名词相同;参数的类型或顺序不同
overwrite
派生类屏蔽了与其同名的基类函数
5.虚函数的作用
若不是虚函数,不管基类指针指向哪个派生类的对象,调用时都会调用基类中定义的函数。
虚函数:动态绑定的基础
6.malloc 和 free
void* 类型可通过类型转换,强制转化为任何类型的指针
与new & delete的区别
- new可以按照变量类型自动分配所需要的空间长度
- new可以自动返回正确的指针类型
- new会自动调用构造函数,delete自动调用析构函数;但malloc和free不会
- new申请的内存在自由储存区(栈或静态储存区);malloc分配的内存在堆区
- new内存不足时会抛出异常,而malloc返回NULL
如何判断new()是否成功
try
{
int *p = new int[1e9];
}
catch (const bad_alloc & e)
{
cout << "failed" << endl;
//return(-1);
}
int *p = new (nothrow) int[1e9];
if (p == 0)
cout << "failed" << endl;
delete[] p;
delete[ ]怎么知道需要释放空间的大小
申请空间的首位置的前4个字节记录了申请空间的大小。(不一定是4个字节,具体需要跟具体的内存对齐相关)在Visual Studio上的代码如下
using namespace std;
class A
{
static int i;
public:
A() = default;
~A()
{
cout << "delete the " << ++i << " A." << endl;
}
};
int A::i = 0;
A* test()
{
return(new A[6]);
}
int main()
{
A *p = test();
int address = (int)p - 4;
cout << *(int *)address << " A will be deleted" << endl;
delete[] p;
return(0);
}
运行结果为:
6 A will be deleted
delete the 1 A.
delete the 2 A.
delete the 3 A.
delete the 4 A.
delete the 5 A.
delete the 6 A.
7.防止头文件被重复包含或引用
#pragma once——保证头文件只被编译一次(在头文件最开始的时候加)- 条件编译——解决多重包含问题
#ifndef _HEADNAME_H#define _HEADNAME_H#endif
8.include<> vs include ""
- <>引用的是编译器类库路径里面的头文件
- “”引用的是程序相对路径(当前目录下)的头文件,如果找不到就到编译器的类库文件中找
9.class和struct
- 默认访问权限和继承权限不同。class——private;struct——public
- class可以用于表示模板类型,struct不行
10.C/C++程序执行过程
- 预处理——对源文件进行宏替换,条件编译,头文件展开,去除注释
- 编译——将上一部处理后的文件进行语法分析,语义分析,以及优化后产生汇编代码文件
- 汇编——将编译出的代码翻译成机器指令
- 链接——将所有文件链接到一起生成一个可执行的程序
链接可分为静态和动态:
- 静态链接/lib——lib中所有的指令都被直接包含在最终生成的exe文件中
- 动态链接/dll——dll不必包含在最终的exe中,exe文件执行可动态引用或卸载dll,这样可以节省内存。dll和exe相互独立,只要接口不变,更换dll就不会影响,故极大提高了扩展性和可维护性。
11.内联函数
为了解决函数调用的效率问题
程序在编译器编译时,编译器将程序中出现的内敛函数的调用表达式用内内联函数的函数体表示。而对于其他的函数都是运行时才被替换。
空间换时间
inline int add(int x, int y)
{
return(2 * x + y);
}
12.缓冲区/缓冲
在内存空间预留了一定的储存空间,用来缓冲输入输出的数据
用在输入输出设备和CPU之间
- 全缓冲:当填满标准I/O缓存后才执行实际的I/O操作(例如对磁盘的读写)
- 行缓冲:遇到换行符才进行实际I/O操作(例如键盘输入数据);endl,flush
- 不带缓冲:标准出错情况(stderr),使错误信息尽快显示出来
13.有继承时,析构函数必须为虚函数
class Base
{
public:
Base() = default;
virtual ~Base()
{
cout << "Base's destructor called" << endl;
}
};
class Derived : public Base
{
public:
Derived() = default;
~Derived()
{
cout << "Derived's destructor called" << endl;
}
};
int main()
{
Base *p = new Derived();
delete p;
return(0);
}
输出Derived's destructor called
Base's destructor called
如果基类析构函数非虚,则只调用基类析构函数,不调用派生类析构函数,造成内存泄漏Base's destructor called
14.深拷贝和浅拷贝
深拷贝会在堆上申请空间来储存数据
当数据成员有指针时,必须使用深拷贝。(有指针时,浅拷贝两个指针会指向同一地址)
15.指针函数和函数指针
指针函数
int* fun(int x, int y)
{
int *p = new int;
*p = x + y;
return(p);
}
//本质是一个函数,返回值为指针
函数指针
int(*fun)(int x, int y);
int add(int x, int y)
{
return(2 * x + y);
}
int main()
{
fun = add;
cout << (*fun)(2, 2) << endl;
}
//本质是一个指针,指向一个函数
16.内存管理
堆、栈、自由储存区、全局/静态 储存区 和 常量储存区
自由储存区:抽象概念——凡是由new()申请的空间就叫自由储存区
new()除了调用系统默认的构造函数外,还会给基本数据类型和指针类型成员用0赋值。
所谓内存管理,用的就是new和delete。请见第1页第6项。



Comments | NOTHING