c++

argument实参 parameter形参
默认参数只在声明时指定,定义时不设置

多线程
智能指针
代理绑定
设计模式 了解

emplace_back代替push_back

  1. 减少对象构造和复制 / 移动操作

push_back需要先构造一个临时对象,然后将其复制或移动到容器中。而emplace_back则直接在容器的内存位置上构造对象,省去了临时对象的创建和后续的复制 / 移动操作。

  1. 直接使用参数构造对象

emplace_back可以接受构造对象所需的参数,直接在容器内构造对象:

// 使用push_back需要先创建对象
vec.push_back(MyClass(1, "hello"));  // 临时对象 + 移动操作

// 使用emplace_back直接在容器内构造
vec.emplace_back(1, "hello");  // 直接构造,无需临时对象
  1. 支持隐式转换
    emplace_back支持隐式类型转换,可以接受能转换为容器元素类型的参数:

移动语义

函数返回一个临时对象时调用拷贝构造,但构造后马上析构

cstring getstring(cstring &str)
{
	const char* pstr = str.c_str();
	cstring tmpstr(pstr);//普通构造
	return tmpstr;
}
int main()
{
	cstring str1("abcde");
	cstring str2;
	str2 = getstring(str1);
}

typename 强制声明类型

C++ 规定,当依赖名称表示一个类型时,必须使用 typename 显式声明:

编译时多态和运行时多态

编译时多态

编译时多态是指在编译阶段就确定调用哪个函数的多态形式,主要通过函数重载和模板来实现。

运行时多态

运行时多态是指在程序运行阶段才确定调用哪个函数的多态形式,主要通过虚函数和指针或引用来实现。

虚函数和虚函数表

当一个函数被声明为虚函数(在基类中使用 virtual 关键字声明)时,派生类可以重写这个虚函数。通过基类指针或引用调用虚函数时,程序会在运行时根据指针或引用实际指向的对象类型,来决定调用哪个类的虚函数版本。

虚函数表

  • 用于实现c++运行时多态(动态绑定)
  • 每个含有虚函数的类会自动生成一个虚函数表,表中存放所有虚函数的地址
  • 实现的对象中存储指向虚函数表的指针(vptr),运行时通过该指针调用对应虚函数
  • 每个类只有一份,存储在只读数据段

多重继承对vtable的影响

继承多个类就会有多个vptr,分别指向每个基类对应的vtable

虚继承对vtable的影响

虚继承解决菱形继承中重复基类实例的问题
引入虚基表(vbptr)记录虚基类相对对象的偏移
共享成员单独存放在最后

函数指针

在 C++ 中,函数名在大多数情况下可以隐式地转换为指向该函数的指针。所以下面这行代码:

int (*func)(int, double) = print;

而显式使用取地址符&来获取函数指针也是可行的,即:

int (*func)(int, double) = &print;

c++11

NULL c++中define为0,c语言中define为(void* 0)

原始字面量R“(内容)”

constexpr

常量表达式在编译阶段完成,变量计算在运行阶段。常量表达式可以提高程序运行效率。const有双重语义:变量只读(如函数形参),修饰常量。使用constexpr可以让编译器知道改行是常量表达式。
使用constexpr定义常量和修饰函数,称为 常量表达式函数 ,函数中不能出现非常量表达式之外的语句
若用constexpr修饰构造函数,则初始化必须写在初始化列表中,函数体要为空

auto

auto使用时必须要初始化
auto只在推导const *或 volitale *或const &时保留const和volitale关键字,其他时候会忽略const与volitale,所以要使用const auto xxx来推导定义。

decltype在编译时推导新的变量

decltype(表达式) 

若表达式是一个左值或被()包围,推导出的是表达式类型的引用,且若有const需要加上

返回值类型后置

template< typename T, typename U>
auto add(T t, U u)->decltype(t+u)
{
	return t+u;
}

auto与decltype对比

