C++常用汇总
1、封装
public、protected、private访问权限
public | 可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问; |
---|---|
protected | 可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问; |
private | 可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。 |
类中默认包含构造函数、析构函数、赋值运算符重载、拷贝构造函数。这四个函数一经实现,默认不复存在。
对于数据包含在栈上的空间,必须要自实现拷贝构造,达到深拷贝的目的,不然则会造成重析构,double free的现象。(也就是类内有数据为指针类型的情况,要实现深拷贝)
系统提供默认的拷贝构造器,一经定义不再提供。但系统提供的默认拷贝构造器是等位拷贝,也就是通常意义上的浅(shallow)拷贝。如果类中包含的数据元素全部在栈上,浅拷贝也可以满足需求的。但如果堆上的数据,则会发生多次析构行为。所以,对于有堆上的数据,需要自实现深拷贝和深深的赋值

深拷贝、深深的赋值
string(const string& s)// 深拷贝
{
_str = new char[strlen(s._str) + 1];
strcpy_s( _str, strlen(s._str) + 1, s._str );
}
string& operator=(const string& s)// 深深的赋值
{
if (this != &s)
{
delete[] _str;
this->_str = new char[strlen(s._str) + 1];
strcpy_s(this->_str, strlen(s._str) + 1, s._str);
}
return *this;
}
2、继承
继承方式 | 语义 |
---|---|
public | 基类的公有成员和保护成员在派生类中保持原有访问属性, 其私有成员仍为基类的私有成员,子类不可以访问 |
private | 基类的公有成员和保护成员在派生类中成了私有成员, 其私有成员仍为基类的私有成员。 |
protected | 基类的公有成员和保护成员在派生类中成了保护成员, 其私有成员仍为基类的私有成员。 |
2.1 单继承
由于子类中, 包含了两部分内容, 一部分来自, 父类, 一部分来自, 子类。 父类的部分, 要由调用父类的构造器来完成, 子类的部分, 在子类的构造器中来完成始化。 子类中,有内嵌的子对象(内嵌子对象就是类中的数据成员是其他类的一个对象)也需要构造。
子类会调用父类构造器,只有在父类含有标配的情况下才会调用,否则只能在子类中显示的调用。(标配就是不需要提供参数的构造器,也就是无参构造器或参数列表都有默认参数的情况),内嵌子对象也一样。
注意:基类的构造器和内嵌子对象只能放在参数列表中,而且参数列表中的是内嵌子对象,是放的对象,不是类,而且基类只能放在内嵌子对象前面。
#include <iostream>
using namespace std;
class Student
{
public:
Student(string name, int num, char sex)
:_name(name),_num(num),_sex(sex){}
void dump()
{
cout << "name:" << _name<<endl;
cout << "num :" << _num<<endl;
cout << "sex :" << _sex<<endl;
}
private:
string _name;
int _num;
char _sex;
};
class Birthday
{
public:
Birthday(int y, int m, int d)
:_year(y),_month(m),_day(d)
{
}
void disBirthday()
{
cout<<"birth date:"<<_year<<":"<<_month<<":"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
class Graduate:public Student
{
public:
Graduate(string name, int num , char sex, float salary, int y, int m, int d)
:Student(name,num,sex),_salary(salary),birth(y,m,d)
{
_salary = salary;
}
void dis()
{
dump();
cout<<"salary:"<<_salary<<endl;
birth.disBirthday();
}
private:
float _salary;
Birthday birth;
};
int main()
{
Student s("zhao",1001,'s');
s.dump();
Graduate g("sun",2001,'s',10000,1990,9,19);
g.dis();
return 0;
}
2.2多继承
例子:沙发床,继承沙发又继承床
class SofaBed:public Sofa,public Bed
{
};
①三角问题:
多个父类中重名的成员,继承到子类中后,为了避免冲突,携带了各父类的作用域信 息,子类中要访问继承下来的重名成员,则会产生二义性,为了避免冲突,访问时需要提 供父类的作用域信息。
#include <iostream>
using namespace std;
class X
{
public:
X(int d)
:_data(d){}
void setData(int i)
{
_data = i;
}
int _data;
};
class Y
{
public:
Y(int d)
:_data(d){}
int getData()
{
return _data;
}
int _data;
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3)
{}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
②:钻石问题
三角关系中需要解决的问题有两类:
数据冗余问题
访问不方便的问题
解决方案,是三角转四角的问题。具体操作:
提取公共成员构成祖父类,即虚基类
各父类虚继承虚基类。
经提取,存有公共元素的,被虚继承的祖父类,称为虚基类。虚基类,需要设计和抽 象,虚继承,是一种继承的扩展。
#include <iostream>
using namespace std;
class A
{
public:
A(int i)
:_data(i){}
int _data;
};
class X: virtual public A //虚继承
{
public:
X(int d)
:A(d){}
void setData(int i)
{
_data = i;
}
};
class Y:public virtual A
{
public:
Y(int d)
:A(d){}
int getData()
{
return _data;
}
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3),A(100) {}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
3、多态
①函数重载(静多态):函数名可以一样,函数参数个数或者类型不同,如果去掉默认参数(默认参数只能从右向左,指定默认值)和另外的函数一模一样,是不可以的,函数返回值不作为重载区别。
②动多态:
多态的实现条件:
1.父类中有虚函数,即共用接口。
2.子类override(覆写)父类中的虚函数。
3.通过己被子类对象赋值的父类指针,调用共用接口。
虚函数和纯虚函数:
含有纯虚函数的类,称为Abstract Base Class(抽象基类),不可实例化。即不能创建对象,存在的意义就是被继承,提供族类的公共接口,如果一个类中声明了纯虚函数,而在派生类中没有该函数的定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类
含有虚函数的类,析构函数也应该声明为虚函数,称为虚析构函数。在 delete 父类指针,指向的堆上子类对象时,会调用子类的析构函数,实现完整析构。
class 类名
{
virtual func(); // 虚函数
virtual func1() = 0; // 纯虚函数
};
多态例子:
#include <iostream>
using namespace std;
class Shape
{
public:
Shape(int x,int y)
:_x(x),_y(y){}
virtual void draw()
{
cout<<"draw Shape";
cout<<"start ("<<_x<<","<<_y<<") "<<endl;
}
protected:
int _x;
int _y;
};
class Circle:public Shape
{
public:
Circle(int x, int y,int r)
:Shape(x,y),_r(r){}
void draw()
{
cout<<"draw Circle";
cout<<"start("<<_x<<","<<_y<<")";
cout<<"raduis r = "<<_r<<endl;
}
private:
int _r;
};
class Rect:public Shape
{
public:
Rect(int x, int y,int l,int w)
:Shape(x,y),_len(l),_wid(w){}
void draw()
{
cout<<"draw Rect";
cout<<"start("<<_x<<","<<_y<<") ";
cout<<"len = "<<_len<<" wid = "<<_wid<<endl;
}
protected:
int _len;
int _wid;
};
int main()
{
Circle c(1,2,4);
c.draw();
Rect r(2,3,4,5);
r.draw();
Shape *ps;
int choice;
while(1)//真正的实现了动多态,在运行阶段决定。
{
scanf("%d",&choice);
switch(choice)
{
case 1:
ps = &c;
ps->draw();
break;
case 2:
ps = &r;
ps->draw();
break;
}
}
return 0;
}
4、运算符重载
operator进行重载
只能对已有的 C++运算符进行重载。 例如, 有人觉得 BASIC 中用"**"
作为幂运算符很方便, 也想在 C++中将"**"
定义为幂运算符, 用"3**5"
表示 3^5
, 这是不行的。
关系运算符">"和"<"等是双目运算符, 重载后仍为双目运算符, 需要两个参数。 运算符"+", "-", "*", "&"
等既可以作为单目运算符, 也可以作为双目运算符, 可以分别将它们重载为单目运算符或双目运算符。
应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现的功能。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
Complex& operator+=(const Complex &c)
{
this->_x += c._x;
this->_y += c._y;
return * this;
}
private:
float _x;
float _y;
};
不可能通过增加istream或ostream 成员的方式重载>>/<<,此时只能通过在自定义的类中增加友元函数的方式重载>>/<<。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend ostream &operator<<(ostream &os, const Complex &c);
friend istream &operator>>(istream &is, Complex &c);
//成员的话必须保证左值为该类的对象
private:
float _x;
float _y;
};
ostream &operator<<(ostream &os, const Complex &c)
{
os<<"("<<c._x<<","<<c._y<<")";
return os;
}
istream &operator>>(istream &is, Complex &c)
{
is>>c._x>>c._y;
return is;
}
int main()
{
Complex c(2,3);
cout<<c<<endl;
cin>>c;
cout<<c<<endl;
return 0;
}
5、static和const
①static修饰数据成员
共享 | static成员变量实现了同族类对象间信息共享。 |
---|---|
初始化 | static成员使用时必须初始化,且只能类外初始化。声明与实现分离时, 只能初始化在实现部分(cpp部分)。 |
类大小 | static成员类外存储,求类大小,并不包含在内。 |
存储 | static成员是命名空间属于类的全局变量,存储在data区rw段。 |
访问 | 可以通过类名访问(无对象生成时亦可),也可以通过对象访问。 |
②static修饰成员函数
静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。 |
---|
staitc关键字修饰成员函数,仅出现在声明处,不可在定义处。 |
静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有this指针。 |
类的静态成员函数,可以直接通过类名::函数名进行访问
③const修饰函数
承诺在本函数内部不会修改类内的数据成员,为此,也只能调用承诺不会改变成员的其它const成员函数,而不能调用其它非const成员函数。const会构成重载
类体外定义的const成员函数,在定义和声明处都需要const修饰符(有些关键字 是定义型的,有些是声明型的)。
void dis() const
④const修饰数据成员
const修饰数据成员,称为常数据成员,可能被普通成员函数和常成员函数来使用, 不可以更改。必须初始化,可以在类中(不推荐),或初始化参数列表中(这是在类对象生成之前唯一的一次改变const成员的值的机会了)。初始化参数列表,一方面提高了效率,另一方面解决一类因面向对象引入的一些新功 能的特殊用法。
6、类型转换
转换方式 | 使用场景 |
---|---|
static_cast |
在一个方向上可以作隐式转换,在另外一个方向上就可以作静态转换。 双向可隐式的自然不需要强制转换。 |
reinterpret_cast |
"通常为操作数的位模式提供较低层的重新解释"也就是说将数据以二进制存在形式的重新解释,在双方向上都不可以隐式类型转换的,则需要重解释类型转换。 |
const_cast |
用 来 移 除 非 const 对 象 的 引 用 或 指 针 的 常 量 性 (cast away the constness)使用 const_cast 去除 const 对于引用和指针限定,通常是为了函数能够接受参数或返回值。非 const 对象--> const 引用或指针-->脱 const-->修改非 const 对象//目标类类型只能是指针或引用 |
dynamic_cast |
用于多态中的父子类之间的强制转化。 |
dynamic_cast应用于类的指针、类的引用或者void*。 dynamic_cast运算符可以在执行期决定真正的类型。
如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。 如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。 dynamic_cast 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
基类指针下转时,如果发现指向的不是一个发生在多态中的子类对象,则会返回 NULL指针以后判断使用。
#include <iostream>
using namespace std;
class A
{
public:
virtual ~A(){}
};
class B:public A{};
class C:public A{};
class D{};
//downcast no-relation
int main(int argc, char *argv[])
{
// if(typeid(*pa) == typeid(b))
B b;
A *pa = &b;
B* pb = dynamic_cast<B*>(pa);
if(pb != NULL)
cout << "pb != NULL" << endl;
else
cout << "pb == NULL" << endl;
C *pc = dynamic_cast<C*>(pa);
if(pc != NULL)
cout << "pc! = NULL" << endl;
else
cout << "pc == NULL" << endl;
D *pd = dynamic_cast<D*>(pa);
if(pd != NULL)
cout << "pc! = NULL" << endl;
else
cout << "pc == NULL" << endl;
return 0;
}
在什么情况下你应该使用dynamic_case替代虚函数?
如果我们需要在派生类中增加新的成员函数f,但又无法取得基类的源代码,因而无法在基类中增加相应的虚函数,这时,可以在派生类中增加非虚成员函数,但这样一来,就无法用基类指针调用函数f,如果在程序中需要通过基类指针(如使用该继承层次的某个类中所包含的指向基类对象的指针数据成员p)来调用f,则必须使用dynamic_cast将p转换为只想派生类的指针,才能调用f,也就是说,如果无法为基类增加虚函数,就可以使用dynamic_cast代替虚函数.
7、内存管理
new和delete:new 申请空间失败不是返回 NULL,而是抛出异常
int *p = new int(10); // 申请单变量空间
float *p1 = new float [10]{1.2,3.4}; // 申请数组
char **p2 = new char*[5];// 申请指针数组
string *p3 = new string("china");
delete p;
delete []p1;
delete []p2;
delete p3;
8、IO流
C++引入了ostringstream、istringstream、stringstream这三个类,要使用他们创建对象就必须包含sstream.h头文件。
istringstream类用于执行C++风格的串流的输入操作。
ostringstream类用于执行C风格的串流的输出操作。
stringstream类同时可以支持C风格的串流的输入输出操作。
istringstream例子:
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
istringstream istr;
istr.str("1 56.7");
//上述两个过程可以简单写成 istringstream istr("1 56.7");
cout << istr.str() << endl;
int a;
float b;
istr >> a;
cout << a << endl;
istr >> b;
cout << b << endl;
return 0;
}
输出结果:
1 56.7
1
56.7
ostringstream 可以无视类型,都流到ostringstream中
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
int a = 222;
string str = "China";
ostringstream ostr;
ostr << "111" << a << str;
cout << ostr.str() << endl;
}
输出:111222China
stringstream例子:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
stringstream ostr("ccc");
ostr.put('d');
ostr.put('e');
ostr<<"fg";
string gstr = ostr.str();
cout<<gstr<<endl;
char a;
ostr>>a; //取出来第一个字符 d
cout<<a;
system("pause");
return 0;
}
输出结果:
defg
d请按任意键继续. . .
9、RTTI
通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型。c++通过下面两个操作符提供RTTI。
(1)typeid:返回指针或引用所指对象的实际类型。
(2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用。
对于带虚函数的类,在运行时执行RTTI操作符,返回动态类型信息;对于其他类型,在编译时执行RTTI,返回静态类型信息。
typeid用在普通类型:
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
short s1 = 5;
unsigned ui1 = 20;
int i1 = 50, i2 = 100;
char ch1 = 'a';
wchar_t wch1 = L'b';
float f1 = 3.14159265f;
cout<<"typeid name:"<<endl;
cout<<typeid(s1).name()<<endl; // s
cout<<typeid(ui1).name()<<endl; // j
cout<<typeid(i1).name()<<endl; // i
cout<<typeid(ch1).name()<<endl; // c
cout<<typeid(wch1).name()<<endl; // w
cout<<typeid(f1).name()<<endl; // f
cout<<"typeid compare result:"<<endl;
if (typeid(i1) == typeid(i1)) {
cout<<"typeid(i1) == typeid(i2)"<<endl; //执行到这个语句(类型相等)
}else {
cout<<"typeid(i1) != typeid(i1)"<<endl;
}
if (typeid(s1) == typeid(i1)) {
cout<<"typeid(s1) == typeid(i1)"<<endl;
}else {
cout<<"typeid(s1) != typeid(i1)"<<endl; //执行到这个语句(类型不相等)
}
return 0;
}
// 输出信息:
typeid name:
s
j
i
c
w
f
typeid compare result:
typeid(i1) == typeid(i2)
typeid(s1) != typeid(i1)
typeid用在类类型
#include <iostream>
#include <typeinfo>
using namespace std;
class Base {
public:
int a;
virtual void fun1() {cout<<"Base::fun1"<<endl;}
virtual void fun2() {cout<<"Base::fun2"<<endl;}
virtual void fun3() {cout<<"Base::fun3"<<endl;}
};
class A {
public:
int a;
};
class Derive : public Base{
public:
int b;
void fun2() {cout<<"Derive::fun2"<<endl;}
};
int main()
{
Base b;
Derive d;
A a;
cout<<typeid(b).name()<<endl; // 4Base
cout<<typeid(d).name()<<endl; // 6Derive
cout<<typeid(a).name()<<endl; // 1A
cout<<"compare result:"<<endl;
if (typeid(b) == typeid(d)) {
cout<<"typeid(b) == typeid(d)"<<endl;
}else {
cout<<"typeid(b) != typeid(d)"<<endl; //执行到这个语句
}
return 0;
}
// 输出信息:
4Base
6Derive
1A
compare result:
typeid(b) != typeid(d)
10、异常
异常是面向对象语言处理错误的一种方式。当一个函数出现自己无法处理的错误时,可以抛出异常,然后输的直接或者间接调用者处理这个错误。
有三个关键字:
throw:当问题出现,程序抛出一个异常。抛异常使用throw关键字完成。
catch:用于捕捉异常。catch(...)可以捕获任意类型的异常,主要时用来捕获没有显示捕获类型的异常。相当于条件判断中的else。
try:try中包含会出现异常的代码或者函数。后面通常会跟一个或者多个catch块。
int test(){
int a = 0;
int b = 0;
cin >> a >> b;
if (b == 0){
throw "除0错误";//抛出异常,异常的描述
}
return a / b;
}
int main(){
try{
cout << test() << endl;//会出现异常的代码
}
//捕获异常
catch (const char* a){
cout << a << endl;
}
catch (...){
cout << "unknow exception" << endl;
}
system("pause");
return 0;
}
C++ 标准的异常
C++ 提供了一系列标准的异常,定义在
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
11、模板
函数模板
// 函数模板默认参数为int
template<typename T = int> void myswap(T & a, T &b){
T t = a;
a = b;
b = t;
}
类模板
template<typename T> class ClassName
{
void func(T );
};
ClassName<int> cn; // 类模板->模板类->类对象
12、C++11智能指针
C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。
1、C++11智能指针介绍
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
2、为什么要使用智能指针?
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
3、auto_ptr
(C++98的方案,C++11已经抛弃)采用所有权模式。
auto_ptr<string> p1 (new string ("I reigned lonely as a cloud."));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
unique_ptr
(替换auto_ptr)unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。尝试复制p3时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr比auto_ptr更安全。
另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 不允许
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 允许
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。
(额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。)
例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
4、shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr<int> sp(new int(1));
sp 与 sp.get()是等价的。
share_ptr的简单例子:
int main()
{
string *s1 = new string("s1");
shared_ptr<string> ps1(s1);
shared_ptr<string> ps2;
ps2 = ps1;
cout << ps1.use_count()<<endl; //2
cout<<ps2.use_count()<<endl; //2
cout << ps1.unique()<<endl; //0
string *s3 = new string("s3");
shared_ptr<string> ps3(s3);
cout << (ps1.get()) << endl; //033AEB48
cout << ps3.get() << endl; //033B2C50
swap(ps1, ps3); //交换所拥有的对象
cout << (ps1.get())<<endl; //033B2C50
cout << ps3.get() << endl; //033AEB48
cout << ps1.use_count()<<endl; //1
cout << ps2.use_count() << endl; //2
ps2 = ps1;
cout << ps1.use_count()<<endl; //2
cout << ps2.use_count() << endl; //2
ps1.reset(); //放弃ps1的拥有权,引用计数的减少
cout << ps1.use_count()<<endl; //0
cout << ps2.use_count()<<endl; //1
}
5、weak_ptr
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
class B; //声明
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout << "A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout << "B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
cout << pb.use_count() << endl; //1
cout << pa.use_count() << endl; //1
pb->pa_ = pa;
pa->pb_ = pb;
cout << pb.use_count() << endl; //2
cout << pa.use_count() << endl; //2
}
int main()
{
fun();
return 0;
}
可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_ ,运行结果如下:
1
1
1
2
B delete
A delete
这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。
注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:
shared_ptr<B> p = pa->pb_.lock();
p->print();
weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.
expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
use_count 返回与 shared_ptr 共享的对象的引用计数.
reset 将 weak_ptr 置空.
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.
share_ptr和weak_ptr的核心实现 weakptr的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值,构造,所以先把counter和share_ptr简单实现
Counter简单实现
class Counter
{
public:
Counter() : s(0), w(0){};
int s; //share_ptr的引用计数
int w; //weak_ptr的引用计数
};
counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。
share_ptr的简单实现
template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
template <class T>
class SharePtr
{
public:
SharePtr(T *p = 0) : _ptr(p)
{
cnt = new Counter();
if (p)
cnt->s = 1;
cout << "in construct " << cnt->s << endl;
}
~SharePtr()
{
release();
}
SharePtr(SharePtr<T> const &s)
{
cout << "in copy con" << endl;
_ptr = s._ptr;
(s.cnt)->s++;
cout << "copy construct" << (s.cnt)->s << endl;
cnt = s.cnt;
}
SharePtr(WeakPtr<T> const &w) //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
{
cout << "in w copy con " << endl;
_ptr = w._ptr;
(w.cnt)->s++;
cout << "copy w construct" << (w.cnt)->s << endl;
cnt = w.cnt;
}
SharePtr<T> &operator=(SharePtr<T> &s)
{
if (this != &s)
{
release();
(s.cnt)->s++;
cout << "assign construct " << (s.cnt)->s << endl;
cnt = s.cnt;
_ptr = s._ptr;
}
return *this;
}
T &operator*()
{
return *_ptr;
}
T *operator->()
{
return _ptr;
}
friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
protected:
void release()
{
cnt->s--;
cout << "release " << cnt->s << endl;
if (cnt->s < 1)
{
delete _ptr;
if (cnt->w < 1)
{
delete cnt;
cnt = NULL;
}
}
}
private:
T *_ptr;
Counter *cnt;
};
share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存。
weak_ptr简单实现
template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造
WeakPtr()
{
_ptr = 0;
cnt = 0;
}
WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt)
{
cout << "w con s" << endl;
cnt->w++;
}
WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt)
{
cnt->w++;
}
~WeakPtr()
{
release();
}
WeakPtr<T> &operator=(WeakPtr<T> &w)
{
if (this != &w)
{
release();
cnt = w.cnt;
cnt->w++;
_ptr = w._ptr;
}
return *this;
}
WeakPtr<T> &operator=(SharePtr<T> &s)
{
cout << "w = s" << endl;
release();
cnt = s.cnt;
cnt->w++;
_ptr = s._ptr;
return *this;
}
SharePtr<T> lock()
{
return SharePtr<T>(*this);
}
bool expired()
{
if (cnt)
{
if (cnt->s > 0)
{
cout << "empty" << cnt->s << endl;
return false;
}
}
return true;
}
friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
protected:
void release()
{
if (cnt)
{
cnt->w--;
cout << "weakptr release" << cnt->w << endl;
if (cnt->w < 1 && cnt->s < 1)
{
//delete cnt;
cnt = NULL;
}
}
}
private:
T *_ptr;
Counter *cnt;
};
weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。
13、thread
1、线程对象与线程
C++线程的启动,只需要#include <thread>
即可。线程对象的创建,意味着线程的开始。
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void func()
{
cout << "thread id:" << this_thread::get_id() << endl;
cout << "do some work" << endl;
// sleep(10);
this_thread::sleep_for(chrono::seconds(10));
}
int main()
{
cout << "main thread id:" << this_thread::get_id() << endl;
thread t(func); // 此处已开始运行
t.join(); // join
cout << "main thread is waiting thread" << endl;
return 0;
}
2、join与detach
join 是阻塞的。
join 和 t.detach 标志着,线程对象和线程的关系。join 表示,线程与线程对象的同 步关系。而detach表示,线程与线程对象的异步关系。
detach后的线程,不能再join,是否可以join可以通过joinable()来判断。
3、传参方式
线程,有自己独立的栈。可以共享全局的变量。在线程启动的时候可以传入启动参数。
#include <iostream>
#include <thread>
using namespace std;
void func(int n, string s)
{
for (int i = 0; i < n; i++)
{
cout << s << endl;
}
}
int main()
{
thread t(func, 5, "china");
t.join();
return 0;
}
除了传入参数,共享全局以外,还可以传入本地变量的引用。传递引用的方式, 编译通不过。要用std::ref
#include <iostream>
#include <thread>
using namespace std;
void func(int &n, string &s)
{
for (int i = 0; i < n; i++)
{
cout << s << endl;
}
n = 10;
s = "Japan";
}
int main()
{
int n = 5;
string str = "china";
thread t(func, std::ref(n), std::ref(str));
t.join();
cout << "n = " << n << " str = " << str << endl;
return 0;
}
14、mutex
1、mutex
std::mutex 是 C++中最基本的互斥量,std::mutex 对象提供了独占所有权的特性--即 不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
成员函数:
①lock() 调用线程将锁住该互斥量,线程调用该函数会发生以下 3 种情况:
a)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock 之前,该线程一直拥有该锁。
(b)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
(c)如果当前互斥量被当前调用线程锁住,则会产生死锁
②unlock() 解锁,释放对互斥量的所有权。
③try_lock() 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被 阻塞
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
volatile int counter(0); // non-atomic counter
mutex mtx; // locks access to counter
void increase10Ktime()
{
for (int i = 0; i < 10000; i++)
{
mtx.lock();
counter++;
mtx.unlock();
}
}
int main()
{
thread ths[10];
for (int i = 0; i < 10; i++)
{
ths[i] = thread(increase10Ktime);
}
for (auto &th : ths)
th.join();
cout << "after successful increase :" << counter << endl;
return 0;
}
2、lock_guard
在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当 前线程锁住。在 lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于 不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最 简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以 正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。
例如下面这里的代码,可以改进为后面的:
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
mutex mtx;
void printEven(int i)
{
if (i % 2 == 0)
cout << i << " is even" << endl;
else
throw logic_error("not even");
}
void printThreadId(int id)
{
try
{
mtx.lock();
printEven(id);
mtx.unlock();
}
catch (logic_error &)
{
}
}
int main()
{
thread ths[10]; // spawn 10 threads
for (int i = 0; i < 10; i++)
{
ths[i] = thread(printThreadId, i + 1);
}
for (auto &th : ths)
th.join();
return 0;
}
改进版本
void printThreadId(int id)
{
try
{
lock_guard<mutex> lck(mtx); //栈自旋 抛出异常时栈对象自我析构。
printEven(id);
}
catch (logic_error &)
{
cout << "exception caught" << endl;
}
}
15、右值引用
通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法。
可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。
有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
int num = 10;
//int && a = num; //右值引用不能初始化为左值
int && a = 10; //右值引用必须初始化
移动构造
所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。
#include <iostream>
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//添加移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
demo get_demo(){
return demo();
}
int main(){
demo a = get_demo();
return 0;
}
move本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
#include <iostream>
using namespace std;
class movedemo{
public:
movedemo():num(new int(0)){
cout<<"construct!"<<endl;
}
//拷贝构造函数
movedemo(const movedemo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//移动构造函数
movedemo(movedemo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
public: //这里应该是 private,使用 public 是为了更方便说明问题
int *num;
};
int main(){
movedemo demo;
cout << "demo2:\n";
movedemo demo2 = demo;
//cout << *demo2.num << endl; //可以执行
cout << "demo3:\n";
movedemo demo3 = std::move(demo);
//此时 demo.num = NULL,因此下面代码会报运行时错误
//cout << *demo.num << endl;
return 0;
}
16、STL
STL链表删除元素
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> li;
li.push_back(1);li.push_back(2);li.push_back(3);
for (auto itr = li.begin(); itr != li.end();)
{
if (*itr == 2)
{
li.erase(itr++);
}
else
{
itr++;
}
}
for (auto itr = li.begin(); itr != li.end(); itr++)
{
cout << *itr << endl;
}
}
17、正则表达式
符号 | 含义 |
---|---|
? | 字符需要出现0次或1次(ab?可匹配a、ab); |
* | 字符可出现0次以上(ab*c可匹配ac、abc、abbc、abbbc......) |
+ | 字符至少出现1次以上(ab+c可匹配abc、abbc、abbbc......不会匹配ac); |
{} | 可以限定匹配的数量(a{6}b匹配aaaaaab;a{1,3}b可匹配ab、aab、aaab,a的数量在1-3之间;a{2,}b可匹配ab、aab、aaab.....,a的数量大于2) |
[] | 表示里面的内容只能取自于它们([a-z]表示所有小写的英文字母,[abc]能匹配到abc,aabbcc,abcc,[a-zA-Z]表示所有的英文字母,[0-9]表示所有数字,如果在[]前加上^,表示[]之外的字符) |
\d | 匹配数字,等同[0-9] |
\w | 匹配字母、数字、下划线。等价于 [A-Za-z0-9_] |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v] |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\W | 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]' |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
^ | 匹配输入字符串的开始位置 (^a匹配abv,accc) |
$ | 匹配输入字符串的结束位置 |
? | 字符需要出现0次或1次(ab?可匹配a、ab); |
C++正则组件库
regex | 表示有一个正则表达式的类 |
---|---|
regex_match | 将一个字符序列与一个正则表达式匹配 |
regex_search | 寻找第一个与正则表达式匹配的子序列 |
regex_replace | 使用给定格式替换一个正则表达式 |
sregex_iterator | 迭代器适配器,调用regex_search来遍历一个string中所有匹配的子串 |
smatch | 容器类,保存在string中搜索的结果 |
ssub_match | string中匹配的子表达式的结果 |
#include <regex>
void test()
{
//查找不是在字符c之后的ei组合存在的单词
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);
smatch results;
string test_str("receipt freind theif receive");
if (regex_search(test_str, results, r)) // 寻找第一个与正则表达式匹配的子序列
cout << results.str() << endl;//freind
}
18、自定义sort排序
// 示例
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> test{ 2,0,1,5,3,-35,32,-36,67,-15 };
for (const auto& val : test) {
cout << val << " ";
}
cout << endl;
sort(test.begin(), test.end(), [](const auto& a, const auto& b)->bool { return a > b; });
for (const auto& val : test) {
cout << val << " ";
}
cout << endl;
return 0;
}
按照map中的value进行排序
#include <iostream>
#include <string>
#include <map>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<string, int> PAIR;
bool cmp_val(const PAIR &left,const PAIR &right)
{
return left.second < right.second;
}
int main()
{
map<string, int> ma;
ma["Alice"] = 86;
ma["Bob"] = 78;
ma["Zip"] = 92;
ma["Stdevn"] = 88;
vector<PAIR> vec(ma.begin(),ma.end());
sort(vec.begin(),vec.end(),cmp_val);
for (vector<PAIR>::iterator ite = vec.begin(); ite != vec.end(); ++ite)
{
cout << ite->first << " " << ite->second << endl;
}
getchar();
}
作者: mengchao
出处:https://www.cnblogs.com/meng-chao/p/16290859.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!