初级C++基础夯实

c++ 内功

虚拟地址空间划分

存在,能看见 是物理的

存在, 看不见 是透明的

不存在, 看得见 是虚拟的

不存在, 看不见 被删除了

  • .text段:指令段,存放指令

  • .rodata:只读数据段,常量就存放在此,例如const char *p = “hello”;//p在栈上,而“hello”在只读数据段上,因为这个是字符串常量

  • .data:数据段,存放已经初始化且初始化不为0的全局变量

  • .bss:数据段,存放初始化为0和未初始化的全局变量,内核会将此数据段都置0

  • .heap:堆区,程序员自己管理内存

  • .dll,*so:加载共享库,前者为win,后者为linux

  • .stack:

  • 命令行参数,环境变量:

  • 内核空间:

    1. ZONE_DMA
    2. ZONE_BORMAL
    3. ZONE_HIGHMEN

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51cGpLmZ-1680868403212)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221126210031494.png)]

  • 全局变量都是数据,放在数据段(1,4在.data段 2,3,5,6在.bss段)

  • 声名局部变量a,b,c生成的是mov指令,指令放在text段,abc在栈上

  • static局部变量是程序第一次运行到时初始化,e在.data,f,g在.bss

  • 每个进程的用户空间是私有的,内核空间是共享的,匿名管道通信就是在内核空间划分一道地址空间进行通信

指令角度堆栈调用过程

int sum(int a, int b) {
    int temp = 0;
    temp = a + b;
    return temp;
}

int main() {
    int a = 10;// mov dword ptr[ebp - 4], 0Ah
    int b = 20;// mov dword ptr[ebp - 8], 14h
    int ret = sum(a, b);//取a,b的值,放入寄存器,压入sum函数的栈(esp从main函数栈顶,上移两个int的位(分别放形参a,b)变成sum函数的栈顶)
    cout << ret;
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcTKyq9Y-1680868402410)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221129113909187.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LzRImafe-1680868402411)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221129113947123.png)]