1. 推导规则不同
  • auto:根据初始化表达式的值类别(value category)推导类型,通常会 忽略引用和 cv 限定符

    int x = 42;
    const int& cref = x;
    auto y = cref;  // y 的类型为 int(忽略引用和 const)
    
    int arr[5];
    auto ptr = arr;  // ptr 的类型为 int*(数组退化为指针)
    
  • decltype:根据表达式的 值类别和类型 精确推导,保留 引用和 cv 限定符

    int x = 42;
    const int& cref = x;
    decltype(cref) z = x;  // z 的类型为 const int&(保留引用和 const)
    
    int arr[5];
    decltype(arr) arr2;  // arr2 的类型为 int[5](不退化)
    
2. 推导时机不同
  • auto:在 编译期 根据初始化表达式推导类型。

  • decltype:在 编译期 根据表达式的静态类型推导,但可用于延迟类型计算(如模板元编程)。

3. 使用场景不同
  • auto:简化类型声明,避免冗长的类型名,适用于迭代器、lambda 等场景。
  • decltype:精确获取表达式类型,用于模板元编程、返回类型推导、保持引用特性等场景。

volatile

1. 禁止编译器优化
若 value 被声明为 volatile,编译器会强制每次都从内存读取:
2. 确保内存可见性
在嵌入式系统或多线程环境中,某些变量可能被硬件或其他线程直接修改,volatile 确保程序每次都能获取最新值:

左值右值和左右值引用

左值(L - value)

  • 定义:左值是可以出现在赋值表达式左边的值,它表示一个占据内存空间、有持久化存储位置的实体。一般来说,左值具有名称,可以通过地址运算符&获取其地址。例如变量就是典型的左值
    右值(R - value)

  • 定义:右值是只能出现在赋值表达式右边的值,它通常是临时的、短暂存在的。右值没有持久化的存储位置,一般不能对其取地址(C++11 引入的std::move等特殊情况除外)。例如字面常量、函数的临时返回值等都是右值:

右值引用通过双&&符号来声明。例如:

int&& rref = 10; // 将一个右值(字面常量10)绑定到右值引用rref

右值引用只能用右值初始化
右值引用的作用
延迟生命周期

  • 移动语义:在传统 C++ 中,当对象传递或赋值时,即使对象是临时的,也会进行深拷贝,这在处理大对象时效率较低。右值引用使移动语义成为可能。移动语义允许我们将临时对象(右值)的资源 “窃取” 过来,而不是进行深拷贝。
    • 完美转发:右值引用在模板元编程中实现完美转发功能。完美转发允许函数模板将其参数原封不动地转发给其他函数,即保持参数的左值或右值属性。例如:
template<typename T>
void forward(T&& arg) {
    otherFunction(std::forward<T>(arg));
}

右值引用只能绑定到右值,即临时对象,比如字面常量、函数的临时返回值等。

final和override

final修饰函数只能修饰虚函数,阻止子类重写父类函数
final修饰类,该类不允许被继承,即无派生类
final写在函数名或类名的后面

override不是必须的,但编译器可以检查函数是为了重写的,而不是相似名字的新函数

委托构造函数

在一个构造函数中调用其他构造函数,减少代码复用。必须在初始化列表调用

Test(int max)
{
	this->m_max=max;
}
Test(int max,int min):Test(max)
{
	//相当于在此处执行了第一个构造函数
	this->m_min=min;
}

继承构造函数

class Base
{
	Base(){
	}
}
class Child:public Base
{
public:
	using Base::Base;
}

initializer_list<>

接收任意多相同类型数据,使用size(),begin(),end()确定范围

范围循环

for(auto &t:容器,数组,初始化列表)
{
	//...
}

set,map的key即使不限定cosnt也是只读的
范围循环只会访问对象一次,确定对象元素的个数

lambda

会被看做仿函数

[capture](params)opt->ret{body;};
捕捉方式 参数列表 选项   返回值类型 函数体

其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。

捕获列表

lambda表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量, 同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
[this] - 捕获当前类中的this指针
让lambda表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项

