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上,栈自下而上增长,即高地址->低地址。
  • 最后一段是命令行参数和环境变量。
  • 内核空间。

img

图中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的值存放到对应存储空间

每一个进程的用户空间是私有的,内核空间是共享的,进程间可以通过向内核空间中读写数据实现共享。

进程的通信方式:匿名管道通信

函数的调用堆栈详细过程

img
img

  • 第一句:初始化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

程序编译与链接原理

img

  • 预编译:处理#命令,但保留#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文件的格式组成包括各种段

img
img
编译过程中不为符号分配地址,通过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

posted @   二氧化硅21  阅读(20)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示