问题1.sum函数调用完,如何知道回到main函数 问题2.回到main函数,如何知道从哪一行开始
  • esp:当前函数栈顶指针

  • ebp:当前函数栈低指针(虚拟内存空间中栈底是高地址

  • call:函数调用指令

    1. 把下一行指令的地址(位于.text段)压栈(问题2)
    2. 进入sum
  • 进入sum函数之后,把esp的位置压栈(问题1),然后esp从main函数,上移到ebp位置(esp=ebp),并为sum函数开辟栈帧,有的编译器会为开辟的栈帧中初始化为0xCCCCCCCC(如果此类编译器还允许访问未初始化的值,那么打印出来可能就是此值)

  • sum函数后ebp回到esp位置,开辟的栈空间返回给系统;把栈的值出栈,给esp(,即回到main函数栈底)再出栈(将出栈内容,call的1所存入的值,放入CPU的PC寄存器),形参的地址归还给系统

编译链接原理

  • .a,.lib 静态库文件
  • 预编译:#开头的命令 , (除了#pragma lib#pragma link)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMR7JFks-1680868402411)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221129135730943.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVFCe7CI-1680868402411)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221129135752473.png)]

  • 自己定义的变量,有位置;引用其他文件的全局变量是UND
  • 编译过程中符合不分配虚拟地址,
  • 在链接阶段,
    1. 会合并所有的段,引用的符号也解析成功(找到全局变量所在文件)
    2. 符号重定向,分配虚拟地址

基础知识精讲

默认形参的函数

  • 给默认参数从右往左给

  • 效率问题,默认参数少一次mov指令

    int sum(int a, int b = 10){}
    
    int main(){
    	int a = 10;
    	int b = 20;
    	sum(a,b);//形参b是,先把取实参b的值,再压入栈
    	sum(a);//形参b是直接压入栈10,比上面少一次mov指令
    	return 0;
    }
    

内联函数

  • inline内联函数在编译过程中,没有函数调用的开销(call指令首先将下一条指令地址入栈,再把调用方栈底指针入栈,再开辟栈帧,初始化栈帧空间),在函数调用点直接把函数的代码展开

  • 符号表也不给inline生成符号

  • inline只是建议(debug版本,inline是不起作用的,release版本才有用),例如递归

  • 简单的函数建议处理成内联

  • 通过 gcc -c main.cpp objdump -t main.o 查看内联函数在不在符号表里面,判断inline是否起作用

    inline int add(int a, int b) { 
        return a + b;
    }
    
    int main() {
        int a = 10;
        int b = 20;
        int ret = add(a, b);
        cout << ret;
    	return 0;
    }
    

函数重载

关于const char* = 常量 https://www.cnblogs.com/0-lingdu/p/12228803.html

  1. c++为什么支持函数重载,c语言不支持
    • c++给函数产生符号,是函数名+ 参数列表
    • c给函数产生符号,是函数名
  2. 函数重载需要注意什么
    • 函数名相同,参数列表个数或者类型不同,且在同一个作用域下是函数重载(返回值不同不行)
    • const和不加const是一个类型
    • volatile
    • 静态多态
  3. c++,c代码直接如何互相调用

例一: 用c写了一个函数,在cpp文件中调用,会出现链接错误,无法解析的外部符号

原因:在c++中给函数产生符号是函数名+ 形参,c文件编译时产生的符号是函数名无法匹配

方法:在c++中声明的时候使用extern “c”{} 包起来,那么就会以C的方式进行编译

例二:c++写了一个函数,在c中调用

方法:把c++的源码也使用extern “c”{} 包起来。

cpp编译器中都定义了 __cplusplus 这个宏,那么如果是cpp编译器就会进入被包含的部分,使用c编译器的方式编译接下来的代码,

而c编译器没有定义 __cplusplus宏,那么看不到被包的地方,照常使用c编译器

#ifdef __cplusplus
extern "C"{
#endif
	int sum(int a, int b){
		return a + b;
	}
#ifdef __cplusplus
}
#endif

const

  1. const修饰的变量不能再作为左值,初始化完成后,值不能被修改,只是不能作为左值修改,直接通过地址能修改

  2. c中不叫常量,叫做常变量,,且不能当初常量使用,例如定义数组大小c++中使用常量初始化叫常量,使用变量初始化叫常变量

  3. c中就是当作个变量来编译生产指令,c++中所有出现const常量名字的地方,都被常量的初始值替换了

  4. c++如果使用变量来初始化const,那效果和c一样,也叫常变量

    const int a  = 20;
    int array[a] = {};//c报错 c++可以
    
    int *p = (int*)&a;
    *p = 30;
    
    printf("%d%d%d", a,*p,*(&a));//c:30 30 30 c++(使用20初始化a):20 30 20 c++(使用变量初始化啊):303030
    
    
const和指针

const修饰的是离的最近的类型,可以听过typeid§.name查看是什么类型

const int* p = &a;//修饰的int,p可以指向不同的int内存,但是不能通过p间接修改指向的内存

int const* p = &a;// 同上,这两个可以指向const int 的地址
    
int *const p = &a;//修饰的int*,即指针不能指向其他内存,但是指向的内存的值可以修改
    
const int * const p = &a;//第一个const修饰int 第二个修饰int *
  • const和多级指针

    const int **q;//修饰int
    int * const * q;//修饰*q
    int ** const q;//修饰q
    
    int a = 10;
    int *p = &a;
    const int **q = &p;//错误,错误原因:
    /*
    const int **q = &p;
    那么 *q 和p此时指向同一块内存
    *q此时可以理解为一个const int *
    那么可以
    const int b = 20;
    *q = &b;
    此时就会将一个const int的地址给p,这很明显是错误的,常量的地址不能泄露给普通指针
    解决办法: 给int *q 改为 const int *q
            或者const int **q 改为 const int * const *q
    */
    

const 右边没有指针时,const不参与类型

int * 《= const int* 错误

const int* 《= int * 可以

int** <= const int ** 错误

const int ** <= int ** 错误

引用和指针
  1. 引用必须初始,指针可以不初始化
  2. 定义指针和引用和通过二者修改值在汇编层面是一样的
  3. 引用只有一级引用

定义引用不会时,参考定义指针如何定义

int (*q)[5] = &array;
int (&q)[5] = array;
  • 右值引用专门用来引用右值类型,指令上,自动产生临时量,直接引用临时量

  • 写一句代码在内存0x0018ff44处写一个4字节的10

    int *&&p = (int*)0x0018ff44;
    int * const &p = (int*)0x0018ff44;
    

new和delete

  1. new不仅开辟内存,还做了内存初始化
  2. malloc开辟内存失败,通过返回值与nullptr做比较,new是通过抛出异常

new有多少种

int *p1 = new (nothrow) int;

const int *p2 = new const int(40);//在堆开辟地址存放一个常量,因此需要const int *接

int data = 0;
int *p3 = new (&data) int(50);//定位new,在指定地址上初始化

c++面向对象

  • 类的内存大小按最大的成员变量内存对齐

  • 每个对象有自己的成员变量,共享成员函数,成员函数放在代码段(那么函数如何知道是哪个对象调用的自己呢?)

    • this指针,指向调用的对象,成员方法编译后会自动被加上一个this指针的形参,接收对象的地址。

      object1.init(10);//编译器就会转变为 init(&object1, 10);
      
      
  • C++的访问控制是类层面的 class-level, 而不是对象级别的object-level,即一个对象的成员函数能访问不同对象的私有成员变量

  • 初始化列表与在构造函数中赋值的区别:

    • 注意:成员变量初始化的顺序与他们在类中定义的顺序相同

    • 如果是内置类型那么没有什么区别

    • 如果是类类型,例如在B类中含有成员A

      B(A a){ m_A = a;}//先默认构造了m_A,再给A赋值
      
      B(A a):m_A(a){}//直接拷贝构造A,效率更高
      
  • 静态成员和静态函数,都不需要通过对象来调用,静态成员函数没有this指针,普通成员函数有this指针的参数,因此不能通过类名调用,这也是为啥静态函数不能访问普通成员变量,因为普通成员变量被访问的时候,前面都有个this指针,但是静态成员函数里面不会有this。

  • 静态方法和普通方法的本质区别就是是否会自动添加一个this指针的形参

  • 由上,const对象也无法直接调用普通方法,因为const对象调用方法时,方法会自己添加一个this指针类型的形参,

    const A a(10);
    a.show();//等价于show(&a),但是show需要的是一个A* ,但是传入的是const A*,后者无法给前者,
    //需要在定义show方法时,在小括号后面加上const,此时方法对成员变量只能访问不能修改
    
    ...
    
    void show() const{
    ...
    }
    

深拷贝浅拷贝

class myString {
public:
	myString(const char* str = nullptr) {
		if (str != nullptr) {
			m_data = new char[strlen(str) + 1];
			strcpy(m_data, str);
		}
		else {
			m_data = new char[1];
			*m_data = '\0';
		}
	}
	myString(const myString& other) {
		
		m_data = new char[strlen(other.m_data) + 1];
		strcpy(m_data, other.m_data);
		
	}

	~myString() {
		delete[] m_data;
		m_data = nullptr;
	}

	myString& operator=(const myString& other) {
		if (this == &other) {
			return *this;
		}
		delete[] m_data;
		m_data = new char[strlen(other.m_data) + 1];
		strcpy(m_data, other.m_data);

		return *this;
	}
private:
	char* m_data;
};

//循环队列
class Queue {
public:
	Queue(int size = 20) {
		_pQue = new int[size];
		_front = _rear = 0;
		_size = size;
	}
	Queue(const Queue& other) {
		_size = other._size;
		_front = other._front;
		_rear = other._rear;
		_pQue = new int[other._size];
		memcpy(_pQue, other._pQue, _size * sizeof(int));
		/*for (int i = _front; i != _rear; i = (i + 1) % _size) {
			_pQue[i] = other._pQue[i];
		}*/
	}

	Queue& operator=(const Queue& other) {
		if (this == &other)
			return *this;
		delete[] _pQue;
		_size = other._size;
		_front = other._front;
		_rear = other._rear;
		_pQue = new int[other._size];
		//memcpy(_pQue, other._pQue, _size);
		for (int i = _front; i != _rear; i = (i + 1) % _size) {
			_pQue[i] = other._pQue[i];
		}
	}

	~Queue() {

		delete[] _pQue;
		_pQue = nullptr;
	}

	void push(int val) {
		if (full()) {
			resize();
		}
		_pQue[_rear] = val;
		_rear = (_rear + 1) % _size;
	}

	void pop() {
		if (empty()) {
			return;
		}
		_front = (_front + 1) % _size;
	}

	int front() {
		if (empty()) {
			return -1;
		}
		return _pQue[_front];
	}

	bool full() {
		return (_rear + 1) % _size == _front;
	}

	bool empty() {
		return _rear == _front;
	}

	void resize() {
		int* ptemp = new int[2 * _size];
		int index = 0;
		for (int i = _front; i != _rear; i = (i + 1) % _size) {
			ptemp[index++] = _pQue[i];
		}
		_front = 0;
		_rear = index;
		_size *= 2 ;
		delete[] _pQue;
		_pQue = ptemp;
	}
private:
	int* _pQue;//申请队列的数组空间
	int _front;//队头
	int _rear;//队尾
	int _size;
};

int main() {
	Queue q;
	for(int i = 0; i < 20; i ++){
		q.push(i);
	}
	Queue q2(q);
	while (!q.empty()) {
		cout << q.front() << endl;
		q.pop();
	}
	cout << "********" << endl;
	
	while (!q2.empty()) {
		cout << q2.front() << endl;
		q2.pop();
	}
	return 0;
}

指向成员(成员变量和成员方法)的指针

class A {
public:
	A() {
		cout << "默认构造" << endl;
		m_b++;
	}
	void show() {
		cout << "show m_a:"<< m_a  << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	static void showm_b() {
		cout << "showm_b" << m_b << endl;
	}
//private:
	static int m_b;
	int m_a;
};
int A::m_b = 0;
int main() {
	A a;
	//指向普通成员变量的指针
	int A::* p = &A::m_a;
	a.*p = 20;
	a.show();

	int * p2 = &A::m_b;
	*p2 = 30;
	cout <<"A::m_b: " << A::m_b << endl;
	//指向普通成员函数的指针
	void(A:: * pfun)() = &A::show;
	(a.*pfun)();

	//指向静态成员函数的指针
	void(* pfun2)() = &A::showm_b;
	(*pfun2)();
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgBFX7BO-1680868402412)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221202154231666.png)]