作者: 苏丙榅
链接: https://subingwen.cn/cpp/lambda/#2-捕获列表
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

仿函数

重载了()运算符的类对象

class Car{
	Car(){};
	void operator () (){
	}
}

using使用

https://subingwen.cn/cpp/using/#2-模板的别名
1.命名空间
2.在子类中调用隐藏的父类同名函数 Father::func()
3,.c++11中,给类型定义别名,和typedef类似

using newtypename = oldtype;
using uint_t = int;

区别:
定义函数指针

//定义函数指针类型,返回Int,参数有int和string
typedef int(*func)(int,string); 
using func1 = int(*)(int,string);//更直观

定义模板别名

template <typename T>
typedef map<int, T> mapType;//错误,语法不允许这样定义

//正确
template <typename T>
struct MyMap
{
	typedef map<int, T> mapType;
}

可调用对象

按函数调用

  • 函数指针
  • 重载operator()的类对象(即仿函数)
  • 可转换为函数指针的类对象
using func_ptr = void(*)(int,string);
Class MyClass{
	static void print(int a,string b)
	{
		cout<<a<<b<<endl;
	}
	//类型转换运算符重载
	//类对象转换为函数指针
	//格式为operator type()
	operator func_ptr()//把对象转换为func_ptr类型,可以使用对象作为参数隐式调用
	{
		return print;
	}
	int my_id;
}
//调用时
Myclass obj;
obj(15,"岁");

using ptr1 = int Test::*;
ptr1 pt = &Test::my_id;
  • 一个类成员函数指针或类成员指针
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Test
{
    void print(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }
    int m_num;
};

int main(void)
{
    // 定义类成员函数指针指向类成员函数
    void (Test::*func_ptr)(int, string) = &Test::print;
    // 类成员指针指向类成员变量
    int Test::*obj_ptr = &Test::m_num;

    Test t;
    // 通过类成员函数指针调用类成员函数
    (t.*func_ptr)(19, "Monkey D. Luffy");
    // 通过类成员指针初始化类成员变量
    t.*obj_ptr = 1;
    cout << "number is: " << t.m_num << endl;

    return 0;
}


作者: 苏丙榅
链接: https://subingwen.cn/cpp/bind/#1-%E5%8F%AF%E8%B0%83%E7%94%A8%E5%AF%B9%E8%B1%A1
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
可调用对象包装器
#include<functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象

1. 包装普通函数
function<void(int, string)> f1 = print;
2. 包装类的静态函数
function<void(int, string)> f2 = MyClass::print;
3. 包装仿函数
4. 包装转化为函数指针的对象

调用
f1(18,"岁");
f2(18,"岁");

例子

class A
{
public:
	//构造函数参数是一个包装器对象
	A(const function<void(int,string)> &f): callback(f)
	{
	}
	void notify(int id, string name)
	{
		callback(id,name);//调用通过构造函数得到的函数指针
	}
private:
	function<void(int,string)> callback;

}

若要让包装对象可读可写,加&
function<int&(void)> = bind(Test::m_number,&t);
绑定器bind

std::bind用来将可调用对象与其参数一起绑定。绑定的结果可以用std::function进行保存,并延迟调用到任何需要的时候。
作用:
1.将可调用对象与参数绑定为一个仿函数 (使用auto f = bind()得到的是仿函数类型,而不是包装器类型)
2.将多参数可调用对象转换为一元或(n-1)元可调用对象,即只绑定部分参数

//绑定非类成员函数与变量
auto f = std::bind(可调用对象地址,绑定的参数/占位符);
//绑定成员函数与变量(变量不用填参数)
auto f = std::bind(类函数/成员地址,类实例对象地址,绑定的参数/占位符)

占位符如placeholders::_1,表示函数调用时被传入的第一个参数替代

注意:绑定时的参数有效时,调用对象传入的参数无效

