类、对象、继承
重载
重载的前提是各个重载函数之间的参数(类型或个数)不同,与返回值类型无关 重载定义的形式是同一个函数名定义多个函数 析构函数是不支持重载的 成员函数和构造函数支持函数重载
面向对象
类
类是对同一类对象的抽象总结,是一个概念 对象是按照类的规定创建的实体 类主要包括: 属性
行为
class Name:private FatherName{
private:
int num;
public:
Name(){
}
~Name(){
}
int age;
};
实例化对象
栈内存对象:
在函数生命周期结束后自动被销毁
栈内存使用.调用成员
Name num;
num.age=2;
堆内存对象
需要使用new关键字创建,使用delete关键字销毁,不销毁会在内存中持续存在,容易导致内存泄露问题 堆内存对象通常使用指针来保存堆内存内存对象的地址
堆内存对象使用->调用成员
Name *num=new Name;
num->age=22;
delete num;
封装
将类的一些属性和细节进行隐藏,并重新公开给外部的接口 封装写法并不唯一
通常先对属性进行私有化操作,再根据需求编写getter和setter方法 封装有利于提升程序的安全性
构造函数
构造函数是类内部的一种特殊的函数,用来创建一个对象
如果一个类中程序员不编写构造函数,编译器会自动添加一个无参构造函数,且构造函数的函数体为空,如果程序员写了任意一个构造函数,编译器不会自动添加构造函数了
构造函数要求函数名和类名必须完全一致,并且无需返回值
可以给构造函数增加参数,使用参数给属性赋予初始值
构造函数也可以重载 后遭初始化列表,一般推荐常量使用构造初始化列表,变量使用正常方式进行初始化
拷贝构造函数
拷贝构造函数和普通构造函数相同如果程序员不手动编写拷贝构造函数,编译器也会自动添加一个拷贝构造函数,拷贝构造函数和普通构造函数也是重载关系,但是,鉴于参数的原因拷贝构造函数不能再进行重载操作了 拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中
深拷贝与浅拷贝
当类中成员变量出现指针,默认为浅拷贝
析构函数
构造函数 | 析构函数 |
---|---|
手动调用 | 在对象销毁时被调用 |
通常用于在对象创建时初始化 | 通常用于在对象销毁时回收资源 |
可以重载 | 不可以重载,因为没有参数 |
函数名是类名 | 函数名是~类名 |
作用域限定符::
名字空间
namespace myspace{
int a=3;
int b=4;
}
//使用方式一
using namespace myspace;
//使用方式二
myspace::a=20;
类内声明类外定义
与static关键字使用
explicit关键字
赋值时,刚好赋值运算符右边的数值是左边类的构造函数可以接收的类型,编译器会自动调用这个构造函数并把赋值运算符右边的数值传入构造函数中,相当于隐式调用构造函数,为了避免编译器隐式调用构造函数,可以使用explicit关键字
this关键字
this指针是一个特殊的指针,保存的是当前类的对象地址
适用场景:
区分重名的成员变量与局部变量
链式调用
多态传参
static关键字
注: 静态变量只会初始化一次 不同对象会调用同一局部静态变量 静态变量需要类内声明类外初始化 静态局部变量 静态成员变量 此类的所有对象共用此变量 非const静态成员变量通常需要类内声明,类外初始化 静态成员变量可以直接使用类名::来调用,更推荐使用这种方式 静态成员变量在程序运行时创建,程序结束时销毁 静态成员函数 静态成员函数不能访问此类中非静态成员,因为没有this指针 静态成员函数只能调用本类中静态的成员 非静态成员函数可以调用静态成员 除了可以使用当前类对象调用静态成员函数外,也可以直接使用类名::调用,推荐后者 如果静态成员函数声明与定义分离,只需要在声明处使用static修饰
const关键字
c++的const并不是常量,因为const只能在程序运行期间只读,即在编译期可以改变其数值 常成员函数 可以调用本类中非const的成员变量,但是不能修改其数值 不能调用非const的成员函数 建议成员函数只要不修改成员变量值就写为常成员函数 常量对象 常量对象的任何属性值不能被修改 常量对象不能调用任何非const成员函数 const修饰对象时,const关键字可以写在类名前面,也可以写在类名后面 常成员变量 程序运行时,常成员变量的值不可变 不能在函数体中赋值,只能通过直接赋值或构造初始化赋值
友元
友元是一种定义在类外部的普通函数,但是他需要在类内进行说明,在说明时前面加friend关键字 友元主要运用在运算符重载上,其他情况下一般不使用友元,因为会破坏面向对象特性 友元函数
因为友元函数不是类内的函数,因此没有this指针,因此需要给友元函数增加一个传递对象的参数为,用此来调用类中的成员 友元函数不属于类,因此不受类中权限修饰符的映像,即友元函数的说明可以放在类中的任意位置
一个友元函数可以访问多个类的成员,只需要在多个类中分别说明
//友元类
class A{
friend class B;
};
class B{
public:
void func(A&a){
}
};
//友元成员函数
class A;
class B{
public func(A& a);
};
class A{
friend void B::func(A&a);
};
void B::func(A&a){
cout<<endl;
};
类示例
class Name{
//封装
private:
int age;//属性
string name;
const string gender;
char *unknow;
public:
//构造函数
Name(){}//空
Name(int age,string name,string gender,char *unknow):gender(gender){
this.age=age
this.name=name;
this.unknow=new char[20];
strcpy(this.unknow,unknow);//深拷贝
//这里会产生一个问题:处于堆区的内存不能够被正常释放,会产生内存泄露问题
}
//拷贝构造函数
Name(const Name&nm){
age=nm.age;
name=nm.name;
gender=nm.gender;
unknow=new char[20];
strcpy(unknow,nm.unknow);
}
//析构函数
~Name(){
delete this.unknow;
this.unknow=Null;
}
void setName(string name){//成员函数
this.name=name;
}
string getName() const{
return this.name;
}
int getage();//类内声明,类外定义
Value& add(int age){//链式调用
this.age=age;
return *this;
}
//友元函数
friend void testFriend(Name&t1);
};
void testFriend(Name&t1){
t1.age=22;
cout<<t1.age<<endl;
}
int Name::getage(){
return this.age;
}
主函数示例
int main(int argc,const char *args[]){
Name nm1(12,"zhangsan","Men","onlyone");//普通构造函数
Name nm2(nm1);//拷贝构造函数
nm1.add(22).add(33);
}
运算符重载
友元函数运算符重载 可以使用友元函数进行运算符重载
friend Integer operator +(const Integer&i1,const Integer& i2);
friend Integer operator ++(const Integer &i);//自增运算符 先
friend Integer operator ++(const Integer &i ,int);//自增运算符 后
成员函数运算符重载
Integer operator +(const Integer &i);
Integer operator ++();
Integer operator ++(int);
Teacher& operator =(const Teacher&t){
return *this;
}
优势: 通常无需手动编写赋值运算符 当前类的成员变量出现指针 屏蔽赋值运算符的使用 强制类型转换
operator string(){
return this.obj;
}
注意事项: 运算符重载限制在C++已有运算符范围内,不允许创建新的运算符 运算符重载也是函数重载,运算符也是函数 重载之后的运算符不能改变优先级和结核性 重载之后的运算符不能改变操作数和语法结构 运算符重载不能改变该运算符用于基本数据类型的含义,但是可以把基本书籍类型与自定义类型一起运算,或者都是自定义类型 重载之后的运算符功能要与原有功能类似 通常建议单目运算符使用成员函数运算符重载,双目运算符使用友元函数运算符重载
字符串类
面向对象核心
继承
继承就是在一个已经存在的类的基础上建立一个新的类,并拥有其特性,体现了代码复用的思想 已经存在的类被称为基类或者父类 新建立的类被称为派生类或者子类 派生类往往比基类更具体,基类往往比派生类更抽象
构造函数
类的构造函数和析构函数不能被继承 派生类的任意一个构造函数都必须直接或者简介调用基类的任意一个的构造函数 派生类调用构造函数的写法: 透传构造 透传构造指的是在派生类构造函数中直接调用基类的构造函数
Father(string name,int age):name(name),age(age){}
Son(string name,int age):Father(name,age){}
委托构造 委托构造指的是,某个类的构造函数可以调用这个类的另一个重载的构造函数
//Father构造函数同上
//Son构造函数同上
Son(string name):Son(name,10){}//委托构造函数
继承构造(只是叫这个名字,并没有继承构造函数)
using Father::Father;//继承构造
对象的创建与销毁
多重继承
多重继承容易出现二义性问题,需要使用作用域运算符的形式进行区分两个基类的同名成员 在菱形继承时也会出现上述的二义性问题,可以使用作用域运算符解决同类问题 当然也可以使用虚继承的方式解决这类问题 虚继承完美解决了菱形继承的问题,但是虚继承的开销要高于菱形继承
class A{};
class B{};
class C:public A,public B{};//多重继承
//解决二义性问题方法一
class C:public A,public B{
A::func();
};
//姐居然二义性问题方法二
class C:virtual public A{};
class D:virtual public B{};
class E:public C,public D{};
权限
权限修饰符
本类中 | 派生类中 | 全局 | |
---|---|---|---|
private | T | F | F |
protected | T | T | F |
public | T | T | T |
继承权限
公有继承
基于private的成员 | 无法在派生类中直接访问 |
基于protected的成员 | 会继续称为派生类的protected成员 |
基于public的成员 | 会继续称为派生类的public成员 |
保护继承
基于private的成员 | 无法在派生类中直接访问 |
基于protected的成员 | 会继续称为派生类的protected成员 |
基于public的成员 | 会继续称为派生类的protected成员 |
私有继承
基于private的成员 | 无法在派生类中直接访问 |
基于protected的成员 | 会继续称为派生类的private成员 |
基于public的成员 | 会继续称为派生类的private成员 |
多态
实现多态的条件
实现多态需要有公有继承 实现多态需要有函数覆盖,派生类中覆盖基类的成员函数 基类引用/指针指向派生类对象
函数覆盖
当函数覆盖成功时虚函数具有传递性
C++11中可以在派生类新覆盖的函数后增加override关键字进行覆盖是否成功的验证 成员函数与析构函数可以定义为虚函数,静态成员函数和构造函数不能定义为虚函数
如果成员函数的声明与定义分离,virtual关键字只需要在声明时使用
class Animal{
public:
vritual void eat(){
cout<<endl;
}
};
class Dog:public Animal{
public:
void eat()override
{
cout<<"bajibaji"<<endl;
}
};
class Cat:public Animal{
public:
void eat()override
{
cout<<"mimi"<<endl;
}
}
//基于引用的多态参数传递
void test_eat(Animal& a){
a.eat();
}
//基于指针的多态参数传递
void test_eats(Animal *a){
a->eat();
}
int main(){
Dog d;
Cat c;
Animal &a1=d;
Animal &a2=c;
a1.eat;
a2.eat;
Dog *dd=new Dog;
Cat *cc=new Cat;
Animal *a11=dd;
Animal *a22=cc;
a11->eat();
a22->eat();
//此处暂时不考虑delete的问题
}
多态传参的原理
当一个类中有虚函数时,编译器会为这个类创建一个虚函数表,这个类的对象拥有隐藏的成员变量虚函数表指针指向虚函数表,此类被继承时,虚函数表也会被继承,但是如果当前继承的派生类中出现函数覆盖,则会在派生类中更新这张表
子类的虚函数表构建过程 直接父类的虚函数表 如果子类重写了父类中的某个虚函数,那么就在这个虚函数表中进行相应的替换
在实际的多要运行中是一个动态类型绑定的过程,当使用基类引用或指针指向派生类对象时,编译器会产生一端代码,用来检查当前内存中的对象的真正类型,在运行时通过对象的虚函数表指针找到真正的调用函数,因此多态也是一个查表的过程
注意:
使用多态会产生一些额外的代码调用,注意不要滥用多态
在使用多态时也有可能会产生内存泄露的问题,因此为了避免这类问题的产生,需要手写虚构函数来避免内存泄露的问题
class Animal{
public:
virtual void eat(){
}
virtual ~Animal(){
}
};
class Dog:public Animal{
void eat()override
{
}
~Dog(){
}
}
抽象类
抽象类值表达抽象的概念,并不与具体的对象相关联,因此无法创建对象 抽象类只会为其派生类提供一个算法框架,抽象类不光无法创建实例对象,也不能作为声明类型,因此不能作为参数类型,返回值类型 如果一个类有纯虚函数,那么这个类就是抽象类 如果一个类是抽象类,那么这个类一定包含纯虚函数 纯虚函数是一种虚函数,这种函数只有声明,没有定义 注意: 抽象类的派生类没有实现抽象基类的所有纯虚函数,此时派生类也会编程抽象类,需要继续继承直到所有的纯虚函数都被实现 抽象类也支持多态,因此抽象类的虚构函数需要写为虚析构函数
class Shape{
public:
virtual void area()=0;
virtual void perimeter()=0;
};
class Circle:public Shape{
public:
void area(){
cout<<endl;
}
void perimeter(){
cout<<endl;
}
};
STL编程、智能指针
STL编程
C++中使用模板可以实现“参数化多态”可以函数声明一种通用数据类型,使类中或者函数中编写与类型无关的代码
通常模板有两种实现形式:函数模板,类模板
函数模板
函数模板可以使函数的参数或返回值支持任意数据类型,需要注意的是函数中对任意数据类型的处理都要考虑到算法的通用性问题
class Dog{
public:
Dog(int){}
int operator )(Dog&){
return 666;
}
int get_msg(){
return 666;
}
};
template <class T>
T add(T a,T b){
return a+b;
}
类模板
类模板可以使一个类支持通用数据类型
template <typename T>
class Demo{
public:
Demo(T value):value(value){}
void set_value(T value){
this->value=value;
}
T get_value(){
return value;
}
private:
T value;
};
int main(){
Demo<int>d1(10);
d1.set_value(11);
Demo<bool>d2(flase);
}
关于声明和定义分离的模板类
template <typename T>
class Demo{
public:
Demo(T value);
void set_value(T value);
T get_value()const;
private:
T value;
};
template <typename T>
Demo<T>::Demo(T value):value(value){}
template <typename T>
void Demo<T>::set_value(T value){
this->value=value;
}
template <typename T>
T Demo<T>::get_value()const{
return this->value;
}
容器
泛型编程与STL
泛型编程提出的目的是:发明一种语言机制,能够实现一个标准的容器库(标准模板库STL),标准容器库可以做到编写一般化的算法,来支持不同的数据类型
标准模板库(STL)主要包括:算法,容器,迭代器
在STL中,几乎所有的代码都采用了模板来实现,相比于传统的库,具有更好的适用性和重用性
容器类型
C++中的STL容器类都是std名字空间中的类型,因此后面不再赘述命名空间
container
顺序容器:string字符串 array数组 vector向量 list列表 deque队列
相关容器:map键值对映射 multimap多重键值对映射
注意:所有容器类的使用都要引入头文件,string的头文件包含在iostream中,可以不写,容器类型的对象统一使用栈内存
顺序容器
顺序容器是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集,每个元素都有固定的位置,通过下标可以访问,除非使用插入或删除等操作修改元素的位置
String字符串
array数组
array是C++11中新增的容器类型,相比传统数组更加安全和易于使用
//创建一个长度为5的int数组
array<int,5>arr1;
array<int,5>arr2={1,2,3};
//取出元素值
cout<<arr1.at(1)<<endl;
cout<<arr1[1]<<endl;
//修改
arr2[2]=999;
//迭代器遍历
for(int i:arr2){
cout<<i<<" ";
}
vector向量
vector内部使用数组实现,能高效的随机进行存取,但是插入和删除效率较低
#include <vector>
//初始化
vector<int> vec(5);//长度为5
vector<int> vec2;//长度为0
//取出元素
cout<<vec[0];
cout<<vec.at(0);
//判断是否为空
cout<<vec2.empty()<<endl;
//追加
//向后追加
vec2.push_back(222);
//开头追加
vec2.insert(vec2.begin(),666);
//第二个位置追加
vec2.insert(vec2.begin()+1,888);
//最后位置追加
vec2.insert(vec2.end(),999);
//倒数第3个位置追加
vec2.insert(vec2.end()-2,787);
//删除
//删除第二个元素
vec2.erase(vec2.begin()+1);
//删除倒数第二个元素
vec2.erase(vec2.end()-2);
//删除最后一个元素
vec2.pop_back();
//遍历
for(int i:vec2){
cout<<i<<" ";
}
list列表
list内部使用双向列表实现,能高效的进行插入和删除操作,但是随机存取的效率低。其次,list不支持使用下标操作元素,需要使用迭代器指针操作元素
#include <list>
//创建一个空列表对象
list<string> lis;
//判断列表是否为空
cout<<lis.empty();
//向后追加元素
lis.push_back("End");
//向前追加元素
lis.push_front("Start");
//在第二个位置插入元素
lis.Insert(++lis.begin(),"Second");
//将列表保存为迭代器指针
list<string>::iterator iter=lis.begin();
//移动指针到列表第二个位置
advance(iter,2);
//在第二个位置插入元素
lis.insert(iter,"Second_iter");
//修改迭代器指针所在位置的元素
*iter="This_iter";
//遍历
//使用for each
for(string i:lis){
cout<<i<<"";
}
cout<<endl;
//删除最后一个元素
lis.pop_back();
//删除第一个元素
lis.pop_front();
//删除第二个元素
lis.erase(++lis.begin());
deque队列
该API基本兼顾vector及list,且性能均衡,这里就不再叙述了
关联容器
关联容器的各个元素之间没有严格的物理上的顺序关系,但是在内部依旧具有排序的特点,因此可以使用迭代器进行遍历
常见的关联容器类型:map muitimap
#include <map>
//创建一个内容为空的键值对对象
map<string,int> map1;
//检查容器是否为空
cout<<map1.empty();
//检查键在容器内部是否存在
if(map1.find("age")==map.end()){
//增加元素方法1
map1["age"]=22;
}
//增加元素方法2
map2.insert(pair<string,int>("salary",2000));
//修改元素
if(map1.find("age")=map1.end()){
map1["age"]=25
}
//删除元素
map1.erase("weight");
//元素数量
cout<<map1.size();
//取出元素
cout<<map1["age"];
//迭代器遍历(见迭代器)
迭代器
迭代器是一种特殊的指针,通常用于遍历所有的容器类型,而且遍历效率高,推荐使用迭代器进行遍历操作
其次,不同的容器类型使用迭代器的方法基本一致,const_iterator是只读迭代器,iterator是读写迭代器
#include <string>
string str="random";
for(string::const_iterator iter=str.begin();iter!=str.end();iter++){
cout<<*iter<<" ";
}
cout<<endl;
#include <array>
array<string,4> arr={"random","string","vector","iterator"};
array<string,4>::iterator arr_iter=arr.begin();
while(arr_iter!=arr.end()){
cout<<*(arr_iter++)<<" ";
}
cout<<endl;
#include <vector>
vector<string> vec(5,"vector");
for(vector<string>::iterator iter=vec.begin();iter!=vec.end;vec++){
cout<<*iter<<" ";
}
cout<<endl;
#include <list>
list<string> lis(5,"list");
for(list<string>::iterator iter=lis.begin();iter!-lis.end();iter++){
cout<<*iter<<" ";
}
cout<<endl;
#include <deque>
deque<string> deq(5,"qeque");
for(queue<string>::iterator iter=deq.begin();iter!=deq.end();iter++){
cout<<*iter<<" ";
}
cout<<endl;
//迭代器遍历map
#include <map>
map<string,int> map1;
map1.insert(pair(<string,int>("age",24));
for(map<string,int>::iterator iter=map1.begin();iter!=map1.end();iter++){
cout<<iter->first<<iter->second<<endl;
}
异常处理
C++的异常是指程序在运行期间出现的问题,编译可以通过,说明代码出现了逻辑问题,而不是语法问题。当程序运行的过程中出现了 C++标准库中预定义的异常现象时,程序会抛出一个异常对象,此对象需要正确地处理,否则会导致运行终止。
处理异常的方式有两种:
抛出异常:throw
捕获异常:try-catch
抛出异常
使用throw可以抛出任意类型的对象,此对象作为当前程序运行时的错误的代表
double division(double a,double b){
if(b==0){
throw "除数不能为0";
}
return a/b;
}
自定义异常类型
C++内置了一些标准异常类型,抛出的const char*类型的异常对象并不属于标准异常类型家族;
可以自己写一个异常类继承标准异常类型,从而加入标准异常家族,使用标准异常类型需要引入头文件
标准异常类型家族
自定义异常类
#include <stdexcept>
class ZeroException::public exception
{
public:
const char*what() const throw(){
return "除数不能为0";
}
}
double disvision(double a,double b){
if(b==0){
throw ZeroException();
}
return a/b;
}
智能指针
概念
C++语言中堆内存的对象在new之后创建,如果忘记delete则会产生内存泄露的问题,一些高级语言使用了垃圾回收机制来自动处理不再回收的对象,C++也引入了智能指针的概念,使堆内存对象能够自动回收
智能指针主要用于管理堆内存对象的生命周期,本身指针对象是位于栈内存的对象,当智能指针对象的生命周期结束后,会在析构函数里释放管理的堆内存对象的内存空间,从而防止内存泄露
C++的四种智能指针
auto_ptr 自动指针
unique_ptr 唯一指针
shared_ptr 共享指针
weak_ptr 虚指针
注意:智能指针的使用需要引入头文件<memory>
auto_ptr
#include <memory>
class Test//创建一个标准的类,这里就不再赘述了,简单叙述一下这个类的声明
{
public:
Test();
~Test();
void show();
}
int main(){
{
Test *t=new Test("A");
//创建一个智能指针对象,并管理对象A
auto_ptr<Test> ap1(t);
//让C对象顶替对象A被ap1管理
ap1.reset(new Test("C"));//A被销毁了
Test *t3=ap1.get();
ap1.release();//释放管理权,但并不销毁资源对象
//手动管理对象C
delete t3;
//创建一个堆内存对象B,并交给ap2进行管理
auto_ptr<Test>ap2(new Test("B"));
//获取ap2管理的对象并使用
ap2.get()->show();
Test *t2=ap2.get();
}
}
关于auto_ptr的缺陷
auti_ptr容易在复制语义中出现控制权转移的问题
int main(){
{
auto_ptr<Test> ap1(new Test("A"));
auto_ptr<Test> ap2=ap1;//隐式拷贝,权限发生转移
auto_ptr<Test> ap3(ap2);//显示拷贝,权限再次转移
auto_ptr<Test> ap4;
ap4=ap3;//赋值运算符,权限再次发生转移
ap1.get()->show();//报错
}
}
unique_ptr
作为auto_ptr的改进,unique_ptr对持有的堆内存对象具有唯一的控制权,因为unique_ptr屏蔽了auto_ptr的复制语义
#include <memory>
int main(){
{
unique_ptr<Test> up1(new Test("A"));
/*
错误写法合集:
unique_ptr<Test> ip2=up1;
unique_ptr<Test> ip3(up2);
unique_ptr<Test> ip4;
ip4=ip3;
*/
}
}
那么unique_ptr就不能进行复制操作了吗?
不是的,unique_ptr只是提高了转移控制权的语法门槛,通过move函数配合复制语义可以转移控制权
#include <memory>
int main(){
{
unique_ptr<Test> ip1=new Test("A");
unique_ptr<Test> ip2=move(ip1);
unique_ptr<Test> ip3(move(ip2));
unique_ptr<Test> ip4;
ip4=move(ip3);
}
}
shared_ptr
由于unique_ptr对资源具有独占性,因此shared_ptr可以把资源在多个shared_ptr之间共享
//shared_ptr有两种初始化方式
#include <memory>
int main(){
{
//传统方式
shared_ptr<Test> sp1(new Test("A"));
//使用make_shared函数
shared_ptr<Test> sp2=make_shared<Test>("B");
}
}
使用make_shared函数的优点:
性能更高 更加安全 延迟释放
shared_ptr通过引用计数来处理资源共享的问题,每当一个shared_ptr对象对资源进行管理,计数会增加1,每一个管理资源对象的shared_ptr销毁时,计数会减少1,如果计数从1到0,说明没有任何一个shared_ptr对象持有该资源,则释放资源
#include <memory>
int main(){
{
shared_ptr<Test> sp1=make_shared<Test>("A");
//复制语义
shared_ptr<Test> sp2(sp1);
cout<<sp1.use_count()<<sp2.use_count()<<endl;//22
sp1.reset();
cout<<sp1.use_count();//0
cout<<sp2.use_count();//1
}
}
weak_ptr
weak_ptr本身并不控制资源的生命周期。只提供了对其管理的资源的一种访问手段,通常协助shared_ptr使用,因为不会影响引用计数
weak_ptr 可以通过lock函数得到一个share_ptr对象,此时计数会+1
#include <memory>
int main(){
weak_ptr<Test> wp1;
shared_ptr<Test> sp1;
wp1=sp1;
weak_ptr<Test> wp2(sp2);
weak_ptr<Test> wp3=sp1;
//weak_ptr<Test> wp4(new Test("B"));//报错
shared_ptr sp2=wp3.lock();
//失效性检测
if(wp2.expired()){
//不能被lock
}else{
//能被lock
}
}
其他内容
nullptr
C++11中用于替代传统的NULL,因为传统NULL在源码中的具体值为0,NULL本身可以简单理解为一个宏定义
#include <iostream>
using namespace std;
void func(int){
cout<<"a"<<endl;
}
void func(char *){
cout<<"b"<<endl;
}
int main(){
func(NULL);//a
func(nummptr)//b
}
类型推导
可以使用auto关键字自动推导类型
#include <iostream>
using namespace std;
int main(){
auto i=6;//int
auto i2=new suto(19);//int*
}
auto不能用于参数类型,也不支持表达式和数组,可以使用decltype的关键字进行表达式推导,decltype的原理是编译器只分析表达式的结果类型,而不实际计算表达式的值
#include <iostream>
using namespace std;
int main(){
auto x=2;
auto y=2.3;
decltype(x*y)z=123.2;
cout<<z<<endl;//123.2
}
流
标准输出流cout
进制输出
cout支持十进制、八进制、十六进制输出
#include<iostream>
using namespace std;
int main(){
//切换为显示进制模式(八进制前加0,十六进制前加0x)
cout<<showbase;
//切换为八进制
cout<<oct;
cout<<9;//011
//切换为16进制
cout<<hex;
cout<<16;//0x11
cout<<dec;
cout<<11;//11
cout<<noshowbase;//切换为不显示进制的模式
}
输出域
输出域可以控制一次输出内容的宽度,如果输出的内容大于设定的宽度,依然会按照内容的宽度进行输出
需要引入头文件<iomanip>
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
cout<<setw(10)<<"123"<<setw(10)<<"1234567890123"<<endl;
}
字符串流
sstream是字符串流的头文件,使用流式数据完成string与数字类型之间的转换
#include <iostream>
#include <sstream>
using namespace std;
int main(){
//int to string
int i=1;
stringstream ss;
ss<<i;
//string to int
string s="1234";
istringstream iss(s);
iss>>i;
}