c++类库编程基础

函数模板

  1. 函数模板
  2. 模板的实例化
  3. 模板函数(现在的函数名 = 函数名 + <>)

声明了一个函数模板之后,在函数的调用点(),编译器会实例化一个模板函数出来,被编译器所编译,实例化一个类型只会产生一次

  1. 模板类型参数

    template <typename/class T>

  2. 模板非类型参数

    template <typename/class T, int SIZE>

    SIZE就是模板的非类型参数,必须是整数类型(整数或者地址/引用)都是常量,只能使用

  3. 模板实参推演

    可以根据用户传入实参,可以推演出应该实例化什么类型,即调用时可以不带<typename/class>

  4. 模板的特例化

    有时对于编译器的实参推演,对于特定类型,逻辑是不对的,比如compare<>()只适合于int,double,对于const char* 不合适,因此用户自己提供一个对于const char *的特例化

  5. 函数模板,模板的特例化,非模板函数的重载关系

    非模板函数是函数名字与模板的名字相同,但是不是模板函数的函数。

    template<class T>
    bool fun(T a){ //函数模板
    ...
    }
    
    template<>
    bool fun(const char* a){//函数模板的特例化
        ...
    }
    
    bool fun(const char* a){//非模板函数
        ...
    }
    
    调用时,优先找普通函数,再找特例化再找模板函数
    
  • 模板代码不能在一个文件中定义,在另一个文件使用,模板代码调用之前,必须能看到模板定义的地方,这样模板才能正常实例化,产生被编译器编译的代码
  • 如果分文件编译,调用的文件会给 bool compare(int,int) 产生符号,但是位置是UND,在链接的时候去找这个函数的位置,但是实例化是发生在函数的调用点,定义模板的文件不会产生 bool compare(int,int)的位置,因此会出现链接错误
  • 所以模板代码都在头文件中,在源文件中直接进行#include,预处理的时候直接展开

类模板

类名 + 才是类名,构造和析构函数名可以不加,但是其他出现模板的地方都要加上类型参数列表

类模板中的方法只有在被使用的时候才会被实例化

template<typename T>
class SeqStack 
{
public:
	SeqStack<T>(int size = 10) : _pstack(new T[size]), _top(0), _size(size) {}
	~SeqStack<T>() 
	{
		if (_pstack != nullptr) 
		{
			delete[] _pstack;
			_pstack = nullptr;
		}
	}
	SeqStack<T>(const SeqStack<T>& stack):  _top(stack._top), _size(stack._size)
	{
		_pstack = new T[_size];
		//如果用memcpy,如果T是一个对象,且对象中存在指针,那么使用memcpy逐字节copy做的都是浅拷贝,不可以
		//memcpy(_pstack, stack._pstack, sizeof(T) * _size);
		for(int i = 0; i < _top; i++)
		{
			_pstack[i] = stack._pstack[i];
		}
	}
	SeqStack<T>& operator=(const SeqStack<T>& stack)
	{
		if (this == &stack)
			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];
		}
	}

	void push(const T& val);
	void pop()
	{
		if (empty())
			return;
		--_top;
	}
	T top() const
	{
		if (empty())
			throw "stack is empty!";
		return _pstack[_top - 1];

	}
	bool full() const
	{
		return _top == _size;
	}
	bool empty() const
	{
		return _top == 0;
	}
private:
	T* _pstack;
	int _top;
	int _size;

	void expand() 
	{
		T* ptmp = new T[_size * 2];
		for (int i = 0; i < _top; i++) {
			ptmp[i] = _pstack[i];
		}
		delete[] _pstack;
		_size *= 2;
		_pstack = ptmp;
	}
};

template<class T>
void SeqStack<T>::push(const T& val) {
	if (full())
	{
		expand();
	}
	_pstack[top++] = val;
}

容器的空间配置器

自己实现的vector

template<typename T>
class myvector
{
public:
	myvector(int size = 10)
	{
		_first = new T[size];
		_last = _first;
		_end = _first + size;
	}

	~myvector()
	{
		delete[] _first;
		_first = _last = _end = nullptr;
	}

	myvector(const myvector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		_first = new T[size];
		_last = _first;
		_end = _first + size;

		for (int i = 0; i < rhs._last - rhs._first; i++) 
		{
			*_last = rhs._first[i];
			_last++;
		}
	}

	myvector<T>& operator=(const myvector<T>& rhs)
	{
		if (this == &rhs) {
			return *this;
		}
		delete[] _first;

		int size = rhs._end - rhs._first;
		_first = new T[size];
		_last = _first;
		_end = _first + size;

		for (int i = 0; i < rhs._last - rhs._first; i++)
		{
			*_last = rhs._first[i];
			_last++;
		}
	}

	void push_back(const T& val)
	{
		if (full())
			expand();
		*_last++ = val;
	}