void output(int x,int y)
{
	cout<<x<<y<<endl;
}
bind(output,1,2)();//最后一个()相当于调用该仿函数,且调用时不用传入参数,因为绑定了1,2
bind(output,placeholders::_1,2)(10);//10作为传入参数,在placeholders位置传入调用
bind(output,2,placeholders::_2)(10,20);//执行时,10被忽略,使用绑定的2,传入20

右值引用

右值包括
纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、lambda表达式等
将亡值:与右值引用相关的表达式,如T&&类型返回值,std::move的返回值

有时产生临时对象后马上析构,造成内存开销,为了优化,c++11引入右值引用·

class Test
{
public:
	//移动构造函数(有右值引用参数的函数),复用另一个对象的(堆内存)
	//实现浅拷贝而不是深拷贝,因为对象a已经消耗资源进行构造,再次构造的花费很多
	Test(Test&& a):m_num(a.m_num)
	{
		a.m_num = nullptr;//防止a析构时销毁a的堆内存,导致m_num失效
		cout<<"move construct"<<endl;
	}

	int *m_num;
}
Test getObj()
{
	Test obj;
	return obj;
}
Test getObj1()
{
	return Test();
}
Test &&getObj2()//返回右值引用
{
	return Test();
}
int main()
{
	Test t = getObj();//编译器会判断,如果右侧产生的是临时对象,则优先使用移动构造函数,而不是拷贝构造函数
	Test&& t1 = getObj();
	//如果没有移动构造函数,使用右值引用初始化要求右侧是一个临时的不能取地址的对象
	Test&& t2= getObj1();
	return 0;
}

move与forward完美转发

右值引用在传递时会被转换为左值引用,通过std::move方法可以把左值转换为右值(将亡值)
把资源的所有权转移

Test&& t2 = getObj1();
Test&& t3 = t2;//错误,右值引用只能用右值初始化
Test&& t3 = move(t2);//正确

例子:

list<string> ls1{"nihao","shijie","hello","world"};
list<string> ls2 = ls1;
list<string> ls3 = move(ls1);//资源转移,不再是用ls1了,通过Move减少开销
完美转发
  1. 概念:完美转发是指函数模板能够将其参数原封不动地转发给其他函数,同时保持参数的左值或右值属性。也就是说,传入函数模板的实参如果是左值,转发后仍然是左值;如果是右值,转发后仍然是右值。这在编写通用库或框架时非常有用,因为它允许库函数在不丢失实参语义的情况下,将实参传递给其他函数。
    右值引用独立于值,右值引用在作为函数参数的形参时,在函数内部转发该参数给内部其他函数时会作为左值,不是原来的类型了。如果需要按照原来的类型转发给其他函数,使用forward(),该函数实现的功能称为完美转发
//函数原型
template<class T> T&& foward( typename remove_reference<T>::type& t) noexcept

//精简
std::forward<T>(t);
  • 当T为左值引用类型时,t被转换为T类型的左值
  • 当T不是左值引用类型时,t被转换为T类型的右值

未定义类型引用

即auto&& 或模板T &&得到的变量
通过右值推导,得到右值引用
非右值(右值引用、左值、左值引用、常量左值右值引用)推导得到左值引用
const T&&一定是右值引用

智能指针

new -delete malloc- free区别:new delete能自动调用构造函数和析构函数
智能指针能保证资源自动释放,不产生野指针和内存溢出。利用栈上对象出作用域时析构自动释放
智能指针头文件<memory>

  • shared_ptr共享智能指针,多个智能指针共享内存引用计数,在计数为0时使用析构释放内存
  • unique_ptr独占智能指针,只有一个指针管理一块内存
  • weak_ptr弱引用智能指针,不共享指针,不能操作资源,用来监视shared_ptr
shared_ptr

1.初始化

shared_ptr<T> 智能指针名字(管理的堆内存)

2.拷贝和移动构造初始化

shared_ptr<int> ptr1(new int(520));
shared_ptr<int> ptr2(ptr1);//ptr1.use_count()会增加
shared_ptr<int> ptr3 =ptr1;
shared_ptr<int> ptr4 = move(ptr1);//use_count不会增加

