C++学习笔记

C++学习笔记

即是对自己基础的加强,也是为了更好地去迎接面试考官的毒打。

const关键字

const关键字可以放在变量定义时的数据类型前,数据类型后,指针类型前,指针类型后,也可以放在函数前后。

  1. const放在数据类型前,表示该数据为常量,不可修改
#include <iostream>
using namespace std;

int main()
{
	int a = 10;	
	const int b = 20;
	b = 10;		//编译器会报错

	return 0;
}
  1. const放在数据类型后,同样表示该数据为常量,不可修改
#include <iostream>
using namespace std;

int main()
{
	int const c =30;
	c = 20;		//编译器会报错
	return 0;
}
  1. const放在指针类型前,表示不可通过此指针修改所指向的内容
#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	const int *q = &a;
	*q = 2;		//编译器会报错,仅可通过指针读取内存
	return 0;
}
  1. 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;
}
  1. 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;
}
  1. 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

函数模板

函数模板是通用的函数描述,使用泛型来定义函数;使用typenameclass关键字创建模板,表示任意类型;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_ptrunique_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;
	}
};

若是不适用成员初始化列表,则调用基类的默认构造函数。
成员初始化列表只能用于构造函数。

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值