	void pop_back()
	{
		if (empty())
			return;
		_last--;
	}

	T back()const
	{
		return *(_last - 1);
	}

	int size() {
		return _last - _first;
	}

	bool full()const
	{
		return _last == _end;
	}

	bool empty()const
	{
		return _last == _first;
	}
private:
	T* _first;
	T* _last;
	T* _end;

	

	void expand()
	{
		int size = _end - _first;
		T* ptemp = new T[size * 2];
		for (int i = 0; i < size; i++) {
			ptemp[i] = _first[i];
		}
		delete _first;
		_first = ptemp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};
  • 空间配置器的重要性:

    • 没有内存配置器,当定义一个存放类的myvector时,会直接调用类的10次默认构造和析构,在new T[size],不止开辟了空间,还调用构造去初始化,在delete[]时,把每个空间都认为有类存在然后析构,但是我们只需要析构里面有效的容器,然后再释放空间。
    • push_back时,我们想要的是直接在_last上构造对象,但是却变成了赋值.pop_back也没有顺利析构,只是逻辑上删除,如果类中有占用堆内存,那么就会出现内存泄漏。
  • 空间配置器:内存开辟/内存释放 对象构造/对象析构,以上四个内容都由空间配置器完成

加上空间配置器

template<typename T>
class Allocator {
public:
	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,在p位置上
	}
	void destroy(T* p) {//对象析构
		p->~T();
	}
};

template<typename T, typename Alloc = Allocator<T>>
class myvector
{
public:
	myvector(int size = 10)
	{
		//_first = new T[size];

		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}

	~myvector()
	{
		//delete[] _first;
		       
		_allocator.deallocate(_first);
		_first = _last = _end = nullptr;
	}

	myvector(const myvector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;

		for (int i = 0; i < rhs._last - rhs._first; i++) 
		{
			//*_last = rhs._first[i];
			//_last++;
			_allocator.construct(_first + i, rhs._first[i]);
		}
	}

	myvector<T>& operator=(const myvector<T>& rhs)
	{
		if (this == &rhs) {
			return *this;
		}
		//delete[] _first;
		for (T* p = _first; p != _last; ++p) {
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_allocator.allocate(size);

		int len = rhs._last - rhs._first;
		_last = _first + len;
		_end = _first + size ;

		for (int i = 0; i < len; i++)
		{
			//*_last = rhs._first[i];
			//_last++;
			_allocator.constract(_first + i, rhs._first[i]);
		}
	}

	void push_back(const T& val)
	{
		if (full())
			expand();
		//*_last++ = val;
		_allocator.construct(_last, val);
		_last++;
	}

	void pop_back()
	{
		if (empty())
			return;
		//_last--;
		_last--;
		_allocator.destroy(_last);
	}

	T back()const
	{
		return *(_last - 1);
	}

	int size() {
		return _last - _first;
	}

	bool full()const
	{
		return _last == _end;
	}

	bool empty()const
	{
		return _last == _first;
	}
private:
	T* _first;
	T* _last;
	T* _end;
	Alloc _allocator;
	

	void expand()
	{
		int size = _end - _first;
		//T* ptemp = new T[size * 2];
		T* ptemp = _allocator.allocate(size * 2);

		for (int i = 0; i < size; i++) {
			//ptemp[i] = _first[i];
			_allocator.construct(ptemp + i, _first[i]);
		}
		//delete _first;
		for (T* p = _first; p != _last; p++) {
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);

		_first = ptemp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};

class Test {
public:
	Test() {
		cout << "Test()" << endl;
	}
	~Test() {
		cout << "~test()" << endl;
	}
	Test(const Test& t) {
		cout << "Test(const Test& t)" << endl;
	}
};

int main() 
{
	/*myvector<int> myvec;
	for (int i = 0; i < 20; i++)
	{
		myvec.push_back(i);
	}


	myvector<int> vec2(myvec);
	vector<int> v;

	while (!vec2.empty())
	{
		cout << vec2.back() << endl;
		vec2.pop_back();
	}*/
	Test t1, t2, t3;
	cout << "**********" << endl;
	myvector<Test> vec;
	vec.push_back(t1);
	vec.pop_back();

	cout << "**********" << endl;
	return 0;
}

运算符重载

Test运算符重载

  • 注意 Test operator++(int){} 和 Test& operator++(){}
class Test {
public:
	Test(int i1 = 0, int i2 = 0)
		:_i1(i1), _i2(i2){}
	~Test();
	/*Test operator+(const Test& src) {
		return Test(_i1 + src._i1, _i2 + src._i2);
	}*/
	void show() {
		cout << _i1 << " " << _i2 << endl;
	}
	
	Test operator++(int) {//默认,有参数的是后置++,
		/*Test tmp = *this;
		_i1++;
		_i2++;*/
		return Test(_i1++, _i2++);
	}

	Test& operator++() {//默认,无参数的是前置++,
		_i1++;
		_i2++;
		return *this;
	}

	void operator+=(const Test& src) {
		_i1 += src._i1;
		_i2 += src._i2;
	}

	
private:
	int _i1;
	int _i2;

	friend Test operator+(const Test& lhs, const Test& rhs);
	friend ostream& operator<<(ostream& out, const Test& src);
	
	friend istream& operator>>(istream& in, Test& src);
};
ostream& operator<<(ostream& out, const Test& src) {
	out << src._i1 << " " << src._i2;
	return out;
}

Test operator+(const Test& lhs, const Test& rhs) {
	return Test(lhs._i1 + rhs._i1, lhs._i2 + rhs._i2);
}


istream& operator>>(istream& in, Test& src) {
	in >> src._i1 >> src._i2;
	return in;
}


int main(){
    Test t1,t2,t3;
    t1 = t2 + t3;//相当于编译器去找加号左边的加法函数 相当于t2.+(t3),优先找成员方法,如果没有就去找全局方法
    t1 = t2 + 10;//先找Test类型有没有 Test(int) 的构造函数,做隐式转换,再调用 t2.(Test(10));
    
    return 0;
}

MyString带迭代器版本

迭代器:提供一种统一的方式,透明的遍历容器,在提供迭代器时,通常将迭代器定义在容器内部,在用的时候也是Container::iterator

class MyString {
public:
	MyString(const char* p = nullptr) {
		cout << "MyString(const char* p = nullptr)" << endl;
		if (p != nullptr) {
			_pstr = new char[strlen(p) + 1];
			strcpy(_pstr, p);
		}
		else {
			_pstr = new char[1];
			*_pstr = '\0';
		}	
	}

	MyString(MyString&& src) { //提供右值的构造函数,提升operator+的效率,使用临时对象给新对象拷贝构造
		cout << "MyString(MyString&& src)" << endl;
		_pstr = src._pstr;
		src._pstr = nullptr;
	}

	~MyString() {
		cout << "~MyString()" << endl;
		delete[] _pstr;
		_pstr = nullptr;
	}

	MyString(const MyString& src) {
		cout << "MyString(const MyString& src)" << endl;
		_pstr = new char[strlen(src._pstr) + 1];
		strcpy(_pstr, src._pstr);
	}

	MyString& operator=(const MyString& src) {
		if (this == &src) {
			return *this;
		}

		delete[] _pstr;

		_pstr = new char[strlen(src._pstr) + 1];
		strcpy(_pstr, src._pstr);
		
	}
	bool operator>(const MyString& str) const {
		return strcmp(_pstr, str._pstr) > 0;
	}

	bool operator<(const MyString& str) const {
		return strcmp(_pstr, str._pstr) <0;
	}

	bool operator==(const MyString& str) const {
		return strcmp(_pstr, str._pstr) == 0;
	}
	char& operator[](int index) {
		return _pstr[index];
	}

	const char operator[](int index)const { //提供常类型
		return _pstr[index];
	}
	const char* c_str() {
		return _pstr;
	}

	int length()const {
		return strlen(_pstr);
	}

	//提供迭代器
	class iterator {
	public:
		iterator(char* p = nullptr):_pstr(p){}
		bool operator!=(const iterator& it) {
			return _pstr != it._pstr;
		}

		void operator++() { //前置++
			++_pstr;
		}

		char& operator*() {
			return *_pstr;
		}
	private:
		char* _pstr;
	};

	iterator begin() {
		return iterator(_pstr);
	}
	iterator end() {
		return iterator(_pstr + length());
	}

private:
	char* _pstr;

	friend ostream& operator<<(ostream& out, const MyString& str);
	friend MyString operator+(const MyString& lhs, const MyString& rhs);
};

MyString operator+(const MyString& lhs, const MyString& rhs) {
	/*char* tmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
	strcpy(tmp, lhs._pstr);
	strcat(tmp, rhs._pstr);
	MyString str(tmp);
	delete[] tmp;*/
	MyString temp;
	temp._pstr = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
	strcpy(temp._pstr, lhs._pstr);
	strcat(temp._pstr, rhs._pstr);
	return temp;
}

ostream& operator<<(ostream& out, const MyString& str) {
	out << str._pstr;
	return out;
}

vector带迭代器

template<typename T>
class Allocator {
public:
	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,在p位置上
	}
	void destroy(T* p) {//对象析构
		p->~T();
	}
};

template<typename T, typename Alloc = Allocator<T>>
class myvector
{
public:
	myvector(int size = 10)
	{
		//_first = new T[size];

		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}