3.通过make_shared初始化

template<class T, class... Args>
shared_ptr<T> make_shared(Args&&... args);//参数列表即初始化传入的参数
shared_ptr<Test> ptr = make_shared<Test>(参数);
  • T:模板参数数据类型
  • Args&&... args:要初始化的数据,如果通过make_shared创建对象,需要按照构造函数参数列表指定
    4.通过reset方法初始化
ptr.reset();//ptr使用的空间被析构,use_count变为0

//指定新内存
ptr.reset(new int(1));//重置内存

获取原始指针

Test* t = ptr.get();
若直接使用智能指针调用所指对象,就当成指针来用
ptr->function();
指定删除器

引用计数为0时内存会被智能指针析构,也可以在初始化时指定删除动作

shared_ptr<Test> ppp(new Test(100),删除器);
shared_ptr<Test> ppp(new Test(100),[](Test* t){
	//释放内存操作
	delete t;
});

当智能指针管理数组内存时,必须指定删除器

1.自定义删除器
shared_ptr<Test> ppp(new Test[5],[](Test* t){
	//释放内存操作
	delete[] t;
});
2.模板参数中指定数组类型
shared_ptr<Test[]> ppp(new Test[5]);
3.使用c++默认删除器函数
shared_ptr<Test> ppp(new Test[5],default_delete<Test[]>());//<Test[]>是数组,不加[]就不是数组
unique_ptr

1.初始化

1.构造函数
unique_ptr<int> ptr1(new int(2));
2.移动构造函数初始化;
unique_ptr<int> ptr2 = move(ptr1);
3.reset
ptr2.reset();//因为是unique,所以原内存被释放
4.获取原始指针
unique_ptr<Test> ptr3(new Test(1));
Test* pt = ptr3.get();
pt->function();

2.删除器
和shared_ptr的区别是,指定删除器时unique_ptr需要在模板参数里指定删除器类型

using func_ptr = void(*)(int*);
unique_ptr<int,func_ptr> ptr1(new int(10),[](int*p) {delete p});

独占智能指针可以管理数组类型指针,自动释放

unique_ptr<Test[]> ptr(new Test[3]);
weak_ptr

辅助shared_ptr,不管理shared_ptr内部的指针
1.当一个函数返回shared_ptr<Test>(this),当释放该地址时,this对象会被析构两次
2.循环引用时,count无法减为0,造成内存泄漏
初始化

shared_ptr<int> sp;
weak_ptr<int> wp(sp);
weak_ptr<int> wp1 = sp;

weak_ptr可以使用use_count方法
expired()观测资源是否已经释放,返回true(shared_ptr全部释放)或false
lock()得到管理监测的shared_ptr对象,即一块内存,可以给其他指针赋值初始化
reset()清空对象,使其不监测任何资源

注意

shared_ptr
1.不能用一个原始地址(new得到的地址)初始化多个shared_ptr,要用智能指针做第二次赋值
2.不能返回管理了this的共享智能指针对象,否则引用计数不会增加,会重复析构

class Test:public enable_shared_from_this<Test>//必须继承enable...才能调用shared_from_this
{
	shared_ptr<Test> getSharedPtr(){
		return shared_from_this();//返回了一个检测shared_ptr的weak_ptr
	}
}

3.共享智能指针不能循环引用

实现
template<typename T>
classs CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr):mptr(ptr){}
	~CSmartPtr(){ delete mptr;}
	T& operator*() {return *mptr;}
	T* operator->() {return mptr;}
private:
	T *mptr;
}

对资源进行引用计数shared_ptr

//引用计数类
template<typename T>
class ReftCnt
{
public:
	RefCnt(T *ptr=nullptr):mptr(ptr)
	{
		if(mptr!=nullptr)
			mcount = 1;
		else mcount = 0;
	}
	void addRef(){mcount++;}//增加引用计数
	void delRef(){mcount--;}
private:
	T *mptr;
	
