malloc 和 free,称作 c 的库函数
new 和 delete,称作运算符
new不仅可以做内存开辟,还可以做内存初始化操作
malloc开辟内存失败,是通过返回值和nullptr做比较:而new开辟内存失败,是通过抛出bad_alloc类型的异常来判断的。
new 可以认为是 malloc + 构造函数, delete 可以认为是 free + 析构函数
引用是一种更安全的指针。
右值引用
const 修饰的变量不能够再作为左值!!初始化完成后,值不能被修改!!
C中,const就是当作一个变量来编译生成指令的。C++中,所有出现const常量名字的地方,都被常量的初始化替换了!!!
const修饰的量常出现的错误是:
常量不能再作为左值 = 直接修改常量的值
不能把常量的地址泄露给—个普通的指针或者普通的引用变量 = 间接修改常量的
const和一级指针的结合
c++的语言规范:const修饰的是离它最近的类型
const int *p = &a; *p = 20 p = &b
可以任意指向不同的int类型的内存,但是不能通过指针间接修改指向的内存的值
总结 const 和指针的类型转换公式:
int* const int* //是错误的!
const int* int* //是可以的!
int** const int** //是错误的!
const int** int** //是错误的!
int** int*const* //是错误的!
int*const* int** //是可以的!
inline 内联函数:在编译过程中,就没有函数的调用开销了,在函数的调用点直接把函数的代码进行展开处理了。
inline 只是建议编译器把这个函数处理成内联函数。但是不是所有的 inline 函数都会被编译器处理为内联函数,例如 递归函数。
debug 时候,inline 不起作用。inline 只有在 release 下才起作用。
c++代码产生函数符号的时候,函数名+参数列表类型组成的!
c代码产生函数符号的时候,函数名来决定!
静态:编译时期的多态。(多种多样的形态)。如 函数重载
动态:运行时期的多态
把 C++ 源码括在 extern “C” 里面。还是在 C++ 代码里面写 extern C。
把 C 函数的声明括在 extern “C” 里面
extern "C"{
int sum(int a, int b){
return a + b;
}
}
只要是 C++ 编译器,都内置了 _cplusplus 这个宏名
#ifdef __cplusplus
extern "C"{
#endif
int sum(int a, int b){
return a + b;
}
#ifdef __cplusplus
}
#endif
面向对象程序设计 Object-oriented programming
抽象。 封装/隐藏。 继承。 多态。
this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
构造函数可以带参数,因此可以提供多个构造函数。定义对象时自动调用;可以重载;构造完成,对象就产生了。
析构函数不带参数,所以析构函数只能有一个。不带参数;不能重载,只有一个;析构完成,对象就不存在了。
析构函数调用以后,对象就不存在了。不能再调用对应方法了(堆内存的非法访问)
程序启动时构造,结束时析构
new 时候构造,delete 时候析构。new = malloc(内存开辟)+ 构造函数。 delete = 析构 + free(内存释放)
进函数到定义的地方构造,出函数作用域析构
SeqStack s; //没有提供任何构造函数时,会为你生成默认构造函数和析构函数
SeqStack s1(10);
SeqStack s2 = s1; // #1 会调用拷贝构造函数 //浅拷贝,对象默认的拷贝构造是做内存的数据拷贝,关键是对象如果占用外部资源, //那么浅拷贝就出问题了。因为它们会指向同一个内存指针,这时候s2去释放内存,s1就有问题了。
SeqStack s3(s1); // #2。 #1与#2一样
浅拷贝:自定义拷贝构造函数和赋值重载函数
void push(int val){ // 入队操作
if (full()){
resize();
}
_pQue[_rear] = val;
_rear = (_rear + 1) % _size;
}
void pop(){ // 出队
if(empty()){
return;
}
_front = (_front + 1) % _size;
}
void resize(){ // 扩容
int *ptmp = new int[2 * _size];
int index = 0;
for(int i = _front; i != _rear; i = (i + 1) % _size){
ptmp[index++] = _pQue[i];
}
delete _pQue;
_pQue = _ptmp;
_front = 0;
_rear = index;
_size *= 2;
}
可以指定当前对象成员变量的初始化方式
编译器会添加一个 this 形参变量
不会生成 this 形参
static void hanshu(){}
没有 this 指针,可以直接用类名调用。当想访问所有方法共享的信息时,可以用静态方法。
而普通方法会产生 this 指针,需要调用一个对象,接受一个对象的地址。
static int _count;
不属于对象,而是属于类级别的声明
const CGoods *this
// 函数模板,是不进行编译的,因为类型还不知道
// 函数实例化,函数调用点进行实例化
// 模板函数,才是要被编译器所编译的
// 模板的实参推演,可以根据用户传入的实参的类型,来推导出模板类型
// 模板的特例化(专用化),特殊(不是编译器提供的,而是用户提供的)实例化
template //定义一个模板参数列表
bool compare(T a, T b) // compare 是一个函数模板
{
cout << "template compare" << endl;
return a > b;
}
// 在函数调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来
/*
bool compare(int a, int b) // compare 是一个函数模板
{
return a > b;
}
*/
// 针对 compare 函数模板,提供 const char* 类型的特例化版本
template<>
bool compare(const char *a, const char *b)
{
return strcmp(a, b) > 0;
}
int main()
{
// 函数调用点
compare (10, 20);
compare (10.5, 20.5);
return 0;
}
模板代码是不能在一个文件定义,在另一个文件使用
模板代码调用之前,一定要看到模板定义的地方,这样的话,模板才能够进行正常的实例化,产生能够被编译器编译的代码
所以,模板代码都是放在头文件中,然后在源代码当中直接进行 #include
包含。
做四件事情, 内存开辟/内存释放 对象构造/对象析构
template
class vector
{
public:
vector(int size = 10)
{
_first = new T[size];
_last = _first;
_end = _first + size;
}
~vector()
{
delete[] _first;
_first = _last = _end = nullptr;
}
vector(const vector &rhs)
{
int size = rhs._end - rhs._first;
_first = new T[size];
}
private:
T *_first; // 指向数组起始的位置
T *_last;// 指向数组中有效元素的后继位置
T *_end;// 指向数组空间的后继位置
}
编译器做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法);如果没有成员方法,就在全局作用域找合适的运算符重载函数。
#include
#include
using namespace std;
struct Point
{
double x;
double y;
Point()
{
x = 0;
y = 0;
}
};
int main()
{
vector m_testPoint;
m_testPoint.clear();
m_testPoint.shrink_to_fit();
for (int i = 0; i<10; ++i)
{
Point temp;
temp.x = i*i;
temp.y = i*i;
m_testPoint.push_back(temp);
}
//第一种遍历方式,下标
cout << "第一种遍历方式,下标访问" << endl;
for (int i = 0; i::iterator iter = m_testPoint.begin(); iter != m_testPoint.end(); iter++)
{
cout << (*iter).x << " " << (*iter).y << endl;
}
//第三种遍历方式,auto关键字
cout << "C++11,第三种遍历方式,auto关键字" << endl;
for (auto iter = m_testPoint.begin(); iter != m_testPoint.end(); iter++)
{
cout << (*iter).x << " " << (*iter).y << endl;
}
//第四种遍历方式,auto关键字的另一种方式
cout << "C++11,第四种遍历方式,auto关键字" << endl;
for (auto i : m_testPoint)
{
cout << i.x << " " << i.y << endl;
}
return 0;
}
重看
未看
new int [10]
。所以 malloc 开辟内存返回的都是void* operator new -> int*
继承的本质:a. 代码的复用 b.
类和类之间的关系:
组合: a part of … …一部分的关系
继承: a kind of … …一种的关系
private 只有自己或者友元可以访问私有的成员
继承方式 | 基类的访问限定 | 派生类的访问限定 | (main)外部的访问限定 |
---|---|---|---|
public (class B : public A) | public | public 派生类里面可以访问 |
外部可以访问 |
protected | protected 派生类里面可以访问 |
外部不可以访问 | |
private | 不可见 派生类里面不可以访问 |
外部不可以访问 | |
protected(class B: protected A) | public | protected 相当于降级为 protected |
外部不可以访问 |
protected | protected 派生类里面可以访问 |
外部不可以访问 | |
private | 不可见 派生类里面不可以访问 |
外部不可以访问 | |
protected(class B: private A) | public | private 相当于降级为 private 派生类里面可以访问 |
外部不可以访问 |
protected | private 相当于降级为 private 派生类里面可以访问 |
外部不可以访问 | |
private | 不可见 派生类里面不可以访问 |
外部不可以访问 |
总结:
默认的继承方式是什么?
要看 派生类是用 class 定义的,还是 struct 定义的。
class 定义派生类,默认继承方式是 private 私有的。struct 定义派生类,默认继承方式是 public 公有的。
class 的成员默认是 private 权限,struct 默认是 public 权限。(与上面继承对应)
派生类从基类可以继承来所有的成员(变量和方法),除构造函数和析构函数
派生类怎么初始化从基类继承来的成员变量呢?
通过调用基类相应的构造函数来初始化。
派生类的构造函数和析构函数,负责初始化和清理派生类部分
派生类从基类继承来的成员由基类的构造函数和析构函数负责。
派生类对象构造和析构的过程是:
派生类调用基类的构造函数,初始化从基类继承来的成员
调用派生类自己的构造函数,初始化派生类自己特有的成员
… 派生类对象的作用域到期了
调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)
一组函数要重载,必须处在同一作用域中;而且函数名字相同,参数列表不同
在继承结构中,派生类的同名成员把基类的同名成员给隐藏了,也就是调用的时候调用的是派生类的成员函数。要调用基类那就加作用域,(比如 Base::show)
把继承结构也说成从上(基类)到下(派生类)的结构
Base(10);
Derive(20);
b = d; // 基类对象b <- 派生类对象d 类型从下到上的转换 允许
d = b; // 派生类对象d <- 基类对象b 类型从上到下的转换 不允许
Base *pb = &d; // 基类指针(引用) <- 派生类对象 类型从下到上的转换 允许
Derive *pd = &b; // 派生类指针(引用) <- 基类对象。 类型从上到下的转换 不允许
// 在继承结构中进行上下的类型转换,默认只支持从下到上的类型转换
静态绑定:静态–编译时期;绑定–函数的调用
动态绑定:动态–运行时期;绑定–函数的调用
覆盖:基类和派生类的方法,返回值、函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,它们之间成为覆盖关系(虚函数表中虚函数地址的覆盖。派生类存在和基类 返回值、函数名、参数列表都相同的函数,会进行覆盖,相当于重写。
)
如果发现函数是普通函数,就进行静态绑定
如果发现函数是虚函数,就进行动态绑定了。
cl *.cpp /dlreportSingleClassLayoutDerive
虚函数依赖:
构造函数:
派生类对象构造过程 先调用基类的构造函数,然后才调用派生类的构造函数
虚函数和动态绑定的问题:是不是虚函数的调用一定就是动态绑定? 肯定不是的!
在类的构造函数中,调用虚函数,也是静态绑定(构造函数中调用其他函数(虚),不会发生动态绑定)
用对象本身调用虚函数,属于静态绑定
动态绑定,必须由指针调用虚函数(Base *pb1 = &b;
),或者必须由引用变量调用虚函数(Base &rb1 = b;
)
虚函数通过指针或者引用变量调用,才发生动态绑定
静态(编译时期)的多态:函数重载、模板(函数模板和类模板)
动态(运行时期)多态:在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数)。基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为多态。
多态底层是通过动态绑定来实现的。
还没看!!!
多重继承:代码的复用 一个派生类有多个基类
抽象类(有纯虚函数的类) / 虚基类
看虚基类这一章
修饰成员方法是虚函数
可以修饰继承方式,是虚继承。被虚继承的类,称作虚基类);
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址
C++ 语言级别提供的四种类型转换方式
int a = (int) b;
const_cast : 去掉(指针或者引用)常量属性的一个类型转换
static_cast : 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换就被否定)
reinterpret_cast : 类似于 C 风格的强制类型转换
dynamic_cast : 主要用在继承结构中,可以支持RTTI类型识别的上下转换
stl六大组件
vector
deque
list
stack
queue
priority queue
无序关联容器
链式哈希表 增删查 O(1)
unordered_set
unordered_multiset
unordered_map
unordered_multimap
有序关联容器
红黑树 增删查O( l o g 2 n log_2{n} log2n) 2是底数(树的层数,树的高度)
set
multiset
map
multimap
数组,string,bitset
iterator 和 const_iterator
reverse_iterator 和 const_reverse_iterator
sort, find, find_if, binary_search, for_each
向量容器。
底层数据结构:动态开辟的数组,每次以原来空间大小的 2 倍进行扩容。
vector vec;
// 增加
vec.push_back(20); //末尾添加元素 O(1) 导致容器扩容
vec.insert(it, 20); // it 迭代器指向的位置添加一个元素 20 O(n) 导致容器扩容
// 删除
vec.pop_back(); // 末尾删除元素 O(1)
vec.erase(it); // 删除 it 迭代器指向的元素 O(1)
// 查询
operator[] // 下标的随机访问 vec[5] O(1)
// iterator迭代器进行遍历。(推荐)
// find, for_each
// foreach 底层就是通过 iterator 实现的。
// 注意!对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次 insert 或者 erase 完成,迭代器就失效了。
常用方法介绍:
size()
empty()
reserve(20): vector 预留空间的 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize(20): 容器扩容用的
swap: 两个容器进行元素交换
双端队列容器
底层数据结构:动态开辟的二维数组,一维数组从 2 开始,以 2 倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标 oldsize/2 开始存放,上下都预留相同的空行,方便支持 deque 的首尾元素添加。
deque deq;
// 增加
deq.push_back(20); // 从末尾添加 O(1)
deq.push_front(20); // 从首部添加元素 O(1)
deq.insert(it, 20); // it 指向的位置添加元素 O(n)
// 删除
deq.pop_back(); // 从末尾删除元素 O(1)
deq.pop_front(); // 从首部删除元素 O(1)
deq.erase(it); // 从 it 指向的位置删除元素 O(n)
//查询搜索
// iterator(连续的 insert 和 erase 一定要考虑迭代器失效的问题)
链表容器
底层数据结构:双向的循环链表 pre data next
list mylist;
// 增加
mylist.push_back(20); // 从末尾添加 O(1)
mylist.push_front(20); // 从首部添加元素 O(1)
// 链表中进行 insert 的时候,先要进行一个 query 查询操作,对于链表来说,查询操作效率就比较慢了
mylist.insert(it, 20); // it 指向的位置添加元素 O(1)
// 删除
mylist.pop_back(); // 从末尾删除元素 O(1)
mylist.pop_front(); // 从首部删除元素 O(1)
mylist.erase(it); // 从 it 指向的位置删除元素 O(1)
//查询搜索
// iterator(连续的 insert 和 erase 一定要考虑迭代器失效的问题)
deque 和 list 比 vector 容器多出来的增加删除函数接口:push_front 和 pop_front