	~myvector()
	{
		//delete[] _first;
		for (T* p = _first; p != _last; ++p) {
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = _last = _end = nullptr;
	}

	myvector(const myvector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;

		for (int i = 0; i < rhs._last - rhs._first; i++)
		{
			//*_last = rhs._first[i];
			//_last++;
			_allocator.construct(_first + i, rhs._first[i]);
		}
	}

	myvector<T>& operator=(const myvector<T>& rhs)
	{
		if (this == &rhs) {
			return *this;
		}
		//delete[] _first;
		for (T* p = _first; p != _last; ++p) {
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_allocator.allocate(size);

		int len = rhs._last - rhs._first;
		_last = _first + len;
		_end = _first + size;

		for (int i = 0; i < len; i++)
		{
			//*_last = rhs._first[i];
			//_last++;
			_allocator.constract(_first + i, rhs._first[i]);
		}
	}

	void push_back(const T& val)
	{
		if (full())
			expand();
		//*_last++ = val;
		_allocator.construct(_last, val);
		_last++;
	}

	void pop_back()
	{
		if (empty())
			return;
		//_last--;
		_last--;
		_allocator.destroy(_last);
	}

	T back()const
	{
		return *(_last - 1);
	}

	int size() {
		return _last - _first;
	}

	bool full()const
	{
		return _last == _end;
	}

	bool empty()const
	{
		return _last == _first;
	}

	T& operator[](int index) {
		if (index < 0 || index >= size()) {
			throw"OutOfRangeException";
		}
		return _first[index];
	}

	//template<typename T>
	class iterator {
	public:
		iterator(T* p):_p(p){}
		bool operator!=(const iterator& it) {
			return _p != it._p;
		}

		void operator++() { //前置++
			++_p;
		}

		T& operator*() {
			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* ptemp = new T[size * 2];
		T* ptemp = _allocator.allocate(size * 2);

		for (int i = 0; i < size; i++) {
			//ptemp[i] = _first[i];
			_allocator.construct(ptemp + i, _first[i]);
		}
		//delete _first;
		for (T* p = _first; p != _last; p++) {
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);

		_first = ptemp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};

迭代器失效

  • 在对vector插入和删除,操作点及其之后的容器都失效,扩容之后迭代器全部失效

    • 解决办法:insert会返回插入元素的迭代器,erase返回删除元素后面的元素
    • 实现方法:在容器类中新增一个由Iterator_Base结构体 类中添加Iterator_Base * _head作为链表的头节点,用于管理所有迭代器,Iterator_Base由一个迭代器指针和指向下一个Iterator_Base的指针组成,每生成一个迭代器就new一个Iterator_Base,插入到链表中,每进行一次insert和erase,expend就对Iterator_Base进行遍历,使迭代器失效
    struct Iterator_Base {
    		struct Iterator_Base(iterator* c = nullptr, Iterator_Base* n = nullptr):_cur(c), _next(n){}
    		iterator* _cur; //指向当前迭代器
    		Iterator_Base* _next;//指向下一个迭代器
    	};
    

深入理解new和delete

  • malloc按字节开辟,new开辟是指定类型,因此malloc返回void* ,new返回类型指针
  • malloc只开辟空间,new还会进行初始化
  • malloc开辟失败返回nullptr, new开辟返回bad_alloc
  • free只释放内存 ;delete 执行析构。
void* operator new(size_t size) {
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr: " << p << endl;
	return p;
}

void operator delete(void* ptr) {
	cout << "operator delete addr: " << ptr << endl;
	free(ptr);
}
void* operator new[](size_t size) {
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr: " << p << endl;
	return p;
}

void operator delete[](void* ptr) {
	cout << "operator delete[] addr: " << ptr << endl;
	free(ptr);
}

int main() {
	Test* p = new Test[5]; //开辟空间后,会在起始位置多开辟一块空间存储元素个数5
	delete[] p;//delete时因为有[],所以会在起始位置查看总共需要析构多少元素
	return 0;
}

  • 如何实现检查内存泄漏

    • 重载new和delete,可以将开辟的空间加入一个容器中,每delete一个就删除一个,查看容器中有没有没有被释放的空间
  • new和new[] delete和delete[]

    • new[]在开辟空间后,会在起始位置多开辟一块空间存储元素个数,delete[]在释放空间时,也会将指针前移一块空间去查找元素个数,来正确释放空间,因此绝对不能混用

对象池

  • pop和push在不断的开辟和释放空间
template<typename T>
struct QueueItem {
	QueueItem(T data = T()): _data(data), _next(nullptr){}
	T _data;
	QueueItem* _next;

};


template <typename T>
class Queue {
public:
	Queue() {
		_front = _rear = new QueueItem<T>();
	}

	~Queue() {
		QueueItem<T>* cur = _front;
		while (cur != nullptr) {
			_front = _front->_next;
			delete cur;
			cur = _front;
		}
		
	}
	void push(const T& val) {          //在调用malloc
		QueueItem<T>* item = new QueueItem<T>(val);
		_rear->_next = item;
		_rear = item;
		
	}

	void pop() {  //在调用free
		if (empty())
			return;
		QueueItem<T>* first = _front->_next;
		_front->_next = first->_next;
		if (_front->_next == nullptr) {//注意如果队内只有一个元素,删除此元素_rear会找不到
			_rear = _front;
		}
		delete first;
	}

	T front()const {
		return _front->_next->_data;
		
	}
	bool empty()const {
		return  _front == _rear;
	}
private:
	QueueItem<T>* _front; //头结点
	QueueItem<T>* _rear;  //队尾元素

};

  • 实现一个内存池,pop和push时不再需要归还内存和开辟内存,直接在初始化的时候做好了

    template<typename T>
    struct QueueItem {
    	QueueItem(T data = T()): _data(data), _next(nullptr){}
    	
    	void* operator new(size_t size) {
    		if (_itemPool == nullptr) {
    			cout << "开辟池子" << endl;
    			_itemPool = (QueueItem*)new char[POOL_ITEM_SIZE * sizeof(QueueItem)]; //按字节开辟
    			QueueItem* p = _itemPool;
    			for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p) {
    				p->_next = p + 1;
    			}
    			p->_next = nullptr;
    		}
    
    		QueueItem* p = _itemPool;
    		_itemPool = _itemPool->_next;
    		return p;
    
    	}
    
    	void operator delete(void* ptr) {
    		QueueItem* p = (QueueItem*)ptr;
    		p->_next = _itemPool;
    		_itemPool = p;
    	}
    	
    	T _data;
    	QueueItem* _next;
    
    	static QueueItem* _itemPool; //指向内存池的头节点
    	static const int POOL_ITEM_SIZE = 100000; //内存池的大小
    };
    
    template <typename T>
    QueueItem<T>* QueueItem<T>::_itemPool = nullptr;
    

OOP

protect:在外部是不可以访问的,和私有一样

派生类可以访问继承下来之后变为protected和public的

继承下来的成员,属性不会超过继承方式,(即以private继承下来的,最多是私有成员)

当基类中的成员不想被外部访问,但是希望被派生类访问,定义成protected

1)public继承方式

  • 基类中所有 public 成员在派生类中为 public 属性;
  • 基类中所有 protected 成员在派生类中为 protected 属性;
  • 基类中所有 private 成员在派生类中不能使用(不可见)。

2) protected继承方式

