关于我面试后发现自己基础薄弱这件事
C++学习笔记
即是对自己基础的加强,也是为了更好地去迎接面试考官的毒打。
const关键字
const关键字可以放在变量定义时的数据类型前,数据类型后,指针类型前,指针类型后,也可以放在函数前后。
- const放在数据类型前,表示该数据为常量,不可修改
#include <iostream>
using namespace std;
int main()
{
int a = 10;
const int b = 20;
b = 10; //编译器会报错
return 0;
}
- const放在数据类型后,同样表示该数据为常量,不可修改
#include <iostream>
using namespace std;
int main()
{
int const c =30;
c = 20; //编译器会报错
return 0;
}
- const放在指针类型前,表示不可通过此指针修改所指向的内容
#include <iostream>
using namespace std;
int main()
{
int a = 10;
const int *q = &a;
*q = 2; //编译器会报错,仅可通过指针读取内存
return 0;
}
- const放在指针类型后,表示该指针不可修改
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int d = 2;
const int* q = &a;
//*q = 2;
q = &d; //可以修改指针
int* const p = &a;
p = &d; //不可修改指针,编译器会报错
return 0;
}
- const放在函数返回类型前,表示函数返回的是const类型变量, 不可修改, 不会出现GetStr() = “abc” 的情况
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n, int v, const string& str) : length(n), value(v), s(str) {}
private:
int length;
int value;
string s;
public:
int GetLength() const
{
return length;
}
int GetValue() const
{
return value;
}
const string& GetStr() const
{
return s;
}
};
int main()
{
Vector vector(1, 2, "sss");
cout << "str = " << vector.GetStr() << endl; //输出sss
return 0;
}
若GetStr()函数不使用const修饰的话,会出现什么情况呢?则返回类型要为String类型;
若一个Add()函数返回Vector对象,若不添加const修饰,则会出现 Add() = vector 的情况
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n, int v, const string& str) : length(n), value(v), s(str)
{
cout << "调用构造函数" << endl;
} //构造函数
Vector(const Vector& vector)
{
this->length = vector.GetLength();
this->value = vector.GetValue();
this->s = vector.GetStr();
cout << "调用复制构造函数" << endl;
}
private:
int length;
int value;
string s;
public:
int GetLength() const
{
return length;
}
int GetValue() const
{
return value;
}
const string& GetStr() const
{
return s;
}
};
Vector AddVector(const Vector& vector1, const Vector& vector2) //未添加const修饰返回值
{
int l = vector1.GetLength() + vector2.GetLength();
int v = vector1.GetValue() + vector2.GetValue();
string s = vector1.GetStr() + vector2.GetStr();
return Vector(l, v, s);
}
int main()
{
Vector vector1(1, 2, "sss");
Vector vector2(2, 3, "aaa");
AddVector(vector1, vector2) = vector2; //允许的
return 0;
}
这里我又去复习了下关于复制构造函数的使用
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n, int v, const string& str) : length(n), value(v), s(str)
{
cout << "调用构造函数" << endl;
}
Vector(const Vector& vector)
{
this->length = vector.GetLength();
this->value = vector.GetValue();
this->s = vector.GetStr();
cout << "调用复制构造函数" << endl;
}
private:
int length;
int value;
string s;
public:
int GetLength() const
{
return length;
}
int GetValue() const
{
return value;
}
const string& GetStr() const
{
return s;
}
};
Vector AddVector(const Vector& vector1, const Vector& vector2)
{
int l = vector1.GetLength() + vector2.GetLength();
int v = vector1.GetValue() + vector2.GetValue();
string s = vector1.GetStr() + vector2.GetStr();
return Vector(l, v, s);
}
int main()
{
Vector vector1(1, 2, "sss");
Vector vector2(2, 3, "aaa");
Vector vector3(AddVector(vector1, vector2)); //这里调用的不是复制构造函数, 因为C++标准允许编译器实现做一些优化。例如:Class X b=X();
Vector vector4(vector1); //调用复制构造函数
return 0;
}
- const放在函数名后,表示此成员函数内不可修改成员变量,此函数为常函数
常函数——表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n) : length(n) {}
private:
int length;
public:
int GetLength() const
{
//length += 1; 报错
return length;
}
};
int main()
{
Vector vector(5);
cout << vector.GetLength() << endl; //输出5
return 0;
}
常函数可以修改静态成员变量,见如下代码
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n) : length(n) {}
static int a; //静态成员变量
private:
int length;
public:
int GetLength() const
{
//length += 1; 报错
a += 2;
return a;
}
};
int Vector::a = 1; //初始化
int main()
{
Vector vector(5);
cout << vector.GetLength() << endl; //输出3
return 0;
}
const对象不能调用非const成员函数
#include <iostream>
using namespace std;
class Vector{
public:
Vector(int n, int v) : length(n), value(v) {}
static int a;
private:
int length;
int value;
public:
int GetLength() const
{
//length += 1; 报错
//a += 2;
return length;
}
int GetValue()
{
return value;
}
};
int Vector::a = 1;
int main()
{
const Vector vector2(1, 10);
cout << "length = " << vector2.GetLength() << endl; //输出5
cout << "Value = " << vector2.GetValue() << endl; //报错
return 0;
}
7、 const放在函数参数前,表示在函数内部处理时不对该变量修改,一般和引用&一起,使用引用将不调用复制构造函数
const Vector AddVector(const Vector& vector1, const Vector& vector2)
{
int l = vector1.GetLength() + vector2.GetLength();
int v = vector1.GetValue() + vector2.GetValue();
string s = vector1.GetStr() + vector2.GetStr();
return Vector(l, v, s);
}
8、指向非const类型数据的指针不能指向const类型数据,const类型指针既可以指向非const类型数据,也可以指向const类型数据
int main()
{
int a = 1;
const int b = 2;
int* q = &b; //编译器会报错
return 0;
}
int main()
{
int a = 1;
const int b = 2;
int const * const q = &b;
const int* p = &a; //指向const变量的指针可以指向非const变量
return 0;
}
noexcept关键字
放在函数后,有两种形式——①noexcept和②noexcept(expression)
1、noexcept
表示函数不会抛出异常
2、noexcept(expression)
根据expression表达式决定函数会不会抛出异常
//swap函数的内部实现
template<typename _Tp, size_t _Nm>
void swap(_Tp (&__a)[_Nm], _Tp (&__b)[_Nm])
#if __cplusplus >= 201103L
noexcept(noexcept(swap(*__a, *__b)))
#endif
函数模板
函数模板是通用的函数描述,使用泛型来定义函数;使用typename和class关键字创建模板,表示任意类型;class关键字是在标准C++98添加typename之前使用的,考虑到向后的兼容性,建议使用typename关键字。
template <typename T> / template <class T>
void Myswap(T& a, T& b) noexcept
{
T temp;
temp = a;
a = b;
b = temp;
}
在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器在使用模板(函数调用)时,才为特定类型生成函数定义,即模板实例化(instantiation)。模板实例化有两种,一种是隐式实例化,一种是显式实例化。
隐式实例化:编译器在函数调用时,通过函数参数类型判断需要生成实例类型,如上代码块
显式实例化:直接命令编译器创建特定的实例,如
template <> void Myswap<int>(int& a, int& b) noexcept
{
int temp;
temp = a;
a = b;
b = temp;
}
函数模板的参数不一定都是泛型,也可以为特定类型,这种指定特殊的类型而不是用作泛型名,称为非类型或表达式参数。如
template <typename T, size_t num>
void Myswap(T (&a)[num], T (&b)[num]) noexcept //使用数组引用作为参数,可以获取数组的大小num,若是使用指针类型,则数组会转化为指针,从而丢失数组的信息
{
for(int i=0; i<num; i++)
{
Myswap(a[i], b[i]);
}
}
int main()
{
char a[] = "abc";
char b[] = "def";
//cout << "a = " << a << " b = " << b << endl;
Myswap<char, 4>(a, b); //显式实例化,数组长度为4,字符串以'\0'结束
//cout << "a = " << a << " b = " << b << endl;
return 0;
}
size_t是标准C库中定义的,它是一个基本的与机器相关的无符号整数的C/C + +类型, 它是sizeof操作符返回的结果类型。
数组指针、指针数组及数组引用
数组指针
指向数组的指针,如下
int main()
{
int arr[2][3] = {1, 2, 3, 4, 5, 6};
int (*ptr)[3] = arr;
for(int i=0; i<3; i++)
cout << "ptr" << i << " = " << *(*ptr+i) << endl;
cout << "指针内存大小" << "ptr = "<< sizeof(*ptr) << " arr = " << sizeof(arr) << endl; //ptr指向长度为3的数组,*ptr将长度为3的数组解引用,ptr的大小为8bit,与计算机的位数相关,即指针的大小,arr为数组名,sizeof读取出来得为数组的大小
cout << "指针加1的偏移量" << "ptr = " << ptr << " ptr + 1 = " << ptr + 1 << endl; //相差c偏移量,即长度为3的数组
cout << "指针加1的偏移量" << "*ptr = " << *ptr << " ptr + 1 = " << *ptr + 1 << endl; //*ptr为长度为3数组的第一个元素的指针,*ptr 与*ptr+1相差一个元素的大小
cout << "指针加1的偏移量" << "arr = "<< arr << " arr + 1 = " << arr + 1 << endl; //arr与arr+1相差c个偏移量,与指针ptr等同
cout << "指针加1的偏移量" << "*arr = "<< *arr << " *arr + 1 = " << *(arr + 1) << endl; //*arr解引用,即指向长度为3的数组,arr+1将偏移了一个长度为3的数组长度
return 0;
}
指针数组
即数组元素为指针的数组,如
int main()
{
char* a[3] = {"ac", "bd", "\0"};
char** p = a; //将p指向数组a,a是长度为3,元素为char*类型的指针数组
while(*p != "\0")
{
cout << *p << " ";
p = p + 1; //将p移向下一个元素
}
return 0;
}
数组引用
通过另一个变量引用数组,从而实现数组的修改
int main()
{
int a[3] = {1, 2, 3};
int (&p)[3] = a; //引用数组a, 维度需要相同
for(int i=0; i<3; i++)
cout << a[i] << " ";
cout << endl;
p[0] = 3; //通过p修改数组a
for(int i=0; i<3; i++)
cout << a[i] << " ";
cout << endl;
cout << "地址" << " p = " << p << " a = " << a << endl;
return 0;
}
类模板
继承和包含并不总是能够满足重用代码的需要,C++的类模板为生成通用的类声明提供了一种更好的方法。模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。
如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
/*自定义栈模板类*/
template <typename T>
class MyStack
{
private:
enum { MAXSIZE = 10 }; //栈的最大长度
int top; //栈顶
int size; //动态分配栈的长度
T* Item; //用于存放元素的数组
public:
//构造函数
MyStack(int s = MAXSIZE);
//析构函数
~MyStack();
//复制构造函数
MyStack(const MyStack& st);
//判断栈是否为空
bool isEmpty() const ;
//判断栈是否为满
bool isFull() const;
//将元素压入栈中
bool Push(const T& item);
//将元素从栈中弹出
bool Pop(T& item);
//返回top指针
int getTop() const;
//返回size
int getSize() const;
//返回栈的容器
T* getItem() const;
//重写操作符=
MyStack& operator=(const MyStack& st);
};
模板可用作结构、类或模板类的成员 可以在模板类中将另一个模板类和模板函数作为成员
template <typename T>
class nestTemplate
{
private:
template <typename V>
class hold;
hold<T> h;
public:
nestTemplate(T v) : h(v) {}
template <typename U>
void show(T a, U b) const;
};
template <typename T>
template <typename V>
class nestTemplate<T>::hold
{
public:
V val;
public:
hold(V v = 0) : val(v) {};
};
template <typename T>
template <typename U>
void nestTemplate<T>::show(T a, U b) const
{
cout << a + b << " " << h.val << endl;
}
int main()
{
nestTemplate<int> nest(5);
nest.show(2, 2.3); //输出4.3 5
}
模板可以包含类型参数和非类型参数,还可以包含本身就是模板的参数
template <template <typename T> class MyStack, typename U, typename V>
class MyPairStack
{
private:
// a pair of stacks
MyStack<U> st1;
MyStack<V> st2;
public:
//构造函数
MyPairStack(const MyStack<U>& s1, const MyStack<V>& s2) : st1(s1), st2(s2) {};
//在保证模板类成员具有Push函数和Pop函数的前提下
bool Push(U u, V v)
{
return st1.Push(u) && st2.Push(v);
}
bool Pop(U& u, V& v)
{
return st1.Pop(u) && st2.Pop(v);
}
};
int main()
{
MyStack<int> s1;
MyStack<const char*> s2;
MyPairStack<MyStack, int, const char*> pairSt(s1, s2);
pairSt.Push(1, "2.2");
int a=0;
const char* b;
pairSt.Pop(a, b);
cout << "a = " << a << " b = " << b << endl;
return 0;
}
模板类示例(自定义数组模板类)
/*
自定义数组模板类 实现可拓展、可泛型
功能:
大小自定义、初始化时设置大小
大小可扩充、resize()
插入元素 insert
在尾部插入元素 push_back,同时可对数组扩充
实现[]操作符,实现对元素的返回,索引
实现at(index)函数,返回索引为Index的元素
删除元素erase(index);
*/
template <typename T>
class MyArray
{
private:
T* Elem; //元素数组
int size; //数组大小
int capacity; //数组容量
public:
//构造函数
MyArray(int s);
//返回数组的大小
int GetSize() const;
//返回数组的容量
int GetCapacity() const;
//再定义数组的容量
void resize(int s);
//返回索引为Index的元素
T& at(int index) const;
//将元素复制一份从尾部添加进数组
void push_back(T elem);
//将元素插入进指定合法位置
bool insert(const T& elem, int index);
//用索引获取数组元素
T& operator[](int index);
//删除合法位置的元素
bool erase(int index);
//析构函数
~MyArray();;
};
template <typename T>
MyArray<T>::MyArray(int s) : capacity(s)
{
Elem = new T[capacity]; //分配空间
size = 0; //初始为0
cout << "调用MyArray构造函数" << endl;
}
template <typename T>
int MyArray<T>::GetSize() const
{
return size;
}
template <typename T>
int MyArray<T>::GetCapacity() const
{
return capacity;
}
template <typename T>
void MyArray<T>::resize(int s)
{
//若s小于capacity,缩容
if (s < capacity)
{
T* p = new T[s];
for (int i = 0; i < s; i++)
p[i] = Elem[i];
delete[] Elem;
Elem = p;
capacity = s;
if (size > s)
size = s;
}
else
{
//s大于capacity,扩容
T* NewElem = new T[s]; //分配新的空间给新的数组
for (int i = 0; i < size; i++)
{
NewElem[i] = Elem[i];
}
delete[] Elem;
Elem = NewElem; //将Elem原数组指针指向新的数组
capacity = s;
}
}
template <typename T>
T& MyArray<T>::operator[](int index)
{
T* p = Elem + index; //指针向后移动
return *p;
}
template <typename T>
T& MyArray<T>::at(int index) const
{
return Elem[index];
}
template <typename T>
void MyArray<T>::push_back(T elem)
{
if (size >= capacity) //数组还有内存
{
//数组已满
this->resize(size + 1);
}
Elem[size++] = elem; //插入到尾部
}
template <typename T>
bool MyArray<T>::insert(const T& elem, int index)
{
//索引小于零,或者大小数组大小, 数组没有容量
if (index < 0 || index > size || size >= capacity)
return false;
//将index及后的元素向后移动
for (int i = size - 1; i >= index; i--)
{
Elem[i + 1] = Elem[i];
}
Elem[index] = elem;
size++;
return true;
}
template <typename T>
bool MyArray<T>::erase(int index)
{
//索引是否合法
if (index < 0 || index >= size)
return false;
//将index后的元素向前移动
for (int i = index; i < size-1; i++)
{
Elem[i] = Elem[i + 1];
}
size--;
return true;
}
template <typename T>
MyArray<T>::~MyArray()
{
cout << "调用MyArray析构函数" << endl;
delete[] Elem;
}
int main()
{
/*用自定义数组模板类处理基本数据类型*/
MyArray<string> strArr(4);
strArr.push_back("abc");
strArr.push_back(string("bc"));
for (int i = 0; i < strArr.GetSize(); i++)
cout << strArr[i] << " ";
strArr.erase(0);
cout << endl;
for (int i = 0; i < strArr.GetSize(); i++)
cout << strArr[i] << " ";
/*自定义数组模板类处理const char*指针类型 */
MyArray<const char*> charStrArr(3);
const char* s = "saa";
charStrArr.push_back(s);
charStrArr.push_back("aaa");
cout << endl;
for (int j = 0; j < charStrArr.GetSize(); j++)
cout << charStrArr[j] << " ";
/*自定义数组模板类处理模板类*/
MyArray<MyStack<int>> StackArr(5); //在分配空间时自动为元素调用构造函数
MyStack<int> st(4);
st.Push(2);
st.Push(3);
StackArr.push_back(st); //调用复制构造函数
MyStack<int> st2(2);
st2.Push(23);
st2.Push(11);
StackArr.push_back(st2);
int val;
for (int i = 0; i < StackArr.GetSize(); i++)
{
while (!StackArr[i].isEmpty())
{
StackArr[i].Pop(val);
cout << val << " ";
}
cout << endl;
}
MyStack<int> st3(st); //调用复制构造函数
return 0;
}
区分具体化、实例化
/*
模板具体化
隐式实例化,显式实例化,都属于具体化
显式具体化,部分具体化,都属于具体化
隐式实例化即在需要对象时,编译器按照通用模板生成类定义
显式实例化即为特定类型声明类,编译器在没有创建类对象时,也会生成类定义。
显式具体化即为特定类型重写定义,不使用通用模板具体化。
部分具体化即指定部分泛型的类型参数。
当通用模板和具体化模板都匹配时,优先使用具体化模板
*/
template <typename T, typename V>
class clock
{
private:
T hour; //小时数
V minute; //分钟数
public:
clock(T h, V m) : hour(h), minute(m) {};
void showTime() const
{
cout << "Time is " << hour << " h " << minute << " minutes" << endl;
}
};
//显式具体化
template <> class clock<int, int>
{
private:
int hour;
int minute;
public:
clock(int h, int m) : hour(h), minute(m) {};
void showTime() const
{
cout << "hour = " << hour << ", minute = " << minute << endl;
}
};
//显式实例化
template clock<float, int>::clock(float, int);
//部分具体化
template <typename T> class clock<T, string>
{
private:
T hour;
string minute;
public:
clock(T h, string s) : hour(h), minute(s) {};
void showTime() const
{
cout << "小时为" << hour << ",分钟为" << minute << endl;
}
};
int main()
{
//隐式实例化
clock<float, float> Clock(1.4, 20.22); //使用通用模板定义
Clock.showTime();
clock<int, int> C(6, 12); //使用具体化模板定义
C.showTime();
clock<int, string> cl(2, "五十"); //使用部分具体化模板定义
cl.showTime();
clock<int, const char*> clo(2, "五十"); //使用通用模板
clo.showTime();
return 0;
}
运行结果
Time is 1.4 h 20.22 minutes
hour = 6, minute = 12
小时为2,分钟为五十
Time is 2 h 五十 minutes
--------------------------------
数组初始化
只有在定义数组时才能初始化,但可以使用数组下标给元素赋值,不能将一个数组赋给另一个数组
int arr[] = {1, 2, 3, 4};
int brr[3] = {2, 3, 5};
int crr[4];
crr[0] = 1;
初始化数组时,可以只给一部分元素赋值,其余的元素默认为0.
int a[4] = {1};
C++使用大括号的初始化(列表初始化)作为一种通用的初始化方式,可以用于所有类型。
float a[4] {1.2, 2.3};
double b[2]{}; //默认为0
int c[4] = {};
列表初始化禁止缩窄转换,可省略‘=’,可不在括号里包含任何东西。
函数指针
函数的地址是存储其机器语言代码的内存的开始地址。与直接调用另一个函数相比,使用函数指针允许在不同的时间使用不同函数,即传递不同函数的地址。C++函数名与函数地址的作用相同,可以将函数名作为函数指针传递。
声明指向函数的指针时,也必须指定指针指向的函数类型,函数的返回类型,函数的特征标。
//加法
double add(double, double);
//减法
double sub(double, double);
//乘法
double mul(double, double);
//计算,使用函数指针分别调用三个函数
double calculate(double, double, double (*p[3])(double, double));
int main()
{
double a = 2.3;
double b = 1.0;
//函数指针数组
double (*p[3])(double, double) = {add, sub, mul};
cout << "sum = " << calculate(a, b, p) << endl;
//函数指针
double (*q)(double, double) = sub;
cout << "sub = " << (*q)(a, b) << endl;
//指向函数指针数组的指针
double (*(*pp)[3])(double, double) = &p;
cout << "add = " << (*pp)[0](a, b) << endl;
return 0;
}
double add(double a, double b)
{
return a + b;
}
double sub(double a, double b)
{
return a - b;
}
double mul(double a, double b)
{
return a * b;
}
double calculate(double a, double b, double (*p[3])(double, double))
{
double sum = 0.0;
for(int i=0; i<3; i++)
{
//一种使用方式——类似于函数名
sum += p[i](a, b);
cout << (*p[i])(a, b) << endl; //另一种使用方法——函数指针形式直观
}
return sum;
}
智能指针
智能指针是行为类似于指针的类对象,智能指针模板总共有四种,分别是auto_ptr,shared_ptr,unique_ptr,weak_ptr。将new获得的地址赋给智能指针对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。
auto_ptr和unique_ptr
auto_ptr是C++98提供的解决方案,C++11已经将其摒弃了,若您的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。unique_ptr为C++11提供的方案;
auto_ptr
和unique_ptr
有相似的策略,但unique_ptr的策略更加严格。两者为了避免程序试图删除同一个对象两次,即两个智能指针指向同一块内存。建立所有权的概念,对于特定的对象,只能有一个智能指针可拥有它;若是使用赋值语句,则将对象的所有权转让。
unique_ptr<double> p1(new double(5));
unique_ptr<double> p2;
//p2 = p1; 编译器报错、不允许,
//auto_ptr建议弃用deprecated
auto_ptr<float> p6(new float(2.3));
cout << "p6 = " << *p6 << endl; //使用*解引用操作符
auto_ptr<float> p7 = p6; //支持
cout << "p7 = " << *p7 << endl;
//cout << "then p6 = " << *p6 << endl; //悬挂指针
那如何将unique_ptr对象赋值给另一个对象呢,可以使用C++11提出的std::move
unique_ptr<double> p1(new double(5));
cout << "p1 = " << *p1 << endl;
unique_ptr<double> p2;
//p2 = p1;
p2 = move(p1);
cout << "p1 move to p2, p2 = " << *p2 << endl;
cout << "p1 = " << *p1 << endl; //悬挂指针
相比auto_ptr,unique_ptr智能指针支持将临时右值赋值给另一个对象,因为临时对象很快就会被销毁了。unique_ptr智能指针还支持移动构造函数。
template <typename V>
unique_ptr<V> GetV(V v)
{
return unique_ptr<V>(new V(v));
}
int main()
{
unique_ptr<string> s(GetV<string>("aaaaa"));
cout << "s = " << *s << endl;
unique_ptr<double> p2 = GetV<double>(21.12); //将临时对象赋值给p2,右值引用
cout << "p2 = " << *p2 << endl;
p2 = unique_ptr<double>(new double(233)); //临时右值
cout << "p2 = " << *p2 << endl;
unique_ptr<double> p1(new double(5));
p1 = unique_ptr<double>(new double(1.111)); //可以多次赋值,p1原来所指向的内存与临时对象指向的内存相交换
return 0;
}
unique_ptr还有一个优点,即可用于数组的变体
unique_ptr<int[]> arr(new int[4]{}); //可用于数组
for(int i=0; i<4; i++)
{
arr[i] = 1; //赋值
cout << arr[i] << " ";
}
shared_ptr
对于上述的问题(如何解决两个指针同时指向一块内存),还有一种解决办法,就是创建智能更高的指针,跟踪引用特定对象的智能指针数。仅当最后一个引用对象的指针过期时,即
引用计数
为零时,才调用delete,这就是shared_ptr
的策略。
int a = 10;
int* p3 = &a;
shared_ptr<int> p4(p3);
cout << "p4 use count = " << p4.use_count() << endl; //输出引用计数
shared_ptr<int> p5 = p4; //共享指针赋值
cout << "p5 use count = " << p5.use_count() << endl; //输出引用计数
cout << "p4 = " << *p4 << endl;
cout << "p5 = " << *p5 << endl;
输出:
p4 use count = 1
p5 use count = 2
p4 = 10
p5 = 10
weak_ptr
shared_ptr智能指针模板也存在自身的缺陷问题,例如当两个对象互相引用对方后,出现环形引用,导致内存泄漏,无法调用~析构函数。这时需要一个类似于shared_ptr的指针模板,可以获得对象的引用,但是却不对引用计数造成影响,不参与所有权的分享,所以C++11提供了
weak_ptr
智能指针模板。
class person
{
public:
int data;
public:
person(int d = 22) : data(d) {
}
};
int main()
{
shared_ptr<person> s1 = make_shared<person>(555); //创建一个由智能指针所有的对象
cout << "s1 address = " << s1.get() << endl; //返回对象指针
cout << "s1 use count = " << s1.use_count() << endl;
weak_ptr<person> w1(s1);
cout << "w1 address = " << w1.lock() << endl; //获得引用对象
cout << "w1 use count = " << w1.use_count() << endl;
cout << "After weak_ptr point to shared_ptr,s1 use count = " << s1.use_count() << endl;
cout << "s1 data = " << s1->data << endl; //使用->操作符
cout << "w1 data = " << w1.lock()->data << endl;
return 0;
}
输出:
s1 address = 0xa07380
s1 use count = 1
w1 address = 0xa07380
w1 use count = 1
After weak_ptr point to shared_ptr,s1 use count = 1
s1 data = 555
w1 data = 555
weak_ptr使用shared_ptr管理实际对象,自身通过lock()返回对象,通过expire()检查对象是否有效,类似于观察者
。weak_ptr也有自身的引用计数,当引用计数为0时,将自身销毁。
shared_ptr<person> s1 = make_shared<person>(555); //创建一个由智能指针所有的对象
weak_ptr<person> w1(s1);
s1.reset(); //将智能指针引用的对象释放掉
weak_ptr<person> w2 = w1;
cout << "w1 use cout = " << w1.use_count() << endl; //检查时发现为0
cout << "w2 address = " << w2.lock() << endl; //获取对象时为NULL
//后两句均不输出,调试发现 segment fault
cout << "w2 data = " << w2.lock()->data << endl;
cout << "w2 use cout = " << w2.use_count() << endl;
输出:
w1 use cout = 0
w2 address = 0
移动语义和右值引用
左值与右值
左值
为一个表示数据的表达式,程序可以获取其地址;而右值不可对其应用地址运算符的值。右值包括字面常量,诸如x * y等表达式以及具有返回值的函数(该函数返回的不是引用类型)。
左值引用与右值引用
左值引用例如 double & 这样的 类型名+&
,右值引用例如 Tp && 这样的 类型名+&&
。
使用右值引用可以关联到右值,将右值存储到特定位置,从而可以获取其地址,也就可以通过右值引用来访问该右值数据。
std::move,std::forward
std::move函数的作用是将左值转换成右值引用,依次来支持移动语义
(这里直接放上源码)
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __add_lvalue_reference_helper
{ typedef _Tp type; };
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
std::forward函数的作用既可以用来转发左值,也可以用来转发右值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
移动语义
移动语义类似于计算机移动文件,实际文件还留在原来的地方,而只修改记录。移动语义实际上避免了移动原始数据,而只是修改了记录。
根据移动语义,可以编写移动构造函数,移动赋值运算符
class User
{
public:
char* id; //user-id
int age;
public:
//默认构造函数
User() : id(nullptr), age(0) { cout << "默认构造函数" << endl;};
//构造函数
User(const char* m_id, int m_a);
//复制构造函数
User(const User& user);
//移动构造函数
User(User && user);
//复制赋值函数
User& operator=(const User& user);
//移动赋值操作符
User& operator=(User && user);
User operator+(const User& user);
~User();
};
User PopUser()
{
User temp("000x", 66);
return temp;
}
int main()
{ //输出
User u1("001", 18); //构造函数
User u2; //默认构造函数
u2 = u1; //复制赋值操作符
User u3(User("002", 22));//构造函数
User u4; //默认构造函数
u4 = User("003", 33); //构造函数+移动赋值操作符+析构函数
User u5; //默认构造函数
u5 = PopUser(); //默认构造函数+移动赋值操作符
User u6(u3 + u4); //+操作符 + 默认构造函数
return 0;
}
User::User(const char* m_id, int m_age) : age(m_age)
{
cout << "构造函数" << endl;
//执行深复制
int len = 0;
while(m_id[len] != '\0') len++;
this->id = new char[len];
for(int i=0; i<len; i++)
{
this->id[i] = m_id[i];
}
}
User::User(const User& user)
{
cout << "复制构造函数" << endl;
this->age = user.age;
//执行深复制
int len = 0;
while(user.id[len] != '\0') len++; //计算长度
this->id = new char[len];
for(int i=0; i<len; i++)
{
this->id[i] = user.id[i];
}
}
User::User(User&& user)
{
cout << "移动构造函数" << endl;
this->age = user.age;
//执行浅复制
this->id = user.id;
//右值指针失效
user.id = nullptr;
}
User& User::operator=(const User& user)
{
cout << "复制赋值函数" << endl;
this->age = user.age;
//执行深复制
delete this->id;
int len = 0;
while(user.id[len] != '\0') len++; //计算长度
this->id = new char[len];
for(int i=0; i<len; i++)
{
this->id[i] = user.id[i];
}
return *this;
}
User& User::operator=(User && user)
{
cout << "移动赋值操作符" << endl;
this->age = user.age;
delete this->id;
this->id = user.id;
user.id = nullptr;
return *this;
}
User User::operator+(const User& user)
{
cout << "+操作符" << endl;
User temp;
temp.age = this->age + user.age;
//不对id成员变量做处理
return temp;
}
User::~User()
{
cout << "析构函数" << endl;
delete this->id;
}
构造函数
程序在创建派生类对象时,首先创建基类对象。即在调用派生类构造函数前,应调用基类构造函数。(析构函数相反,先调用子类析构函数,再调用父类析构函数)C++使用成员初始化列表完成这份工作。
class base
{
private:
int id;
public:
base(int i) : id(i) {};
virtual ~base()
{
cout << "调用base析构函数!" << endl;
}
};
class derive : public base
{
public:
derive(int d) : base(d) {};
virtual ~derive()
{
cout << "调用derive析构函数!" << endl;
}
};
若是不适用成员初始化列表,则调用基类的默认构造函数。
成员初始化列表只能用于构造函数。