all_note
进程的虚拟地址空间内存划分和布局
编程语言->产生指令和数据
- 程序生成exe可执行文件,加载到内存后(不是一步直接加载到物理内存中)如何存放。
- x86 32位linux下,linux会给进程分配一块2的32次方大小的一块空间(4G),这块空间是一块虚拟内存空间,虚拟内存空间本质上是系列数据结构。
- 这一块虚拟地址空间分为两块,3G的用户空间,1G的内核空间。
- 地址起始的一段内容是不可访问的,不可读写。
- .text为代码段,.rodata为read only只读数据段(常量区,只读不写)
- .data数据段,.bss也是数据段:前者存储初始化且不为0的数据,后者存储未初始化或初始化为0的。如一个全局变量默认初始化为0,是内核启动阶段操作系统默认对bss中的未初始化变量赋值0。
- .heap堆内存栈自上而下增长,即低地址->高地址。
- 加载的共享库即动态库。
- 函数运行,或产生线程时,都记录在栈stack上,栈自下而上增长,即高地址->低地址。
- 最后一段是命令行参数和环境变量。
- 内核空间。
图中gdata1-gdata6编译后产生数据于符号表,其中gdata1,gdata4存放于.data段;gdata2,gdata3,gdata5,gdata6存放于.bss段;a,b,c属于栈中的局部变量,链接器对此类变量不感兴趣,符号表不会记录,相反,这三行会产生三条指令。e存放于.data段;f,g存放于.bss段。打印c会显示栈上存放的无效值,打印g则输出0,因为g存储在bss段,自动初始化为0。
红色框存放于代码段.text,蓝色与棕色狂产生变量符号,存放于数据段。
a-c汇编后生成指令存放于代码段,代码执行期间,系统为main函数开辟栈,执行指令将abc的值存放到对应存储空间
每一个进程的用户空间是私有的,内核空间是共享的,进程间可以通过向内核空间中读写数据实现共享。
进程的通信方式:匿名管道通信
函数的调用堆栈详细过程
- 第一句:初始化a,符号表中不产生a变量,仅在代码段中保存此句汇编代码,即mov语句,mov语句把10赋值给栈指针
- 第二局:同第一句
- 第三局:将实参的值传给形参,调用sum实现相加函数。
- 首先,sum函数的参数a,b存为寄存器变量并压栈(push,注意压栈是将变量压到栈顶esp),其中形参压栈顺序是从右向左。然后call调用的sum函数,然后立即将call的下一条语句在.text区中的内存地址压栈,该地址标记sum返回后下一步运行的位置。
- 进入sum后,执行代码前,要先压入ebp栈底地址(标记main),然后开新栈帧:mov ebp, esp,然后esp移动。
- 执行sum内部的几条语句
- 将返回结果保存至寄存器变量中
- 栈帧回退:mov esp, ebp,即将栈顶指针指回栈底。这个过程中,栈被释放,但内容没有被清理,如果一个函数func1返回一个指针,在main中访问该指针指向的值,依然可以访问到,但不安全;如果后续有func2调用,从而建立新的栈帧,那么这块内存可能会被修改。
- pop ebp弹栈:即将之前压入的ebp栈底地址赋值回给ebp,这意味着函数执行返回了main
- ret把再次出栈的内容放入CPU的PC(程序计数)寄存器中,继续执行call sum的下一行语句,并继续把两个形参变量所占用的栈空间交还系统,栈顶地址-8
程序编译与链接原理
- 预编译:处理#命令,但保留#pragma,删除注释。
- 编译:词法分析、语法分析、语义分析和优化,生成汇编代码。
- 汇编:将汇编代码翻译成机器码(AT&T,x86语法),打包为可重定位二进制目标文件,此文件不可执行。输出符号表。
- 链接:合并.o文件段,合并符号表,解析并符号重定向。
//以下为main文件
extern int gdata; //gdata *UND*代表需使用但未定义
int sum(int, int); //sum *UND*
int data = 20; //data .data
int main(){ //main .text
int a = gdata;
int b = data;
int ret = sum(a, b);
return 0;
}
//以下为sum文件
int gdata = 10; //gdata .data
int sum(int a, int b){ //sum_int_int .text
return a + b;
}
以上为.o文件中的部分符号注释
具体的.o文件的格式组成包括各种段
编译过程中不为符号分配地址,通过readelf可以观察到地址皆为0。虽然指令已经在编译阶段翻译好,但变量地址皆为0,需要在链接阶段补充,也因此.o文件无法执行。
链接
链接过程合并若干个.o文件的段合并:text段合并,data段合并...,以及符号表的合并。
-
符号表合并:检查符号表,所有对符号的引用(*UND*),都要找到该符号定义的地方,当然符号定义只能有一个,不可重定义,不可没有。最终符号解析成功。
-
符号解析成功后,在代码段中,为所有符号分配虚拟地址,并把这些地址写回指令中。这个过程叫做符号重定向。
可执行文件
链接产生可执行文件,可执行文件与二进制可重定向文件段格式几乎一致,但可执行文件中多了一个program headers段,这其中包含若干load项,比如.text,.data,这意味着这些load项要在代码执行时,加载如虚拟内存空间
参数为默认值的函数
- 参数从右面开始给默认值。
- 如果不缺省参数,需要在汇编层面mov实参到寄存器,并push压栈此寄存器值;而如果缺省一个参数,在汇编代码层面,相当于少一句mov指令,而可以直接push一个立即数。从而提高效率。
- 定义和声明两阶段都可以给默认值,但二者只能给一次,即使定义和声明两阶段给了相同的默认值也不可以。不过有下述情况。
-
#include<iostream> using namespace std; int sum(int a, int b = 10); int sum(int a = 10, int b); //相当于int sum(int a = 10, int b = 10) int main(){ int a = 20; cout << sum() << endl; cout << sum(10); } int sum(int a, int b){ return a + b; }
内联函数
内联函数与普通函数的区别
inline函数:在编译过程中,就没有函数的调用开销了,在函数的调用点直接把函数的代码进行展开处理,符号表中也不产生内联函数符号。
- 函数的调用开销是什么:参数压栈,栈帧开辟,栈帧回退。
- inline只是建议,不是所有inline被编译器处理为内联函数,比如递归,编译器无法确定递归要执行多少次。大量出现的简单代码时候作为内联。
- debug版本下,inline不起作用,因为会导致无法调试。inline只在release版本下起作用。
函数重载
什么是函数重载:一组函数,函数名相同,但参数类型或个数不同。
C++为什么支持函数重载,但C不支持
- 本质是编译器产生符号的规则不一样:C++代码产生函数符号,由函数名和参数列表类型组成;而C代码产生函数符号,仅由函数名组成。当链接时,会发生符号的重定义。
重载的注意点
-
重载的前提是几个函数在同一作用域下。
//下述情况下可以正确调用对应的函数 #include<cstring> #include<iostream> using namespace std; bool compare(int a, int b){ cout << "int" << endl; return (a>b); } bool compare(double a, double b){ cout << "double" << endl; return(a>b); } bool compare(const char * a, const char * b){ cout << "double" << endl; return strcmp(a, b); } int main(){ compare(1, 2); compare(1.0, 2.0); compare("aa", "vv"); return 0; }
//下述情况下无法正确调用对应的函数 #include<cstring> #include<iostream> using namespace std; bool compare(int a, int b){ cout << "int" << endl; return (a>b); } bool compare(double a, double b){ cout << "double" << endl; return(a>b); } bool compare(const char * a, const char * b){ cout << "double" << endl; return strcmp(a, b); } int main(){ bool compare(int, int);//当在局部声明了一个compare函数 compare(1, 2); //后续的所有compare函数在局部作用域即找到了可调用的函数,则不会继续向全局作用域寻找其他conpare函数 compare(1.0, 2.0); //会double强制类型转化int compare("aa", "vv"); //报错,无法强制类型转换 return 0; }
-
const和volitale修饰形参,怎么影响形参类型的? 待补坑
-
一组函数的函数名和参数相同,返回类型不同,则不算重载。
-
什么是多态:编译期的静态多态:包含重载;运行期的动态多态。编译时期,指令就确定要重载哪个函数。
C++和C之间如何互相调用
-
下述为C++主程序调用C函数
//C规则下,只依据函数名生成符号表 int sum(int a, int b){ //sum .text return a + b; }
//C++规则下,依据函数名与形参生成符号表 int sum(int a, int b); //sum_int_int "UND", 在链接时,无法匹配C中的符号sum int main(){ int ret = sum(10, 20); cout << "ret" << ret << endl; return 0; }
extern "C" //这里把C函数的声明加入extern C中 { int sum(int a, int b); //按照C编译生成符号表 sum “UND” } int main(){ int ret = sum(10, 20); cout << "ret" << ret << endl; return 0; }
-
同样,如果C主程序调用C++
extern "C" //cpp文件按照C编译 { int sum(int a, int b){ //sum .text return a + b; } }
int sum(int a, int b); //sum “UND” int main(){ int ret = sum(10, 20); cout << "ret" << ret << endl; return 0; }
-
经常写作
#ifdef __cplusplus //C++编译器内置的宏 extern "C"{ #endif //C++代码 #ifdef __cplusplus } #endif
const
怎么理解const?
- const修饰的变量不能再作为左值。即初始化后不能被修改。
C和C++中的const有什么区别?
-
C中const量可以只定义,但不被初始化(之后无法再赋值),称作常变量。事实上,通过指针仍可修改变量的值。
-
C++中的const必须初始化,称作常量。通过指针不可修改变量的值。
-
C和C++中对const变量的编译方式不同,C中const当作一个变量编译生成;C++中出现const常量名字的地方,都被常量的初始值替换,包括*(&a)这样的形式,也是直接当作a,替换。事实上,a所处的内存上的值已经被替换。
-
如果C++中const初始化为另一个变量,也是常变量。
int main() { const int a = 2; int arr[a]; //a是常变量,报错 int* p = (int *) & a; *p = 20; cout << a << endl << *p << endl << *(&a); //三者都是20。 }
int main() { const int a = 2; int arr[a]; //a是常量,没问题 int* p = (int *) & a; *p = 20; cout << a << endl << *p << endl << *(&a); //a依然是2,*p是20,*(&a)依然是2。 }
int main() { int c = 5; const int d = c; //即使是在C++中,这种初始化const变量的方式也只生成常变量。 cout << d << endl; int* p = (int*)&d; *p = 4; cout << d << endl; //可以被修改。 }
const修饰的量常出现错误:
- 常量不能直接作为左值(不可直接修改)
- 不能把常量的地址泄露给一个普通指针或普通引用(不可间接修改)
int main() { const int a = 5; int* p = &a; //不能把const int*转换为int*,但可以int*转换为const int* }
const结合一级指针
C++语言规范:const修饰的是离它最近的类型(*不能单独作为类型)
//一般使用前两种保护常量。
const int *p; //const修饰int,即指针所指向的值,p指向内容不变,但p的指向可变。
int const *p; //const修饰int,同上。
int *const p; //const修饰int *,指针p为常量,指向不可变,但可通过指针解引用修改指向内容。
const int *const p; //p指向const int且p的指向不变。
总结:
int * <- const int * //错误
const int * <- int * //正确
int main()
{
//const右面没有指针*的话,const不参与类型。二者类型皆为int *,二者也可相互赋值
int* p1 = nullptr;
int* const p2 = nullptr;
p1 = p2;
cout << typeid(p1).name() << endl;
cout << typeid(p2).name() << endl;
}
const结合二级(多级)指针
int main()
{
int a = 0;
int* p = &a;
//以下情况均错误
//const int** q = &p; //const修饰int,二次指向的值不能变,但q,*q可被赋值。同时也把const int暴露给了*p。
//int* const* q = &p; //const修饰前方int *,*q不能改变,但q,**q可被赋值。
//int** const q = &p; //const修饰前方int **,q不能改变,但*q,**q可被赋值。
const int *const* q = &p; //const修饰int和*q,即**q和*q都不能改变,q指向可改变。
}
总结:
int ** <- const int ** //错误
const int ** <- int ** //错误
int ** <- int* const* //错误
int* const* <- int ** //正确
引用
引用和指针的区别
-
引用是一种更安全的指针。
-
引用必须初始化,指针可以不初始化。引用初始化为另一个同类变量,特殊情况下可初始化为常量。
-
引用只有一级,没有多级引用。
-
从汇编指令层面看。对引用的处理,是采用指针的方法。因为引用是一种const指针。通过引用变量修改所引用内存的值,与通过指针解引用修改指针指向内存的值,二者的底层指令也是一样的。
-
数组如何被引用?
int a; int *b = &a; int &b = a; int array[5]; int (*p)[5] = &array int (&p)[5] = array
-
引用虽然本质是指针,但使用时,总是要先进行解引用,因此进行sizeof运算,依然为所引用变量的字节数。
左值引用和右值引用
- 左值:有内存,有名字,值是可以修改的。
- 右值:反之。
- C++11提供了右值引用:右值引用也必须立即进行初始化操作,且只能使用右值进行初始化。右值引用可以修改右值。
- 右值引用专门引用右值,不能引用左值。
- 右值引用变量本身是一个左值,只能用左值引用引用它。
- 指令上,自动产生临时量,然后直接引用临时量。
//一般情况下,C++只允许左值引用,而不允许右值引用 int num = 10; const int &b = num; //正确 const int &c = 10; //错误 //右值引用 int num = 10; //int && a = num; //右值引用不能初始化为左值 int && a = 10; //指令层面,临时量xx=20,然后a引用xx a = 11; //右值引用可以修改右值
const,一级指针,引用的结合使用
为了观察赋值是否可行,可以先将引用换成指针的写法:引用号&修改为*,等式右侧加上取地址符&,然后比较等式两边的类型是否符合赋值规范。video
int main()
{
//int& p = 10 //错误
const int& p = 10; //正确,产生临时量,p是临时量的引用
int* const& q = (int*)0x0018ff44; //q是const修饰的int指针,不能写成const int* &q
}
int main()
{
int a = 1;
int* p = &a;
const int *& q = p;
//相当于const int ** q = &p; const int **和int **不能相互赋值。
return 0;
}
new和delete
new和malloc区别,delete和free区别
- malloc和free,是C的库函数;new和delete,是运算符。
- new不仅内存开辟,还能做内存的初始化,返回指针,如果开辟失败,抛出异常bad_alloc,而不是返回空指针;malloc只能进行内存开辟,返回指针,如果开辟失败,返回nullptr。malloc需要类型强制转换,new不需要。
- new有多少种?
int main()
{
int* p1 = new int(10);
int* p2 = new(nothrow)int;
const int* p3 = new const int(40);
//定位new,固定位置new一个对象
int data = 0;
int* p4 = new(&data) int(20);
cout << data;
}
类和对象,this
面向对象的四大特性:抽象,封装/隐藏,继承,多态
- 属性一般都是私有的,向外提供公有方法以访问私有属性。
- 类本身不占内存,实例化对象占内存。
- 类体内定义的成员函数默认为内联,体外定义需要加inline修饰。
- 一个类可以定义无数的对象,每一个对象都有自己的成员变量,成员函数是公用的,不占对象的存储空间。
- 那么方法被调用时,如何知道所处理的对象?方法调用时,隐式传入了this指针。
#include <iostream> #include<cstring> #define MAX 10 using namespace std; class Goods { //成员方法总是驼峰命名 public: //所有成员函数在参数中默认加入this指针 Goods(char *name, double price, int amount) : _price(price), _amount(amount) {strcpy(_name, name);} void show(); void setName(char *name) { strcpy(_name, name); } void setPrice(double price) { _price = price; } void setAmount(int amount) { _amount = amount; } const char *getName(){return _name;} double getPrice(){return _price;} int getAmount(){return _amount;} private: char _name[MAX]; double _price; int _amount; }; void Goods::show(){ cout << "Name:" << this->getName() << endl; cout << "Price:" << this->getPrice() <<endl; cout << "Amount:" << this->getAmount() <<endl; } int main(){ Goods a("buf", 30, 200); a.show(); }
构造函数和析构函数
#include<iostream>
using namespace std;
class seqStack {
private:
int* _pstack;
int _size;
int _top;
public:
seqStack(int size) {
_size = size;
_pstack = new int[size];
_top = -1;
}
~seqStack() {
delete[]_pstack;
_pstack = nullptr;
}
int full() {
return _top == _size - 1;
}
void resize() {
int* newstack = new int[2 * _size];
for (int i = 0; i < _size; i++) {
newstack[i] = _pstack[i];
//这里要慎用memcpy和realloc,参考深拷贝与浅拷贝
}
_pstack = newstack;
_size *= 2;
}
int empty() {
return _top == -1;
}
void push(int val) {
if (full()) {
resize();
}
_pstack[++_top] = val;
}
void pop() {
if (empty()) {
return;
}
cout << _pstack[_top--] << endl;
}
};
int main() {
seqStack st(4);
for (int i = 0; i < 10; i++) {
st.push(rand() % 100);
}
cout << sizeof(st) <<endl;
while (!st.empty()) {
st.pop();
}
//st是栈上的对象。离开作用域(本函数),析构自动调用。
return 0;
}
- 构造函数可以带参,可以重载。
- 析构函数不带参数,只能有一个。
- 构造和析构类似入栈和出栈,先构造后析构,后构造先析构。
- 堆上的类对象,不会在程序结束后自动释放,需要手动释放。因此一定要手动调用析构函数
int main() {
seqStack* st = new seqStack(4);
for (int i = 0; i < 10; i++) {
st->push(rand() % 100);
}
cout << sizeof(st) <<endl;
while (!st->empty()) {
st->op();
}
delete st;
//st是栈上的指针对象,但它指向的堆上的对象会一直占据空间。
return 0;
}
- 栈上初始化一个类对象,两步:开内存,调用构造函数。
- 堆上new一个类对象,两步:malloc申请一段空间,调用构造函数。
- free和delete都用来释放堆上申请的空间,它们在释放类对象的区别是什么?delete释放类对象是两步:
- 调用此类的析构函数。
- free类对象的指针。
深拷贝和浅拷贝
- 没有提供任何构造函数的时候,系统默认生成构造和析构函数。
- 当需要使用拷贝构造函数是,系统默认生成拷贝构造函数,是内存拷贝。
class seqStack {
private:
int* _pstack;
int _size;
int _top;
public:
seqStack(int size) {
_size = size;
_pstack = new int[size];
_top = -1;
}
~seqStack() {
delete[]_pstack;
_pstack = nullptr;
}
};
int main() {
seqStack s1(10);
seqStack s2 = s1; // 默认拷贝构造,属于内存拷贝,是一种浅拷贝。
//析构顺序与构造顺序相反,先析构s2,delete了栈空间,s1析构再次释放栈空间会报错。
}
- 对象如果占用外部资源,浅拷贝会出现问题,应转为深拷贝。
- 深拷贝:不仅做内存拷贝,还做外部资源的内容拷贝。
class seqStack {
// private:
// int* _pstack;
// int _size;
// int _top;
public:
seqStack(int size) {
_size = size;
_pstack = new int[size];
_top = -1;
}
~seqStack() {
delete[]_pstack;
_pstack = nullptr;
}
//自定义拷贝构造函数
seqStack(const seqStack &seq) {
_pstack = new int[seq._size];
for (int i = 0; i <= seq._top; i++) {
_pstack[i] = seq._pstack[i];
}
_size = seq._size;
_top = seq._top;
}
//重载等号运算符
//如果需要支持连续赋值,重载等号运算符函数应为seqStack类型,返回*this
void operator=(const seqStack& seq) {
if (this == &seq) //防止自赋值
return;
delete[] _pstack;
_pstack = new int[seq._size];
for (int i = 0; i <= seq._top; i++) {
_pstack[i] = seq._pstack[i];
}
_size = seq._size;
_top = seq._top;
}
};
int main() {
seqStack s1(10);
seqStack s2(s1); //调用拷贝构造函数
seqStack s3 = s1; //调用拷贝构造函数
//默认的等号运算符是内存拷贝,浅拷贝
s3 = s1;// //已经不是初始化阶段了,重载等号运算符
}
类和对象代码实例
class Queue {
private:
int* _pQue;
int _front;
int _rear;
int _size;
public:
Queue(int size = 5) {
cout << "构造" << endl;
_size = size;
_pQue = new int[size];
_front = _rear = 0;
}
~Queue() {
cout << "析构" << endl;
delete[]_pQue;
_pQue = nullptr;
}
Queue(const Queue& src) {
cout << "拷贝构造" << endl;
if (&src == this) {
return;
}
//深拷贝
delete[]_pQue;
_pQue = new int[src._size];
_size = src._size;
_front = src._front;
_rear = src._rear;
for (int i = _front; i != _rear; i = (i + 1) % _size) {
_pQue[i] = src._pQue[i];
}
}
void push(int val) {
if (full()) {
resize();
}
_pQue[_rear] = val;
_rear = (_rear + 1) % _size;
}
void pop() {
if (empty()) {
return;
}
cout << _pQue[_front] << endl;
_front = (_front + 1) % _size;
}
int full() {
return _front == (_rear + 1) % _size;
}
int empty() {
return _rear == _front;
}
int top() {
return _pQue[_front];
}
void resize() {
int* newqueue = new int[2 * _size];
int index = 0;
for (int i = _front; i != _rear; i = (i + 1) % _size) {
newqueue[index++] = _pQue[i];
}
delete[]_pQue;
_pQue = newqueue;
_front = 0;
_rear = index;
_size *= 2;
}
};
int main() {
Queue qq;
for (int i = 0; i < 20; i++) {
qq.push(rand() % 100);
}
Queue pp = qq;
while (!qq.empty()) {
qq.pop();
}
while (!pp.empty()) {
pp.pop();
}
return 0;
}
构造函数的初始化列表
- 构造函数的初始化列表是一种特殊的初始化方式,用于在构造函数中初始化类的成员变量。
- 参数化列表初始化相当于一步初始化,构造函数内初始化相当于先定义再初始化。
- 初始化列表的初始化顺序应与成员变量的声明顺序一致,否则可能出现问题。
class Date {
public:
Date(int y, int m, int d);
private:
int _year;
int _month;
int _day;
};
Date::Date(int y, int m, int d):
_year(y), _month(m), _day(d)
{}
class Goods {
public:
Goods(const char* name, int amount, double price, int y, int m, int d);
private:
char* _name;
int _amount;
double _price;
Date date; //成员对象,需要使用参数化列表对其初始化
};
Goods::Goods(const char* name, int amount, double price, int y, int m, int d) :
//相当于初始化成员变量时直接赋值
_amount(amount), _price(price), date(y, m, d) {
//构造函数内对成员变量赋值,相当于定义好了成员变量后再赋值
_name = new char[strlen(name) + 1];
strcpy(_name, name);
//date = Date(y,m,d); 如果不使用参数化列表,这样写也会有问题,因为date要先默认初始化,可能没有这个默认初始化函数。
}
int main() {
Goods("nn", 100, 20.0, 2000, 3, 7);
}
class Test{
public:
Test(int x):a(x), b(a){} //没问题
Test(int x):b(x), a(b){} //有问题,初始化顺序应该是a,b
private:
int a;
int b;
}
静态成员变量,静态成员函数,常成员方法
- 静态成员变量是类级别共享的,都可以用类名作用域来调用或查询。
- 静态成员变量不属于类对象在类中声明,在类外定义。相当于一个以类为作用域的全局变量。
- 静态成员函数没有 this 指针,只能访问静态成员变量和静态成员函数。
class Date {
public:
Date(int y, int m, int d);
private:
int _year;
int _month;
int _day;
};
Date::Date(int y, int m, int d) :
_year(y), _month(m), _day(d)
{}
class Goods {
public:
Goods(const char* name, int amount, double price, int y, int m, int d);
static int getNum() {
return _num;
}
private:
char* _name;
int _amount;
double _price;
Date date;
static int _num;
};
int Goods::_num = 0;
Goods::Goods(const char* name, int amount, double price, int y, int m, int d) :
_amount(amount),
_price(price),
date(y, m, d)
{
_num++;
_name = new char[strlen(name) + 1];
strcpy(_name, name);
}
int main() {
Goods good1("nn", 100, 20.0, 2000, 3, 7);
Goods good2("nn", 100, 20.0, 2000, 3, 7);
Goods good3("nn", 100, 20.0, 2000, 3, 7);
Goods good4("nn", 100, 20.0, 2000, 3, 7);
cout << Goods::getNum() << endl;
cout << good4.getNum() << endl; //也可以
return 0;
}
- 如何保护const变量的指针?
- 下述show方法由于使用了this指针,所以产生类型转换冲突。
- 常成员方法:函数后加入const限定符,相当于给this指针加上const限定符。
- 只要是只读成员的方法,都要实现成常成员方法,常成员方法对对象只能读不能写。
class Goods {
public:
Goods(const char* name, int amount, double price, int y, int m, int d);
void show() {
cout << _name << '\\' << _amount << '\\' << _price << '\\' << endl;
}
void cshow() const{ //会给this指针加上const限定符
cout << _name << '\\' << _amount << '\\' << _price << '\\' << endl;
}
......
};
int main() {
const Goods good1("nn", 100, 20.0, 2000, 3, 7);
good1.show(); //不可以,此过程隐式将const Goods*转换为Goods*,这是不被允许的(this指针类型为Goods*)
good1.cshow(); //可以
return 0;
}-
指向成员变量和成员方法的指针
指向成员变量的指针
class Test {
public:
int ma;
static int mb;
};
int Test::mb;
int main() {
//类对象的成员变量指针不是普通的指针,需要加入类名限定符
//类的静态成员变量指针不针对某一个特定对象,使用普通指针
int* p = &Test::mb;
int Test::* q = &Test::ma;
Test t1;
Test *t2 = new Test();
t1.ma = 1;
t1.*q = 2;
cout << t1.ma << endl;
t2->ma = 1;
t2->*q = 2;
cout << t2->ma << endl;
//t2->*p = 2; //错误
*p = 2;
cout << Test::mb << endl;
}
指向成员方法的指针
class Test {
public:
void func() {
cout << "func" << endl;
}
static void sfunc() {
cout << "sfunc" << endl;
}
int ma;
static int mb;
};
int Test::mb;
int main() {
Test t1;
Test *t2 = new Test();
//普通成员函数指针是针对对象的,要加上类名限定符
//静态成员函数指针不针对某一个特定对象,使用普通指针
void(Test:: * func_ptr)() = &Test::func;
void(* func_sptr)() = &Test::sfunc;
(t1.*func_ptr)();
(t2->*func_ptr)();
(*func_sptr)();
}
理解函数模板
模板的意义:对函数类型可以做修改
- 函数模板:bool compare(T a, T b)
- 模板实例化:定义一个模板参数类型,进行一次函数的实例化
- 模板函数:一个函数模板的实例化就是一个模板函数
- 模板类型参数:T
- 模板非类型参数:
- 模板的实参推演:根据实参反推模板参数类型
- 模板的特例化:为函数模板的特殊参数类型进行特殊设置
- 对于某些类型来说,依赖模板的函数逻辑是有问题的。需要模板的特例化,特例化不是编译器提供的,而是用户提供的一种实例化方法,
- 函数模板 模板的特例化 非模板(普通)函数的重载关系:如下例三个字符串的compare函数,一般优先调用非模板函数,如果非模板函数不存在,才会调用特例化模板函数或模板函数。
#include <iostream>
#include <cstring>
using namespace std;
template<typename T> //定义一个模板类型参数列表,也可以定义为类
bool compare(T a, T b) { //compare是一个函数模板
cout << "template" << endl;
return a > b;
}
template<> //模板的特例化:针对compare函数模板,提供一个给const char*的特例化版本
bool compare(const char *a, const char *b) { //compare是一个函数模板
cout << "str template" << endl;
return strcmp(a, b) > 0;
}
//compare是一个函数模板
//在函数调用点,编译器用用户指定的类型,从原模版实例化一份函数代码
//从函数模板实例化出来的函数为模板函数,如下
//符号表会依据函数名和参数类型为每个模板函数加入新符号,模板函数才是真正被编译的函数,而函数模板不是
//bool compare<int>(int a, int b) {
// cout << "template" << endl;
// return a > b;
//}
//bool compare<double>(double a, double b) {
// cout << "template" << endl;
// return a > b;
//}
int main() {
//函数的调用点
compare<int>(10, 20);
compare<double>(10.0, 20.0);
compare(10, 20); //函数模板的实参推演:根据用户传入的实参类型,反推出模板所使用的类型参数
//第二次使用compare<int>时不需要再次产生模板函数,直接调用第一次产生的函数即可
//compare(10, 20.0); //报错,无法确定模板参数类型
compare("aa", "bb"); //可以执行,默认实参自动推演为const char*,从而比较两个指针地址的大小。
//如果进行字符串的字典比较,如何?
return 0;
}
- 如果将函数模板定义在另一个文件中定义,会出现问题。因为模板本身不会编译,模板函数才会编译。如果A文件中调用了B文件中的模板函数,事实上在编译汇编阶段,B文件并不会编译产生A所需的模板函数,符号表中也不会产生对应符号,因此在链接阶段,A文件所需使用的几个*UND*函数符号无法在B中找到。不过,B中定义的普通函数和特例化模板函数在A中是可以被看到的。因此模板源代码一般都放在头文件中,在源文件中#include直接展开。
- 如果在调用之前,声明函数模板对应类型的模板函数,也可以避免上述情况,一般不这样。
模板的非类型参数
template<typename T, int SIZE>
//模板的模板参数列表
// //SIZE为非类型形参,非类型形参在模板的内部是常量,且只能是整型,指针和引用,在编译阶段确定值
//将SIZE设置为模板非类型形参,间接不需要函数传参
void sort(T* arr) {
for (int i = 0; i < SIZE - 1; i++) {
for (int j = 0; j < SIZE - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = arr[j];
}
}
}
}
int main() {
char arr[] = { 1, 2, 3, 4, 5, 6 };
const int size = sizeof(arr) / sizeof(int);
sort<char, size>(arr);
}
类模板
- 类成为类名和类型参数的组合
- 无论是一般类还是模板类,只有调用到的成员函数,才会出现在符号表上。
#pragma once
#include <iostream>
#include <cstring>
using namespace std;
template<typename T>
class SeqStack { //模板名称 + 类型参数列表 = 类名称
private:
T* _pstack;
int _top;
int _size;
void resize();
public:
//构造和析构函数名可以不加类型参数列表,其它出现模板的地方都不能省略类型参数列表
SeqStack<T>(int size = 5);
SeqStack<T>(const SeqStack<T>& stack);
~SeqStack<T>();
SeqStack<T>& operator=(const SeqStack<T>& stack);
void push(const T& val);
void pop();
T top()const;//只读的方法时候设计为常方法
bool full()const;
bool empty()const;
int getlen()const { return _size; }
};
template<typename T>
void SeqStack<T>::resize()
{
_size *= 2;
T* tmp = new T[_size];
for (int i = 0; i < _top; i++)
{
tmp[i] = _pstack[i];
}
delete[]_pstack;
_pstack = tmp;
}
template<typename T>
SeqStack<T>::SeqStack<T>(int size)
: _pstack(new T[size])
, _size(size)
, _top(0)
{}
template<typename T>
SeqStack<T>::SeqStack<T>(const SeqStack<T>& stack)
:_size(stack._size)
, _top(stack._top)
{
delete[]_pstack;
_pstack = new T[_size];
for (int i = 0; i < _top; i++) {
_pstack[i] = stack._pstack[i];
}
}
template<typename T>
SeqStack<T>::~SeqStack<T>()
{
delete[]_pstack;
_pstack = nullptr;
}
template<typename T>
SeqStack<T>& SeqStack<T>::operator=(const SeqStack<T>& stack)
{
if (&stack == this)
{
return *this;
}
delete[]_pstack;
_top = stack._top;
_size = stack._size;
_pstack = new T[_size];
for (int i = 0; i < _top; i++) {
_pstack[i] = stack._pstack[i];
}
}
template<typename T>
void SeqStack<T>::push(const T& val)
{
if (full())
{
resize();
}
_pstack[top++] = val;
}
template<typename T>
void SeqStack<T>::pop()
{
if (empty())
{
return;
}
_top--;
}
template<typename T>
T SeqStack<T>::top()const
{
if (empty())
{
return;
}
return _pstack[--top];
}
template<typename T>
bool SeqStack<T>::full()const
{
if (top == _size);
}
template<typename T>
bool SeqStack<T>::empty()const
{
return(_top == 0);
}
int main()
{
SeqStack<int> s1;
cout << s1.getlen() << endl;
return 0;
}
实现C++ STL顺序容器vector -- 代码
下述代码缺少容器的空间配置器
#include<iostream>
using namespace std;
template<typename T>
class vector {
private:
T* _first; //数组起始位置
T* _last; //数组有效元素的后继位置
T* _end; //数组空间的后继位置
void resize()
{
int size = _end - _first;
T* _tmp = new T[size * 2];
for (int i = 0; i < size; i++)
{
_tmp[i] = _first[i];
}
delete[]_first;
_first = _tmp;
_end = _first + size * 2;
_last = _first + size;
}
public:
vector<T>(int size = 4)
{
_first = new T[size];
_last = _first;
_end = _first + size;
}
~vector<T>()
{
delete[]_first;
_end = _last = _first = nullptr;
}
vector<T>(const vector<T>& rhs)
{
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
_first = new T[size];
for (int i = 0; i < len; i++)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
}
vector<T> operator=(const vector<T>& rhs)
{
if (this == &rhs)
{
return *rhs;
}
delete[]_first;
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
_first = new T[size];
for (int i = 0; i < len; i++)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
}
void push_back(const T &rhs)//向容器末尾添加元素
{
if (full())
{
resize();
}
*_last++ = rhs;
}
T& pop_back()
{
if (empty())
{
throw "empty";
}
return *--_last;
}
int full() const
{
return _last == _end;
}
int empty() const
{
return _last == _first;
}
int size()const
{
return _last - _first;
}
int capacity() const
{
return _end - _first;
}
};
int main()
{
vector<int> vec;
for (int i = 0; i < 10; i++)
{
vec.push_back(rand() % 100);
}
cout << vec.capacity() << endl;
cout << vec.size() << endl;
while (!vec.empty())
{
cout << vec.pop_back() << endl;
}
cout << vec.capacity() << endl;
cout << vec.size() << endl;
return 0;
}
容器空间配置器allocator
容器的空间配置器allocator应分开完成:
- 内存开辟
- 内存释放
- 对象构造
- 对象析构
#include<iostream>
using namespace std;
// 容器的空间配置器allocator做四件事情 内存开辟/内存释放 对象构造/对象析构
template<typename T>
struct Allocator
{
T* allocate(size_t size) // 负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p) // 负责内存释放
{
free(p);
}
void construct(T* p, const T& val) // 负责对象构造
{
new (p) T(val); // 定位new,调用拷贝构造函数
}
void destroy(T* p) // 负责对象析构
{
p->~T();
}
};
/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
vector(int size = 10)
{
// 需要把内存开辟和对象构造分开处理
// _first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
cout << "vector()" << endl;
}
~vector()
{
// 析构容器有效的元素,然后释放_first指针指向的堆内存
// delete[] _first;
for (T *p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
{
_allocator.destroy(p);
}
_allocator.deallocate(_first); // 释放内存
_first = _end = _last = nullptr;
}
vector(const vector<T>& rhs)
{
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator = (const vector<T>& rhs)
{
if (this == &rhs)
{
return *this;
}
//delete[] _first;
for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val)
{
if (full())
expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
void pop_back()
{
if (empty())
return;
//--_last; 不仅要把_last指针--,还需要析构删除的元素
--_last;
_allocator.destroy(_last);
}
T back() const
{
return *(_last - 1);
}
bool full() const { return _last == _end; }
bool empty() const { return _first == _last; }
int size() const { return _last - _first; }
private:
T* _first;
T* _last;
T* _end;
Alloc _allocator;
void expand()
{
int size = _end - _first;
//T* ptmp = new T[2 * size];
T* ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; i++)
{
_allocator.construct(ptmp + i, _first[i]);
//ptmp[i] = _first[i];
}
//delete[] _first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
class Test
{
public:
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test&) { cout << "Test(const Test&)" << endl; }
};
int main()
{
//无alloctor的情况下,定义一个基于Test的容器vector,会自动构造默认长度个Test对象
//析构vec的过程则为delete首对象元素的指针,而应为先析构容器有效对象元素,再释放_first申请的堆内存
vector<Test> vec;
Test t1, t2, t3;
//无alloctor的情况下,push_back(t1)相当于在构造函数已经生成对象元素的情况下,重载=运算符来重新赋值
//应在只开辟内存的情况下,使用拷贝构造添加元素
vec.push_back(t1);
vec.push_back(t1);
vec.push_back(t1);
//无alloctor的情况下,pop_back只是将_last指针回退,如果末尾元素有外部资源(如堆资源),则会在元素对象被=重载覆盖后无法被再次访问,造成永久内存泄露。
//应及时释放对象元素的外部资源并析构元素对象
vec.pop_back();
vec.pop_back();
vec.pop_back();
return 0;
}
C++的运算符重载
编译器在做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法)。如果找不到合适的成员方法,则在全局作用域寻找合适的运算符重载函数.
#include<iostream>
using namespace std;
//复数类
class CComplex
{
private:
int mreal;
int mimage;
//友元函数是为了访问private成员变量,不属于成员函数
friend CComplex operator+(const CComplex& lhs, const CComplex& rhs);
public:
CComplex(int real = 0, int image = 0):mreal(real), mimage(image)
{
cout << "construct" << endl;
}
~CComplex(){}
CComplex(const CComplex& comp)
{
cout << "copy" << endl;
mreal = comp.mreal, mimage = comp.mimage;
}
CComplex operator+(const CComplex& comp)
{
return CComplex(this->mreal + comp.mreal, this->mimage + comp.mimage);
}
void show()
{
cout << "real" << mreal << "image" << mimage << endl;
}
};
CComplex operator+(const CComplex& lhs, const CComplex& rhs)
{
return CComplex(lhs.mreal + rhs.mreal, lhs.mimage + rhs.mimage);
}
int main()
{
CComplex comp1(10, 10);
CComplex comp2(20, 20);
//调用comp1.operator+(comp2) 即加法运算符的重载函数,然后在comp3初始化的基础上赋值,而非拷贝构造
CComplex comp3 = comp1 + comp2;
comp3.show();
//下一行的逻辑是:comp3后跟+,意味着调用CComplex operator+,然后可以注意到,这一行发生了两次构造,其中20->CComplex(20),即强制类型转换,最后执行operator+
CComplex comp4 = comp3 + 20;
comp4.show();
//下一行20不会调用CComplex operator+,转而寻找全局作用域下的运算符重载函数,但没有合适的,于是定义了全局的友元运算符重载函数CComplex operator+
CComplex comp5 = 20 + comp3;
comp5.show();
return 0;
}
单目运算符的重载
- operator++()代表前置++
- 其重载形式operator++(int)代表后置++
- 为了统一所有类型对象的输出,可以采用在其类中重载输入输出运算符的形式
#include<iostream>
using namespace std;
//复数类
class CComplex
{
private:
int mreal;
int mimage;
//友元函数是为了访问private成员变量,不属于成员函数
friend CComplex operator+(const CComplex& lhs, const CComplex& rhs);
friend ostream& operator<<(ostream& out, const CComplex& src);
friend istream& operator>>(istream& in, CComplex& src);
public:
CComplex(int real = 0, int image = 0):mreal(real), mimage(image)
{
cout << "construct" << endl;
}
~CComplex(){}
CComplex(const CComplex& comp)
{
cout << "copy" << endl;
mreal = comp.mreal, mimage = comp.mimage;
}
CComplex operator+(const CComplex& comp)
{
return CComplex(this->mreal + comp.mreal, this->mimage + comp.mimage);
}
CComplex operator++()
{
//++this->mimage, ++this->mreal;
//return *this;
return CComplex(++mreal, ++mimage);
}
CComplex operator++(int)
{
/*CComplex comp = *this;
++this->mimage, ++this->mreal;
return comp;*/
return CComplex(mreal++, mimage++);
}
void operator+=(const CComplex& comp)
{
mreal += comp.mreal;
mimage += comp.mimage;
}
void show()
{
cout << "real" << mreal << "image" << mimage << endl;
}
};
CComplex operator+(const CComplex& lhs, const CComplex& rhs)
{
return CComplex(lhs.mreal + rhs.mreal, lhs.mimage + rhs.mimage);
}
ostream& operator<<(ostream &out, const CComplex& src)//提供输出符号的重载形式
{
out << "real:" << src.mreal << "image:" << src.mimage;
return out;
}
istream& operator>>(istream& in, CComplex& src)//提供输入符号的重载形式
{
in >> src.mreal >> src.mimage;
return in;
}
int main()
{
CComplex comp1(10, 10);
CComplex comp2 = comp1++;
CComplex comp3 = ++comp1;
comp1.show();
comp2.show();
comp3.show();
comp1 += comp1;
comp1.show();
cout << comp1 << endl;
cin >> comp1 >> comp2;
cout << comp1 << endl << comp2 << endl;
return 0;
}
String类的实现
//#include<string>
#include<iostream>
using namespace std;
class String
{
private:
char* _pstr;
friend String operator+(const String& s1, const String& s2);
friend ostream& operator<<(ostream& out, const String& str);
public:
String(const char* p = nullptr)
{
if (p != nullptr)
{
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else
{
_pstr = new char('\0');
}
}
~String()
{
delete[]_pstr;
_pstr = nullptr;
}
String(const String& s)
{
_pstr = new char[strlen(s._pstr) + 1];
strcpy(_pstr, s._pstr);
}
String & operator=(const String &src)
{
if (&src == this)
{
return *this;
}
delete[]_pstr;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
return *this;
}
char& operator[](int i) //可以作为左值,被修改
{
return _pstr[i];
}
const char& operator[](int i) const //常方法,String不可被修改
{
return _pstr[i];
}
bool operator>(const String& s)const
{
return strcmp(this->_pstr, s._pstr) > 0;
}
bool operator<(const String& s)const
{
return strcmp(this->_pstr, s._pstr) < 0;
}
bool operator==(const String& s)const
{
return strcmp(this->_pstr, s._pstr) == 0;
}
int length()const
{
return strlen(_pstr);
}
};
String operator+(const String& s1, const String& s2)
{
//为了避免内存泄漏,先构造一个String对象,再释放_s的空间,返回String对象
char* _s = new char[strlen(s1._pstr) + strlen(s2._pstr) + 1];
strcpy(_s, s1._pstr);
strcat(_s, s2._pstr);
String s(_s);
delete[]_s;
return s;
}
ostream& operator<<(ostream& out, const String& str)
{
out << str._pstr;
return out;
}
int main()
{
String s1 = "aaa";
String s2 = "bbb";
String s3 = s1 + s2;
cout << s3 << endl;
cout << (s2 > s1) << endl;
cout << s1.length() << endl;
for (int i = 0; i < s1.length(); i++)
{
cout << s1[i] << " ";
s1[i] = s2[i];
}
cout << s1;
}
下述代码中,为了返回两字符串相连的结果,_s和s内部的调用分别做了一次new和delete,同时return时s做了一次拷贝构造,代价较高。
String operator+(const String& s1, const String& s2)
{
//为了避免内存泄漏,先构造一个String对象,再释放_s的空间,返回String对象
char* _s = new char[strlen(s1._pstr) + strlen(s2._pstr) + 1];
strcpy(_s, s1._pstr);
strcat(_s, s2._pstr);
String s(_s);
delete[]_s;
return s;
}
一种更优解法
String operator+(const String& s1, const String& s2)
{
//减少了一次new和一次delete
String tmp;
tmp._pstr = new char[strlen(s1._pstr) + strlen(s2._pstr) + 1];
strcpy(tmp._pstr, s1._pstr);
strcat(tmp._pstr, s2._pstr);
return tmp;
}
String字符串对象的迭代器iterator实现
- 泛型算法参数接收的都是迭代器
- 泛型算法是一组全局的函数,适用于所有容器
- 基于第二点,泛型算法有一套方法可以统一地遍历所有容器的元素
class String
{
public:
//嵌套定义iterator类
class iterator
{
private:
char* _p; //没有用到外部资源,无需拷贝构造函数
public:
iterator(char *p):_p(p){}
char operator*() const
{
return *(this->_p);
}
bool operator!=(const iterator& it) const
{
return _p != it._p;
}
void operator++()
{
++_p;
}
};
private:
char* _pstr;
...
}
vector对象的迭代器iterator实现
#include<iostream>
using namespace std;
// 容器的空间配置器allocator做四件事情 内存开辟/内存释放 对象构造/对象析构
template<typename T>
struct Allocator
{
T* allocate(size_t size) // 负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p) // 负责内存释放
{
free(p);
}
void construct(T* p, const T& val) // 负责对象构造
{
new (p) T(val); // 定位new
}
void destroy(T* p) // 负责对象析构
{
p->~T();
}
};
/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
vector(int size = 10)
{
// 需要把内存开辟和对象构造分开处理
// _first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
cout << "vector()" << endl;
}
~vector()
{
// 析构容器有效的元素,然后释放_first指针指向的堆内存
// delete[] _first;
for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
{
_allocator.destroy(p);
}
_allocator.deallocate(_first); // 释放内存
_first = _end = _last = nullptr;
}
vector(const vector<T>& rhs)
{
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator = (const vector<T>& rhs)
{
if (this == &rhs)
{
return *this;
}
//delete[] _first;
for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val)
{
if (full())
expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
void pop_back()
{
if (empty())
return;
//--_last; 不仅要把_last指针--,还需要析构删除的元素
--_last;
_allocator.destroy(_last);
}
T back() const
{
return *(_last - 1);
}
bool full() const { return _last == _end; }
bool empty() const { return _first == _last; }
int size() const { return _last - _first; }
T& operator[] (int index)
{
if (index < 0 || index >= size())
{
throw "OutOfRangeException";
}
return _first[index];
}
class iterator
{
public:
iterator(T* p = nullptr) :_p(p) {}
bool operator!=(const iterator& it) const
{
return _p != it._p;
}
void operator++()
{
++_p;
}
T& operator*() { return *_p; }
const T& operator*() const{ return *_p; }
private:
T* _p;
};
iterator begin() { return iterator(_first); }
iterator end() { return iterator(_last); }
private:
T* _first;
T* _last;
T* _end;
Alloc _allocator;
void expand()
{
int size = _end - _first;
//T* ptmp = new T[2 * size];
T* ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; i++)
{
_allocator.construct(ptmp + i, _first[i]);
//ptmp[i] = _first[i];
}
//delete[] _first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
class Test
{
public:
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test&) { cout << "Test(const Test&)" << endl; }
};
int main()
{
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
for (int i = 0; i < 20; i++)
{
//底层数据结构不连续时不支持中括号重载
cout << vec[i] << endl;
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
cout << *it << endl;
}
}
迭代器失效
- 容器调用erase方法,当前位到容器末尾元素位置的迭代器失效,前半部分有效
- 容器调用insert方法,当前位到容器末尾元素位置的迭代器失效,前半部分有效
- 扩容会导致内存转移,迭代器全部失效
int main()
{
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
//insert是把it位置的元素向后移,当前位置添加新元素
//第一次insert后迭代器就会失效
vec.insert(it, *it - 1);
break;
}
}
it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
//第一次erase后迭代器就会失效
vec.erase(it);
break;
}
}
}
迭代器失效的解决办法
对插入删除点的更新操作
int main()
{
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
for (auto v : vec)
cout << v << " ";
cout << endl;
auto it = vec.begin();
/*for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.erase(it);
}
}*/
//for循环改写为while,记录erase时迭代器的位置
while (it != vec.end())
{
if (*it % 2 == 0)
{
//erase时保留当前位置迭代器,并不再进行it++
it = vec.erase(it);
}
else
++it;
}
for (auto v : vec)
cout << v << " ";
cout << endl;
//记录insert时迭代器的位置
for (it = vec.begin(); it != vec.end(); ++it)
{
if (*it % 2 != 0)
{
//insert时保留当前位置迭代器,额外进行一次it++
it = vec.insert(it, *it + 1);
++it;
}
}
for (auto v : vec)
cout << v << " ";
cout << endl;
return 0;
}
迭代器失效的底层原理
容器底层维护了一个迭代器的链表,每个节点代表着一个迭代器,迭代器节点内包含其指向的元素位置和其所属vector指针,迭代器节点按照其指向的元素位置排序,当检查到的迭代器需要置为失效时,则该节点其所属vector指针置为null,节点被删除。
struct Iterator_Base
{
Iterator_Base(iterator* c = nullptr, Iterator_Base* n = nullptr)
:_cur(c), _next(n) {}
iterator* _cur;
Iterator_Base* _next;
};
void verify(T* first, T* last)
{
Iterator_Base* pre = &this->_head;
Iterator_Base* it = this->_head._next;
while (it != nullptr)
{
if (it->_cur->_p >= first && it->_cur->_p <= last)
{
// 迭代器失效,把iterator持有的容器指针置空
it->_cur->_pVec = nullptr;
// 删除当前迭代器节点,继续判断后面的迭代器节点是否失效
pre->_next = it->_next;
delete it;
it = pre->_next;
}
else
{
pre = it;
it = it->_next;
}
}
}
iterator insert(iterator it, const T& val)
{
// 1.不考虑扩容 2.不考虑it._p的指针合法性
verify(it._p - 1, _last);
T* p = _last;
//从后方开始先依次析构
while (p > it._p)
{
_allocator.construct(p, *(p - 1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);
_last++;
return iterator(this, p);
}
iterator erase(iterator it)
{
verify(it._p - 1, _last);
T* p = it._p;
while (p < _last - 1)
{
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
p++;
}
_allocator.destroy(p);
_last--;
return iterator(this, it._p);
}
cpp
cpp
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)