	int mcount;
}


template<typename T>
classs CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr):mptr(ptr){
	mpRefCnt = new RefCnt();//生成对象同时生成引用计数对象
	}
	~CSmartPtr(){ //析构时检查引用计数是否为0,再释放
		if(0==mpRefCnt->delRef())
		{
			delete mptr;
			mptr = nullptr;
		}
		
		
	}
	T& operator*() {return *mptr;}
	T* operator->() {return mptr;}

	//拷贝构造
	CSmartPtr(const CSmartPtr<T> &src)
	:mptr(src.mptr),mpRefCnt(src.mpRefCnt)
	{
		if(mptr!=nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T> &src)//等号左边的计数减1,右边的加1
	{
		if(this == &src)
			return *this;
		if(0==mpRefCnt->delRef())//引用计数为0则析构资源
		{
			delete mptr;
		}
		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}
private:
	T *mptr;//指向资源的指针
	RefCnt<T> *mpRefCnt;//指向该资源引用计数对象的指针
}

分区与存储

栈区(Heap):局部变量,函数参数,返回地址等临时数据,编译器自动分配释放
堆区(Stack):动态分配内存,理论上受限于虚拟内存总量
全局区(静态存储区):静态变量、全局变量。程序启动时分配,结束时释放,未初始化的自动初始化为0
常量存储区:存放常量数据和const修饰的全局变量
代码区:存储可执行代码,只读
流式存储:
用结构体把变量包裹起来,可以把分散的变量连续存储

const

1 . const int* 和 int const* 等价(指向常量的指针),如定义

int x=10,y=20;
const int* p=&x;
*p=15; 错误,指向的是常量,不可修改
p = &y; 正确:指针本身可以修改

2 . int* const 是常量指针。
指针 p 的地址固定,但可以通过 p 修改指向的值。

int x = 10;
int* const p = &x;
*p = 15;  正确:值可修改
p = &y;   错误:指针地址不可修改

3 . const 对象:只能调用 const 成员函数。
总结:
const限制了定义时离const之前最近的修饰,如int* const p=&x;最近的星号表示地址,即p地址固定,但指向的值可以变
修饰变量:值不可变
修饰指针:限制指针或指向的数据

const修饰的函数可以重载。const成员函数既不能改变类内的数据成员,也无法调用非const的成员函数;const类对象只能调用const成员函数,非const对象无论是否是const成员函数都能调用,但是如果有重载的非const函数,非const对象会优先调用重载后的非const函数。

static

类中的static方法属于类而不属于某个类对象,可以通过类名调用

static即静态的意思,可以对变量和函数进行修饰。分三种情况:

(1)当用于文件作用域的时候(即在.h/.cpp文件中直接修饰变量和函数),static意味着这些变量和函数只在本文件可见 ,其他文件是看不到也无法使用的,可以避免重定义的问题。

(2)当用于函数作用域时,即作为局部静态变量时,意味着这个变量是全局的,只会进行一次初始化,不会在每次调用时进行重置,保留这个数值,但只在这个函数内可见。

(3)当用于类的声明时,即静态数据成员和静态成员函数,static表示这些数据和函数是所有类对象共享的一种属性,而非每个类对象独有。

explicit

1. 禁止隐式转换,仅允许显式转换

class String { public: explicit String(const char* str); // 使用 explicit 禁止隐式转换 
// ...
}; 
void printString(const String& s) { std::cout << s << std::endl; } int main() { printString("Hello"); // 错误:无法隐式转换 
printString(String("Hello")); // 正确:显式转换 
printString(static_cast<String>("Hello")); // 正确:显式转换 
return 0; }

引用

性质:相当于一个变量的别名,一个int * const ref = &a
必须引用一块合法内存空间(变量),不能是常量,对初始化后的引用不能更改,只能赋值

int& ref=10; 错误,不能引用常量

但加const后,编译器修改代码为int tmp=10;int& ref=tmp;

const int & ref = 10; 正确

引用可做函数返回值
1.不要返回局部变量引用

