c++ 内功
虚拟地址空间划分
存在,能看见 是物理的
存在, 看不见 是透明的
不存在, 看得见 是虚拟的
不存在, 看不见 被删除了
-
.text段:指令段,存放指令
-
.rodata:只读数据段,常量就存放在此,例如const char *p = “hello”;//p在栈上,而“hello”在只读数据段上,因为这个是字符串常量
-
.data:数据段,存放已经初始化且初始化不为0的全局变量
-
.bss:数据段,存放初始化为0和未初始化的全局变量,内核会将此数据段都置0
-
.heap:堆区,程序员自己管理内存
-
.dll,*so:加载共享库,前者为win,后者为linux
-
.stack:
-
命令行参数,环境变量:
-
内核空间:
- ZONE_DMA
- ZONE_BORMAL
- 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:函数调用指令
- 把下一行指令的地址(位于.text段)压栈(问题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
- 编译过程中符合不分配虚拟地址,
- 在链接阶段,
- 会合并所有的段,引用的符号也解析成功(找到全局变量所在文件)
- 符号重定向,分配虚拟地址
基础知识精讲
默认形参的函数
-
给默认参数从右往左给
-
效率问题,默认参数少一次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
- c++为什么支持函数重载,c语言不支持
- c++给函数产生符号,是函数名+ 参数列表
- c给函数产生符号,是函数名
- 函数重载需要注意什么
- 函数名相同,参数列表个数或者类型不同,且在同一个作用域下是函数重载(返回值不同不行)
- const和不加const是一个类型
- volatile
- 静态多态
- 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
-
const修饰的变量不能再作为左值,初始化完成后,值不能被修改,只是不能作为左值修改,直接通过地址能修改
-
c中不叫常量,叫做常变量,,且不能当初常量使用,例如定义数组大小c++中使用常量初始化叫常量,使用变量初始化叫常变量
-
c中就是当作个变量来编译生产指令,c++中所有出现const常量名字的地方,都被常量的初始值替换了
-
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 ** 错误
引用和指针
- 引用必须初始,指针可以不初始化
- 定义指针和引用和通过二者修改值在汇编层面是一样的
- 引用只有一级引用
定义引用不会时,参考定义指针如何定义
int (*q)[5] = &array; int (&q)[5] = array;
-
右值引用专门用来引用右值类型,指令上,自动产生临时量,直接引用临时量
-
写一句代码在内存0x0018ff44处写一个4字节的10
int *&&p = (int*)0x0018ff44; int * const &p = (int*)0x0018ff44;
new和delete
- new不仅开辟内存,还做了内存初始化
- 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++类库编程基础
函数模板
- 函数模板
- 模板的实例化
- 模板函数(现在的函数名 = 函数名 + <>)
声明了一个函数模板之后,在函数的调用点(),编译器会实例化一个模板函数出来,被编译器所编译,实例化一个类型只会产生一次
-
模板类型参数
template <typename/class T>
-
模板非类型参数
template <typename/class T, int SIZE>
SIZE就是模板的非类型参数,必须是整数类型(整数或者地址/引用)都是常量,只能使用
-
模板实参推演
可以根据用户传入实参,可以推演出应该实例化什么类型,即调用时可以不带<typename/class>
-
模板的特例化
有时对于编译器的实参推演,对于特定类型,逻辑是不对的,比如compare<>()只适合于int,double,对于const char* 不合适,因此用户自己提供一个对于const char *的特例化
-
函数模板,模板的特例化,非模板函数的重载关系
非模板函数是函数名字与模板的名字相同,但是不是模板函数的函数。
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 成员在派生类中不能使用(不可见)。
派生类构造过程
- 派生类如何初始化继承来的成员呢
- 派生类继承了成员和方法,但是没有继承构造和析构函数,但是又可以调用
- 派生类通过调用构造函数,析构函数来初始化和清理继承来的成员变量
重载, 隐藏, 覆盖
重载:一个作用域,函数名相同,参数列表不同。
隐藏:继承结构中派生类的同名成员,把基类的同名成员给隐藏了。
覆盖:返回值,函数名参数列表都相同,且基类是虚函数,那么就会覆盖
Driver d;
d.show();
d.show(10);//派生类只有show(),基类有show(int),此行报错,因为基类的show(int)被隐藏
d.Base::show(10);//想调用父类的,需要这样
-
派生类对象可以赋值给基类对象,派生类对象中含有基类部分
-
基类对象不可以赋值给派生类对象,因为基类中不含派生类部分
-
基类指针(引用)可以指向派生类,但是只能访问派生类的基类部分
-
派生类指针(引用)不可以指向基类,因为派生类的指针会超出基类对象的范围,不合法
静态绑定:编译时期绑定:例如基类指针指向子类对象,调用基类派生类的同名函数(非虚函数),调用的一定是父类的。
虚函数:
- 如果类中定义了虚函数,那么编译阶段,编译器会给这个类产生一个虚函数表,虚函数表主要存储的内容是RTTI指针(运行时类型信息,Base类就指向“Base”,Derive类就指向“Derive”)和虚函数的地址, 程序运行时,虚函数表被加载到.rodata区,只读区
- 每new一个对象,此对象会多一个虚函数指针,指向.rodata虚函数表
- 如果派生类中和基类继承的某个方法,返回值,函数名,参数列表相同,且基类为虚函数,那么派生类的此方法,自动处理为虚函数,此时为覆盖关系,通俗的说也叫重写此方法
- 在派生类中的虚函数表,如果重写了方法,那么虚函数表中原本基类的虚函数地址,被派生类的虚函数地址覆盖
- 调用过程:基类指针指向派生类对象,调用函数,先去基类查看函数的类型,如果是普通函数,那么就是静态绑定,直接调用父类函数。如果发现是虚函数,那么进行动态绑定,首先查看对象的前四个字节(虚函数表)找到虚函数中的虚函数地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GI239aeO-1680868402412)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221207105510525.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toLdnAr3-1680868402413)(F:\codestudy\coding笔记\施磊笔记\初级C++基础夯实.assets\image-20221207105538506.png)]
虚函数依赖:
-
虚函数必须能产生地址,存在虚函数表
-
对象必须存在,因为对象中才有虚函数指针指向虚函数表
因此,构造函数不能是虚函数,static函数不能是虚函数,static函数也不依赖于对象,调用static函数直接通过类去访问
基类的析构函数最好定义为虚函数,方便多态时能正确调用派生类的析构函数, 正确释放资源
通过指针和引用调用虚函数才会发生动态绑定
一些面试题
-
当被覆盖的函数存在默认参数的时候,会发现,动态绑定时,确实调用了派生类函数,但是参数却是基类的默认参数
原因:函数的调用时间发生在运行时,但是参数的压栈是在编译时期压栈的,此时编译器只知道把默认参数的值压入栈,由于是基类的指针因此压入的是基类的默认参数
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;
}
-
当把派生类的覆盖的函数写为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;
}
-
看下面代码分析
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()函数:
- 把调用方地址压入栈
- 将栈底指针移到Base()的栈底
- 给当前函数开辟栈帧
- 初始化栈帧(win的vs才做,GCC/G++不做)
- 给vfptr赋值为Base虚函数表的地址
虚基类和虚继承
- virtual修饰方法,就是虚函数,修饰继承方式,就是虚继承
当常出现虚继承时,基类的成员不再直接存储在派生类对象体内,而是有vbptr,指向vbtable,vbtable中为指针的偏移量和基类成员离指针的偏移量
当一个类是虚继承时,使用基类指针指向他时,会指在什么位置?
- 基类指针指向派生类对象,指向的一定是派生类对象的基类数据部分,当普通继承时,基类部分就在派生类的开头
- 但是虚继承,就是虚基类了,此时虚基类的数据成员被偏移去了派生类的后面,原本的位置被虚基类指针(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)
有序关联容器
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)