  • 基类中的所有 public 成员在派生类中为 protected 属性;
  • 基类中的所有 protected 成员在派生类中为 protected 属性;
  • 基类中的所有 private 成员在派生类中不能使用(不可见)。

3) private继承方式

  • 基类中的所有 public 成员在派生类中均为 private 属性;
  • 基类中的所有 protected 成员在派生类中均为 private 属性;
  • 基类中的所有 private 成员在派生类中不能使用(不可见)。

派生类构造过程

  • 派生类如何初始化继承来的成员呢
    1. 派生类继承了成员和方法,但是没有继承构造和析构函数,但是又可以调用
    2. 派生类通过调用构造函数,析构函数来初始化和清理继承来的成员变量

重载, 隐藏, 覆盖

重载:一个作用域,函数名相同,参数列表不同。

隐藏:继承结构中派生类的同名成员,把基类的同名成员给隐藏了。

覆盖:返回值,函数名参数列表都相同,且基类是虚函数,那么就会覆盖

	Driver d;
	d.show();

	d.show(10);//派生类只有show(),基类有show(int),此行报错,因为基类的show(int)被隐藏
	d.Base::show(10);//想调用父类的,需要这样
  • 派生类对象可以赋值给基类对象,派生类对象中含有基类部分

  • 基类对象不可以赋值给派生类对象,因为基类中不含派生类部分

  • 基类指针(引用)可以指向派生类,但是只能访问派生类的基类部分

  • 派生类指针(引用)不可以指向基类,因为派生类的指针会超出基类对象的范围,不合法

静态绑定:编译时期绑定:例如基类指针指向子类对象,调用基类派生类的同名函数(非虚函数),调用的一定是父类的。

虚函数
  1. 如果类中定义了虚函数,那么编译阶段,编译器会给这个类产生一个虚函数表,虚函数表主要存储的内容是RTTI指针(运行时类型信息,Base类就指向“Base”,Derive类就指向“Derive”)和虚函数的地址, 程序运行时,虚函数表被加载到.rodata区,只读区
  2. 每new一个对象,此对象会多一个虚函数指针,指向.rodata虚函数表
  3. 如果派生类中和基类继承的某个方法,返回值,函数名,参数列表相同,且基类为虚函数,那么派生类的此方法,自动处理为虚函数,此时为覆盖关系,通俗的说也叫重写此方法
  4. 在派生类中的虚函数表,如果重写了方法,那么虚函数表中原本基类的虚函数地址,被派生类的虚函数地址覆盖
  5. 调用过程:基类指针指向派生类对象,调用函数,先去基类查看函数的类型,如果是普通函数,那么就是静态绑定,直接调用父类函数。如果发现是虚函数,那么进行动态绑定,首先查看对象的前四个字节(虚函数表)找到虚函数中的虚函数地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GI239aeO-1680868402412)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221207105510525.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toLdnAr3-1680868402413)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221207105538506.png)]