int &test(){
	int a=10;
	return a;
}
int main()
{
	int &ref= test();
	cout<<ref;//第一次正常输出,因为编译器做了保留
	cout<<ref;//第二次是乱值,a的内存已被释放
}

2.函数调用可作为左值

int &test()
{
	static int a = 10;//静态变量存放在全局区,在程序运行结束后才释放
	return a;
}
int main()
{
	int &ref=test();
	cout<<ref<<endl; //结果为10
	test() = 1000;
	cout<<ref<<endl;//结果为1000
}

常量引用

作用:修饰形参,仿制误操作

struct和class区别:class默认私有,struct默认公有

拷贝构造函数

class Person{
	Person(const Person& p)拷贝构造函数
	{
	}
}

在函数调用形参时是值传递按,会调用拷贝构造
调用时机:

  1. 使用一个已创建完的对象初始化新对象
  2. 值传递方式给形参传值
  3. 以值方式返回局部对象

深拷贝浅拷贝

浅拷贝:编译器默认拷贝函数,若堆区申请空间,会直接复制堆区地址,堆区内存会重复释放
深拷贝:自己实现拷贝构造函数,堆区新申请空间

内联函数与宏

  • 内联函数有什么作用?存不存在什么缺点

(1)作用是使编译器在函数调用点上展开函数,可以避免函数调用的开销;

(2)内联函数的缺点是可能造成代码膨胀,尤其是递归的函数,会造成大量内存开销,exe太大,占用CPU资源。此外,内联函数不方便调试,每次修改会重新编译头文件,增加编译时间。

  • 内联函数和宏有什么区别,有了宏为什么还需要内联函数?

(1)define宏命令是在预处理阶段对命令进行替换,inline是在编译阶段在函数调用点处直接展开函数,节省了函数调用的开销;

(2)define的话是不会对参数的类型进行检查的,因此会出现类型安全的问题,比如定义一个max命令,但是传递的时候可能会传递一个整数和一个字符串,就会出错,但是内联函数在编译阶段会进行类型检查;

(3)使用宏的时候可能要添加很多括号,比较容易出错。

类对象成员

类对象作为类成员,先构造类成员

Class A{

}
Class B{
	A a;
}

友元

全局函数做友元

Class Person{
	friend void test();
	private : int x;
}
void test()
{
	cout<<x;
}

类做友元

Class Person{
	friend void test();
	private : int x;
}
void test()
{
	cout<<x;
}

模板

模板不会编译,在调用时实例化一份函数代码
所以不能在一个文件中定义模板,另一个文件调用文件中的模板

所以模板都是放在头文件中,在源文件中include头文件

具体化模板

template<typename T>
bool compare(T a, T b)//T类型可能是个复杂的类,不能直接比较
{
	if (a>b)
		return true;
	else return false;
}
//利用具体化Person的版本实现
template<> bool compare(Person p1, Person p2)
{
	if(p1.age>p2.age)
		return true;
	else return false;
}

特例化模板
不是编译器提供,而是开发者提供

template<typename T>
bool compare(T a,T b)
{
	return a>b;
}
//告诉编译器进行指定类型的模板实例化
template bool compare<int>(int,int);
//提供const char*类型特例化
template<>
bool compare<const char*>(const char* a,const char* b)
{
	return strcmp(a,b)>0;
}

bool compare(const char* a,const char* b)
{
	return strcmp(a,b)>0;
}
int main()
{
	compare("aaa","bbb");//编译器优先使用普通函数,若没有普通函数,则调用特例化模板

}

非类型模板

模板的非类型参数,必须是整形或地址,必须是常量,只能使用
template<typename T,int SIZE>
void func(T *array)
{
	...
}

类模板与继承
子类继承的父类是一个类模板时,子类声明时要指出父类模板类型。如果子类想灵活,子类也需是类模板

实现vector

空间配置器allocator做四件事:

内存开辟、内存释放、对象构造、对象析构
核心功能:分离了对象的内存开辟和构造,分离了内存的释放和析构