虚函数依赖:

  1. 虚函数必须能产生地址,存在虚函数表

  2. 对象必须存在,因为对象中才有虚函数指针指向虚函数表

    因此,构造函数不能是虚函数,static函数不能是虚函数,static函数也不依赖于对象,调用static函数直接通过类去访问

基类的析构函数最好定义为虚函数,方便多态时能正确调用派生类的析构函数, 正确释放资源

通过指针和引用调用虚函数才会发生动态绑定

一些面试题

  1. 当被覆盖的函数存在默认参数的时候,会发现,动态绑定时,确实调用了派生类函数,但是参数却是基类的默认参数

    原因:函数的调用时间发生在运行时,但是参数的压栈是在编译时期压栈的,此时编译器只知道把默认参数的值压入栈,由于是基类的指针因此压入的是基类的默认参数

class Base {
public:
	virtual void show(int i = 10) { cout << "Base::show(int)" <<i<< endl; }
};
class Driver:public Base{
public:
	void show(int i = 20) { cout << "Driver::show(int)" <<i<< endl; }
};
int main() {
	
	Base* pb = new Driver();

	pb->show();//Driver::show(int)10 !!! 

	return 0;
}
  1. 当把派生类的覆盖的函数写为private,还能不能通过动态绑定调用成功?

    可以,成员方法的访问权限是在编译阶段有效的,编译器运行时期才确定,因此如果在基类中是private,那会直接编译报错,因为在编译阶段就确定用不了

class Base {
public:
	virtual void show() { cout << "Base::show(int)" << endl; }
};
class Driver:public Base{
private:
	void show() { cout << "Driver::show(int)" << endl; }
};
int main() {
	
	Base* pb = new Driver();

	pb->show();//Driver::show(int)10 !!! 

	return 0;
}
  1. 看下面代码分析

    class Base {
    public:
    	Base() { 
    		cout << "Base()" << endl; 
    		clear(); 
    	}
    	void clear() {
    		memset(this, 0, sizeof(*this));
    	}
    	virtual void show() { cout << "Base::show()" << endl; }
    	virtual ~Base() { cout << "~Base()" << endl; }
    };
    
    class Driver:public Base{
    public:
    	Driver()  { cout << "Driver()" << endl; }
    	void show() { cout << "Driver::show()" << endl; }
    	~Driver() { cout << "~Driver()" << endl; }
    };
    
    int main() {
    	Base* pb = new Base();
    	pb->show();//不能调用,在生成对象后,将对象的内存清空,指向虚函数表的指针也被清空,无法访问
    	delete pb;
    
    	Base* pd = new Driver();
    	pd->show();//可以调用
    	delete pd;
    	return 0;
    }
    
  • 进入Base()函数:
    1. 把调用方地址压入栈
    2. 将栈底指针移到Base()的栈底
    3. 给当前函数开辟栈帧
    4. 初始化栈帧(win的vs才做,GCC/G++不做)
    5. 给vfptr赋值为Base虚函数表的地址

虚基类和虚继承

  • virtual修饰方法,就是虚函数,修饰继承方式,就是虚继承

当常出现虚继承时,基类的成员不再直接存储在派生类对象体内,而是有vbptr,指向vbtable,vbtable中为指针的偏移量和基类成员离指针的偏移量

当一个类是虚继承时,使用基类指针指向他时,会指在什么位置?

  1. 基类指针指向派生类对象,指向的一定是派生类对象的基类数据部分,当普通继承时,基类部分就在派生类的开头
  2. 但是虚继承,就是虚基类了,此时虚基类的数据成员被偏移去了派生类的后面,原本的位置被虚基类指针(vbptr)代替,此时基类指针会指向偏移过后的基类数据地址
class Base {
public:
	virtual void fun() { cout << "Base::fun()" << endl; }
private:
	int ma;
};
class Driver:virtual public Base{
public:
	void fun() { cout << "Driver::fun()" << endl; }
private:
	int mb;
};
int main() {
	Base* pb = new Driver();
	pb->fun();
	delete pb;//释放内存出现错误,因为没有指向Driver对象的开头位置,而是偏移后的位置
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1LAhrnOk-1680868402413)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221208120454665.png)]

  • Driver对象内存如上图所示,如果虚函数是来自基类,那么vfptr会在基类数据部分,和上图一样,如果虚函数是自己的,那么vfptr就在派生类数据域

菱形继承内存如下图:(B,C虚继承A,D继承B,C),此时给D初始化的时候,需要通过D调用A的某个构造来初始化A的数据部分,或者A有默认构造

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Se1OYLHh-1680868402413)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221208154301684.png)]

四种类型转换方式

const_cast:一般用于指针或者引用,去掉常量属性的类型转换, 在转换时,编译阶段会检查转换前后能访问的字节数是不是一样大(类型需要一致,int 和const int),如果不一样会编译不通过

使用const_cast去除const限定的目的不是为了修改它的内容
使用const_cast去除const限定,通常是为了函数能够接受这个实际参数

static_cast:提供编译器认为安全的类型转换,当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换。

可以将void*指针转换为某一类型的指针

reinterpret_cast:类似C风格的强制转换,“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。不安全

dynamin_cast:主要用在继承结构中,可以支持RTTI类型识别的向下转换,如果转换成功,返回转换的类型,转换失败返回nullptr

STL

  • 底层数据结构动态开辟的数组,每次以原来的二倍扩容,通过空间配置器来析构构造内存,内存开辟和释放,

  • reserve() 预留空间,仅开辟空间,不会添加新元素

  • resize(size_t size) 容器扩容,开辟size*sizeof(T)的空间 会添加新元素,使用默认构造

deque

  • 底层数据结构是以二倍大小动态扩展的二维数组,一维存储指针,指向另一个数组(4096/sizeof(T)大小)。扩容的时候,把一维扩容两两倍大小,然后把原来的数据copy到中间位置(oldesize/2的位置)

Push_back

Push_front

pop_back

pop_front

Insert(it)

Erase(it)

list

  • 底层是双向的循环链表,函数和deque支持的函数一样

  • deque对比vector,插入和删除vector会稍微比deque好一点,理论上都是o(n),但是deque的内存不是连续的,挪起来慢一点

标准容器适配器

  • 适配器没有自己的数据结构,是另一个容器的封装,通过底层以来的容器实现自己的方法

*没有实现迭代器!

Template<typename T,typename Container = deque<T>>

Class Stack{

Public:

Void push(const T &val) {con .push(val);}

Void pop(const T &val) {con .pop(val);}

T top()const{ return con.back();}

Private:

Container con;

}

Priority_queue :Push() pop() top()队顶元素

Stack》deque

Queue》deque

优先级队列 》 vector

  • vector初始内存使用效率低,需要缓慢增长,deque直接就是4096/sizeof(T)

  • queue需要头尾都操作,因此是deque,queue如果依赖于vector队头操作效率低

  • vector需要连续存储空间,当存储大量数据,deque内存使用效率更高

priority_queue底层是一个大根堆结构,大根堆结构使用vector存储

无序关联容器

关联容器是不可以通过迭代器插入元素的,其底层是哈希表,存储的位置是由哈希函数决定的

但是有erase(it) find(key),可以通过迭代器进行遍历

unordereed_map unordereed_set unordereed_multiset unordereed_multimap

底层都是链式哈希表,增删改O(1)

  • map重载了 operator,通过传入key返回value,但是map的中括号查询后,如果不存在会默认插入一对数据{key,value()}

有序关联容器

set multset map multimap

底层都是红黑树 增删改O(n)

当存储自定义类型时,必须重载<运算符

与无序关联容器的区别只有,数据存储是否有序,来自于底层的数据结构,红黑树会排序

迭代器

iterator =》const_iterator可以,const_iterator是基类,普通迭代器是派生类

因此可以const_iterator it = Obj.begin()

函数对象

函数对象就是c的函数指针

//c:
int sum(int a, int b){
	return a + b;
}
int ret = sum(a, b);
//c++:
class Sum{
public:
	int operator()(int a, int b){
		return a + b;
	}
}
Sum sum;
int ret = sum(a + b);
  • 使用c++函数模板,如果此时有需求需要递减排列,那就需要重新实现一个compare

    template<typename T>
    bool compare(T a, T b) {
    	return a < b;
    }
    
    int main() {
    	
    	cout << compare(10, 20) << endl;
    
    	return 0;
    }
    
  • 使用c的函数指针方式,此时通过函数指针调用函数,即使函数其实很简单,也是没有办法内联的,但是函数调用开销大

    template<typename T>
    bool mygreater(T a, T b) {
    	return a > b;
    }
    template<typename T>
    bool myless(T a, T b) {
    	return a < b;
    }
    template<typename T,  typename Compare>
    bool compare(T a, T b, Compare comp) {
    	return comp(a, b);
    }
    int main() {
    	cout << compare(10, 20, mygreater<int>) << endl;
    	return 0;
    }
    
  • 使用函数对象调用,可以省略函数调用的开销,在编译阶段就能确定是哪个函数,比函数指针效率高,且通过对象可以添加成员变量,来实现许多其他功能,例如查看使用次数。

    template<typename T>
    class myless {
    public:
    	bool operator()(T a, T b) {
    		return a < b;
    	}
    };
    template<typename T>
    class mygreater {
    public:
    	bool operator()(T a, T b) {
    		return a > b;
    	}
    };
    template<typename T,  typename Compare>
    bool compare(T a, T b, Compare comp) {
    	return comp(a, b);
    }
    int main() {
    	cout << compare(10, 20, mygreater<int>()) << endl;
    	return 0;
    }
    

泛型算法与绑定器

binary_search(vec.begin(), vec.end(),val);//二分查找val,在有序的连续存储的容器中(vector),返回bool值

绑定器 + 二元函数对象 =》医院函数对象

头文件:#include

bind1st(greater<>(), val):绑定val到仿函数的第一个参数

bind2st:

find_if(vec.begin(), vec.end(), val, bind1st(greater(), 48) )//查找第一个小于48的元素,返回一个元素的迭代器 相当于 greater(48, val)

multset map multimap

底层都是红黑树 增删改O(n)

当存储自定义类型时,必须重载<运算符

与无序关联容器的区别只有,数据存储是否有序,来自于底层的数据结构,红黑树会排序

迭代器

iterator =》const_iterator可以,const_iterator是基类,普通迭代器是派生类

因此可以const_iterator it = Obj.begin()

函数对象

函数对象就是c的函数指针

//c:
int sum(int a, int b){
	return a + b;
}
int ret = sum(a, b);
//c++:
class Sum{
public:
	int operator()(int a, int b){
		return a + b;
	}
}
Sum sum;
int ret = sum(a + b);
  • 使用c++函数模板,如果此时有需求需要递减排列,那就需要重新实现一个compare

    template<typename T>
    bool compare(T a, T b) {
    	return a < b;
    }
    
    int main() {
    	
    	cout << compare(10, 20) << endl;
    
    	return 0;
    }
    
  • 使用c的函数指针方式,此时通过函数指针调用函数,即使函数其实很简单,也是没有办法内联的,但是函数调用开销大

    template<typename T>
    bool mygreater(T a, T b) {
    	return a > b;
    }
    template<typename T>
    bool myless(T a, T b) {
    	return a < b;
    }
    template<typename T,  typename Compare>
    bool compare(T a, T b, Compare comp) {
    	return comp(a, b);
    }
    int main() {
    	cout << compare(10, 20, mygreater<int>) << endl;
    	return 0;
    }
    
  • 使用函数对象调用,可以省略函数调用的开销,在编译阶段就能确定是哪个函数,比函数指针效率高,且通过对象可以添加成员变量,来实现许多其他功能,例如查看使用次数。

    template<typename T>
    class myless {
    public:
    	bool operator()(T a, T b) {
    		return a < b;
    	}
    };
    template<typename T>
    class mygreater {
    public:
    	bool operator()(T a, T b) {
    		return a > b;
    	}
    };
    template<typename T,  typename Compare>
    bool compare(T a, T b, Compare comp) {
    	return comp(a, b);
    }
    int main() {
    	cout << compare(10, 20, mygreater<int>()) << endl;
    	return 0;
    }
    

泛型算法与绑定器

binary_search(vec.begin(), vec.end(),val);//二分查找val,在有序的连续存储的容器中(vector),返回bool值

绑定器 + 二元函数对象 =》医院函数对象

头文件:#include

bind1st(greater<>(), val):绑定val到仿函数的第一个参数

bind2st:

find_if(vec.begin(), vec.end(), val, bind1st(greater(), 48) )//查找第一个小于48的元素,返回一个元素的迭代器 相当于 greater(48, val)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值