构造析构应该只针对有效元素,而不是一次把开辟空间全部构造或析构。因此不应该使用new和delete对元素管理

两级allocator:
一级:malloc/free
二级:内存池实现

类型转换???

多线程

1.创建启动线程
std::thread定义一个线程对象,传入线程函数和参数,线程自动开启
2.子线程如何结束
子线程函数运行完成,线程结束
3.主线程如何处理子线程
t.join()主线程等待子线程t结束,当前线程继续往下运行
detach()分离线程,主线程结束所以线程就结束

#include<thread>
#include<mutex>
using namespace std;
std::mutex mtx;//全局互斥锁
void threadHandle1()
{
	cout<<"thread1 processing"<<endl;
}
int main()
{
	//创建一个线程对象,传入线程函数,新线程开始运行
	thread t1(threadHandle1);
	//主线程等待子线程结束,主线程继续往下运行。若主线程先结束程序会出问题
	t1.join();
	//把子线程设置为分离线程
	t1.detach();
}

lock_gurad封装mutex锁
lock_guardstd::mutex lock(mtx);//出了作用域析构自动释放锁,但不能拷贝构造和复制重载

unique_lock

CAS

互斥锁比较重,临界区若比较复杂,使用cas保证++,--操作的原子特性。称无锁操作

thread_local

thread_local 是 C++11 引入的一个 存储类型说明符,用于声明一个变量在每个线程中都有自己独立的副本

  • 多个线程访问同一个 thread_local 变量时,每个线程都会看到自己的一份
  • 它就像是为每个线程自动复制了一份变量
  • 线程之间互不干扰,线程退出时会自动销毁

STL

栈stack

栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。

栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。

所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。

从下图中可以看出,栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。

deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。

我们也可以指定vector为栈的底层实现,初始化语句如下:

std::stack<int, std::vector<int> > third;  // 使用vector为底层容器的栈

vecotr

初始化

1.默认初始化
创建空的 vector,后续通过push_back()emplace_back()添加元素。

2.指定大小初始化

// 创建包含4个元素的vector,初始值均为100
std::vector<int> v1(4, 100);  // [100, 100, 100, 100]

3.从其他容器复制

// 1. 从数组复制 
int arr[] = {1, 2, 3, 4, 5}; 
std::vector<int> v1(arr, arr + 5); // [1, 2, 3, 4, 5] 
// 2. 从另一个vector复制 
std::vector<int> v2 = {10, 20, 30}; std::vector<int> v3(v2); // 深拷贝:[10, 20, 30] // 3. 从迭代器范围复制 
std::vector<int> v4(v2.begin(), v2.end()); 
// [10, 20, 30] 
std::vector<int> v5(v2.begin(), v2.begin() + 2); // [10, 20]

4.使用assign方法
动态分配新内容并替换现有内容。

```cpp
std::vector<int> v1;

// 1. 从初始化列表分配
v1.assign({1, 2, 3, 4});  // [1, 2, 3, 4]

// 2. 分配n个相同值
v1.assign(3, 100);  // [100, 100, 100]

// 3. 从迭代器范围分配
std::vector<int> v2 = {5, 6, 7};
v1.assign(v2.begin(), v2.end());  // [5, 6, 7]

vector如何手动释放内存

【参考资料】https://www.cnblogs.com/summerRQ/articles/2407974.html

比如有一个 **vector<int>nums , 比较hack的一种方式是nums = {},这样既可以清空元素还会释放内存(从一个大佬身上学来的);规范的做法是,vector<int>().swap(nums)或者nums.swap(vector<int> ())。

如何拷贝一段数据

(1)C++语法:std::copy(src.begin(), src.end(), dest.begin());

std::copy_n(src.begin(), int n, dest.begin());

(2)C风格语法:memcpy(void* dest, const void* src, size_t num)

posted @ 2025-08-05 11:27  Los1r  阅读(7)  评论(0)    收藏  举报