C++指针总结,看这一篇就够了

3 指针

指向地址的数据类型。

3.1指针简介

  • 1.如何获取地址? &—地址运算符
  • 2.指针类型?简单数据类型、复合类型、类相对应的指针类型
  • 3.指针存储空间?各指针的存储空间都相同,因为他们都指向地址,而一个系统中地址的范围是确定的
  • 4.指针赋值?获取变量的地址赋值给对应变量的指针即可
  • 5.如何获取指针指向地址存储的值?—*运算符
  • 6.注意事项:避免产生野指针
    • (1)每个指针变量名,都需要使用一个。
    • (2)一定要在对指针应用解除引用(*)运算符之前,将指针初始化为一个确定的、适当的地址。
    • (3)不能直接将整数赋值给指针,但是可以使用强制类型转换将整数转换为地址然后赋值给指针。强转的结果也可能引发异常。

3.2 指针与动态内存分配

3.2.1 new

  • (1)new创建动态变量:typename * pointer_name = new typename;
  • (2)new创建动态数组:typename * pointer_name = new typename[num_elements];
  • (3)内存分配失败:如果没有足够的存储空间,则内存分配失败,会引发异常
  • (4)存储地点:变量的值存储在栈(stack)内存区域中,new的内存存储在堆(heap)或自由存储区(free store)中

3.2.2 delete

释放内存,由new分配的内存,需要程序员手动释放,如果未释放则会导致内存泄漏(memory leak)。如果内存泄漏严重,则程序将由于不能寻找更多内存而终止。

注意事项:

  • (1)不要使用delete释放不是new分配的内存块
  • (2)不要使用delete释放同一个内存块两次
  • (3)使用new创建的动态变量,对应使用delete(不带方括号)释放内存
  • (4)使用new创建的动态数组,对应使用delete []释放内存 要求(3)和(4)一定要一一对应
  • (5)对空指针应用delete是安全的

3.3 指针算数

将指针变量加1(or 减1)后,增加(or 减少)的量等于它指向的类型的字节数。

3.4 指针与数组

指针与数组名最大的区别就是,可以修改指针的值,但是不能修改数组名的值。
获取数组元素的值可以使用数组名也可以使用指针

arrayname[i] becomes *(arrayname + i)
pointername[i] becomes * (pointername+i)
sizeof(arrayname)---返回值为整个数组的长度,arrayname不能 += 1
sizeof(pointername)---返回值为指针所占内存空间大小,能+=1

数组名表示第一个元素的地址
对数组名取&地址表示的是整个数组的地址,它是一种特殊的指针:

int (*pas)[20]---一个例子,其他数据类型也可;
sizeof(pas) == 4,如果对pas+1的话,增加的是整个数组的长度

对数组名+1的话,返回值增加了数组中单个元素的长度.

3.5 指针与字符串

在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量(const char *)都被解释为字符串第一个字符的地址。
用括号括起的字符串常量被存储在某一位置,如果将其赋值给指针,则该指针指向该字符串常量,该指针可以获取字符串但是不可以更改字符串
不应该对没有new分配内存的char指针执行cin操作,也不应该尝试获取野指针所指向的内容。
如果给cout提供一个char *,则cout默认打印字符串;如果需要显示改字符串地址,则应该强制类型转换char * 为int *
在为char *指针使用new分配内存时,可以根据字符串的长度为其分配内存,但是记得要给’\0’留空间噢。

3.6 指针new动态结构体

创建结构体:见复合类型结构体。
new结构体格式:inflatable * ps = new inflatable;//假设inflatable是我们创建的结构体
使用指针访问new结构体的元素:箭头成员运算符(->),或者(*指针).成员。

3.7 指针数组、数组指针、指针的指针

指针数组:由指针组成的数组
数组指针:指向数组的指针

3.8 举例

代码:

/*
Project name :			_9pointer
Last modified Date:		2022年3月14日21点22分
Last Version:			V1.0
Descriptions:			指针类型
*/
#include<iostream>

int main()
{
	using namespace std;
	/*
	指针:指向地址的数据类型。
		1.如何获取地址? &---地址运算符
		2.指针类型?简单数据类型、复合类型、类相对应的指针类型
		3.指针存储空间?各指针的存储空间都相同,因为他们都指向地址,而一个系统中地址的范围是确定的
		4.指针赋值?获取变量的地址赋值给对应变量的指针即可
		5.如何获取指针指向地址存储的值?---*运算符
		6.注意事项:避免产生野指针
			(1)每个指针变量名,都需要使用一个*。
			(2)一定要在对指针应用解除引用(*)运算符之前,将指针初始化为一个确定的、适当的地址。
			(3)不能直接将整数赋值给指针,但是可以使用强制类型转换将整数转换为地址然后赋值给指针。强转的结果也可能引发异常。
	*/
	cout << "指针****************************************************************" << endl;
	int* p_int;
	char* p_char;
	cout << "sizeof(p_int) = " << sizeof(p_int) << endl;//sizeof(p_int) = 4
	cout << "sizeof(p_char) = " << sizeof(p_char) << endl;//sizeof(p_char) = 4
	int love = 999999;
	cout << "love = " << love << endl;//love = 999999
	p_int = &love;//指针赋值
	cout << "p_int = " << p_int << endl;//p_int = 00EFFC60
	cout << "*p_int = " << *p_int << endl;//*p_int = 999999
	//注意事项1
	int* x, y;//此处声明的是int指针类型的x和int类型的y
	//注意事项2
	//*x = 1000;//这句执行之后指针会指向一个不知道在哪的地址上,可能会导致一些bug
	//注意事项3
	//x = 0x99999999;//不允许的
	//x = (int*)0x99999999;//允许,但是在使用解除引用*运算符后可能引发异常,因为你也不知道它真正指向了哪里
	//cout << "x = " << x << endl;
	//cout << "*x = " << *x << endl;
	//new
	/*
		7.new:
			(1)new创建动态变量:typename * pointer_name = new typename;
			(2)new创建动态数组:typename * pointer_name = new typename[num_elements];
			(3)内存分配失败:如果没有足够的存储空间,则内存分配失败,会引发异常
			(4)存储地点:变量的值存储在栈(stack)内存区域中,new的内存存储在堆(heap)或自由存储区(free store)中
		8.delete:释放内存,由new分配的内存,需要程序员手动释放,如果未释放则会导致内存泄漏(memory leak)。如果内存泄漏严重,则程序将由于不能寻找更多内存而终止。
			注意事项:
				(1)不要使用delete释放不是new分配的内存块
				(2)不要使用delete释放同一个内存块两次
				(3)使用new创建的动态变量,对应使用delete(不带方括号)释放内存
				(4)使用new创建的动态数组,对应使用delete []释放内存
					要求(3)和(4)一定要一一对应
				(5)对空指针应用delete是安全的
		9.指针算数
			将指针变量加1(or 减1)后,增加(or 减少)的量等于它指向的类型的字节数。
	*/
	cout << "new*******************************************************************" << endl;
	int* p_pointer = new int;
	int* p_test = p_pointer;
	delete p_pointer;//释放分配的内存
	//delete p_test;//已经释放的内存块不能再释放,会触发断点。。
	p_test = new int[3]{ 1,2,3 };
	p_test[1] = 999;//更改动态数组的元素
	cout << "p_test[1] = " << p_test[1] << endl;//p_test[1] = 999 //获取动态数组的元素
	cout << "p_test = " << p_test << endl;//p_test = 00FF0480
	p_test = p_test + 1;
	cout << "p_test = " << p_test << endl;//p_test = 00FF0484
	cout << "p_test[1] = " << p_test[1] << endl;//p_test[1] = 3
	p_test = p_test - 1;
	cout << "p_test = " << p_test << endl;//p_test = 00FF0480
	cout << "p_test[1] = " << p_test[1] << endl;//p_test[1] = 999
	delete[] p_test;//释放动态数组
	double* p_double = new double[3]{ 9.9,9.99,9.88 };
	cout << "p_double = " << p_double << endl;//p_test = 00FF0484
	p_double = p_double + 1;
	cout << "p_double = " << p_double << endl;//p_test = 00FF0484
	p_double = p_double - 1;
	cout << "p_double = " << p_double << endl;//p_test = 00FF0484
	delete[] p_double;
	/*
		10.指针与数组
			指针与数组名最大的区别就是,可以修改指针的值,但是不能修改数组名的值。
			获取数组元素的值可以使用数组名也可以使用指针
			arrayname[i] becomes *(arrayname + i)
			pointername[i] becomes * (pointername+i)
			sizeof(arrayname)---返回值为整个数组的长度,arrayname不能 += 1
			sizeof(pointername)---返回值为指针所占内存空间大小,能+=1

			数组名表示第一个元素的地址
			对数组名取&地址表示的是整个数组的地址,它是一种特殊的指针:int (*pas)[20]---一个例子,其他数据类型也可;
				sizeof(pas) == 4,如果对pas+1的话,增加的是整个数组的长度
			对数组名+1的话,返回值增加了数组中单个元素的长度
	*/
	cout << "指针与数组*************************************************************" << endl;
	int arrayname[4] = { 1,2,3,4 };
	int* pointername = arrayname;//pointername指向数组第一个元素的地址
	cout << "pointername = " << pointername << endl;//pointername = 008FFD74
	cout << "*(arrayname + 1) = " << *(arrayname + 1) << endl;//*(arrayname + 1) = 2
	pointername = &arrayname[1];//pointername指向数组第二个元素的地址
	cout << "pointername = " << pointername << endl;//pointername = 008FFD78
	pointername += 1;//allowed
	cout << "*(pointername + 1) = " << *(pointername + 1) << endl;//*(arrayname + 1) = 3
	//arrayname += 1;//not allowed
	int(*pas)[4] = &arrayname;
	cout << "(*pas)[2] = " << (*pas)[2] << endl;//(*pas)[2] = 3
	cout << "sizeof(arrayname) = " << sizeof(arrayname) << endl;//sizeof(arrayname) = 16
	cout << "sizeof(pointername) = " << sizeof(pointername) << endl;//sizeof(pointername) = 4
	cout << "sizeof(pas) = " << sizeof(pas) << endl;//sizeof(pas) = 4
	cout << "&arrayname = " << &arrayname << endl;//&arrayname = 008FFD74
	cout << "&arrayname + 1 = " << &arrayname + 1 << endl;//&arrayname + 1 = 008FFD84
	cout << "arrayname = " << arrayname << endl;//arrayname = 008FFD74
	cout << "arrayname + 1 = " << arrayname + 1 << endl;//arrayname + 1 = 008FFD78
	cout << "pas = " << pas << endl;//pas = 00CFFC94
	cout << "pas + 1 = " << pas + 1 << endl;//pas + 1 = 00CFFCA4
	/*
		11.指针与字符串
			在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量(const char *)都被解释为字符串第一个字符的地址。
			用括号括起的字符串常量被存储在某一位置,如果将其赋值给指针,则该指针指向该字符串常量,该指针可以获取字符串到但是不可以更改字符串
			不应该对没有new分配内存的char指针执行cin操作,也不应该尝试获取野指针所指向的内容。
			如果给cout提供一个char *,则cout默认打印字符串;如果需要显示改字符串地址,则应该强制类型转换char * 为int *
			在为char *指针使用new分配内存时,可以根据字符串的长度为其分配内存,但是记得要给'\0'留空间噢。
	*/
	cout << "指针与字符串*************************************************************" << endl;
	char var_char[5] = "Jasm";
	const char* p_varchar = "Booo";//不允许更改
	//根据字符串的长度为其分配内存
	char* p_new_var = new char[strlen("Jasm") + 1];//strlen()返回的是字符串的长度,不包含'\0'
	strcpy_s(p_new_var, 5, "Jasm");//使用深拷贝,如果是p_new_var = var_char的话是浅拷贝,则new的存储空间就用不上
	cout << "p_new_var = " << p_new_var << endl;//p_new_var = Jasm   ---  显示字符串
	cout << "p_new_var = " << (int*)p_new_var << endl;//p_new_var = 01140280   ---   显示地址
	delete[] p_new_var;
	/*
		12.指针new动态结构体
			创建结构体:见_7compound_types
			new结构体格式:inflatable * ps = new inflatable;//假设inflatable是我们创建的结构体
			使用指针访问new结构体的元素:箭头成员运算符(->),或者(*指针).成员。
	*/
	cout << "指针new动态结构体*************************************************************" << endl;
	struct person {
		char name[20];
		char id[20];
	};
	person* Jasmine = new person{ "Jasmine","622827131099" };
	cout << "Jasmine->name = " << Jasmine->name << endl;//Jasmine->name = Jasmine
	cout << "(*Jasmine).id = " << (*Jasmine).id << endl;//(*Jasmine).id = 622827131099
	/*
		13.指针数组、数组指针、指针的指针
			指针数组:由指针组成的数组
			数组指针:指向数组的指针
	*/
	cout << "指针数组、数组指针、指针的指针************************************************" << endl;
	person Booo = { "Booo","99999999" };
	person* p_Booo = &Booo;//这是一个数组指针
	person* p_person[3];//这是一个指针数组
	
	p_person[0] = &Booo;//数组指针的第一个元素指向了Booo
	p_person[1] = &Booo;//数组指针的第二个元素指向了Booo
	cout << "p_person[0]->name = " << p_person[0]->name << endl;//p_person[0]->name = Booo
	cout << "(*p_person)->id = " << (*p_person)->id << endl;//(*p_person)->id = 99999999
	person ** pp_person = p_person;//这是指向指针数组的指针
	person* (*p_p_person)[3] = &p_person;//这是指向 指针数组地址 的指针
	cout << "(**p_p_person)->name = " << (**p_p_person)->name << endl;//(**p_p_person)->name = Booo
	cout << "(*pp_person)->name = " << (*pp_person)->name << endl;//(*pp_person)->name = Booo
	cout << "(*(pp_person+1))->id = " << (*(pp_person + 1))->id << endl;//(*(pp_person+1))->id = 99999999

	return 0;
}

运行结果:

指针****************************************************************
sizeof(p_int) = 4
sizeof(p_char) = 4
love = 999999
p_int = 010FFD18
*p_int = 999999
new*******************************************************************
p_test[1] = 999
p_test = 01387280
p_test = 01387284
p_test[1] = 3
p_test = 01387280
p_test[1] = 999
p_double = 01384928
p_double = 01384930
p_double = 01384928
指针与数组*************************************************************
pointername = 010FFCC4
*(arrayname + 1) = 2
pointername = 010FFCC8
*(pointername + 1) = 4
(*pas)[2] = 3
sizeof(arrayname) = 16
sizeof(pointername) = 4
sizeof(pas) = 4
&arrayname = 010FFCC4
&arrayname + 1 = 010FFCD4
arrayname = 010FFCC4
arrayname + 1 = 010FFCC8
pas = 010FFCC4
pas + 1 = 010FFCD4
指针与字符串*************************************************************
p_new_var = Jasm
p_new_var = 01387248
指针new动态结构体*************************************************************
Jasmine->name = Jasmine
(*Jasmine).id = 622827131099
指针数组、数组指针、指针的指针************************************************
p_person[0]->name = Booo
(*p_person)->id = 99999999
(**p_p_person)->name = Booo
(*pp_person)->name = Booo
(*(pp_person+1))->id = 99999999

D:\Prj\_C++Self\_9pointer\Debug\_9pointer.exe (进程 17616)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

3.9 智能指针

由于程序员可能经常会忘记delete掉自己new的空间,由此导致的内存泄漏是很严重的问题,因此提出三种智能指针
auto_ptr, unique_ptr, shared_ptr:auto_ptr是C++98提出的,在C++11中取缔了。
智能指针是定义了模板类,该模板类可以的析构函数可以delete掉所指向的new的内存空间,以此达到精确管理内存的目的。
定义在memory头文件中,在std命名空间中。

3.9.1 智能指针模板类的定义

template<class X> class auto_ptr {
    public:
    explicit auto_ptr(X* p =0) throw();//throw()意味着不会引发异常
...};

3.9.2 智能指针的使用

auto_ptr<string> ps(new string); // ps an auto_ptr to string
// (use in place of string * ps)
unique_ptr<double> pdu(new double); // pdu an unique_ptr to double
shared_ptr<string> pss(new string); // pss a shared_ptr to string

注意事项

1.由于构造函数中explicit的限制,使用智能指针只能是显示初始化,因此不允许将指针赋值给智能指针
2.智能指针在通常情况下为指针的特点,可以使用*解除指针,也可以使用->访问结构体的元素,等等
3.不允许使用引用显式初始化指针

string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // NO!

3.9.3 智能指针分类

多个指针指向同一个内存空间释放内存时会引发异常,因此智能指针有对应的策略:
auto_ptr and for unique_ptr:使用了所有权的概念,只允许一个指针拥有一个对象的所有权,当且仅当智能指针拥有该对象的所有权它才可以使用析构函数的delete该对象,他们拥有赋值转让所有权。
unique_ptr更严格,因为他不会轻易失能上一任智能指针,如果程序尝试将一个unique_ptr转让给另一个,则编译器允许在源对象为临时右值时允许,如果源对象具有一定的持续时间,则不允许。
auto_ptr不允许对容器对象使用,unique_ptr允许对容器对象使用。
为什么unique_ptr可以分辨安全或不安全的使用?因为它使用了移动构造函数和右值引用。
shared_ptr:创建一个更智能的指针,用于跟踪有多少智能指针引用特定对象。这称为引用计数。
std::move():将unique_ptr赋值给另一个unique_ptr。
注意事项:
1.auto_ptr和shared_ptr智能指向new分配的内存空间,不能指向new []分配的内存空间;
2.unique_ptr可以使用new和new []分配的内存空间。
3.std::unique_ptr< double[]>pda(new double(5)); // will use delete []

3.9.4 选择一个智能指针

如果需要多个指针指向一个对象,则使用shared_ptr。
如果不需要多个指针指向一个对象,则使用unique_ptr。

3.9.5 unique_ptr和shared_ptr之间的赋值

在源是一个右值时,允许将unique_ptr赋值给shared_ptr;原因是shared_ptr模板中包含了一个将unique_ptr或右值转换为shared_ptr的显式构造函数,shared_ptr接管以前unique_ptr的对象所有权。

3.9.6 举例

代码:

// str1.cpp -- introducing the string class
/*
Project name :			_20Smart_pointers
Last modified Date:		2022年4月5日17点33分
Last Version:			V1.0
Descriptions:			智能指针
*/
#include <iostream>
#include <string>
#include <memory>
using namespace std;
unique_ptr<string> demo(const char* s);
unique_ptr<int> make_int(int n);
class Report
{
private:
	std::string str;
public:
	Report(const std::string s) : str(s)
	{
		std::cout << "Object created!\n";
	}
	~Report() { std::cout << "Object deleted!\n"; }
	void comment() const { std::cout << str << "\n"; }
};
int main()
{
	cout << "auto_ptr, unique_ptr, shared_ptr*****************************************************" << endl;
	{
		std::auto_ptr<Report> ps(new Report("using auto_ptr"));
		ps->comment(); // use -> to invoke a member function
	}
	{
		std::shared_ptr<Report> ps(new Report("using shared_ptr"));
		ps->comment();
	}
	{
		std::unique_ptr<Report> ps(new Report("using unique_ptr"));
		ps->comment();
	}
	cout << "多个指针指向统一内存空间,各智能指针的策略**************************************************" << endl;
	shared_ptr<string> films[5] =
	{
		unique_ptr<string>(new string("Fowl Balls")),
		unique_ptr<string>(new string("Duck Walks")),
		unique_ptr<string>(new string("Chicken Runs")),
		unique_ptr<string>(new string("Turkey Errors")),
		unique_ptr<string>(new string("Goose Eggs"))
	};
	//此处使用auto_ptr会引发错误
	//auto_ptr<string> pwin;
	shared_ptr<string> pwin;
	pwin = films[2]; // films[2] loses ownership
	cout << "The nominees for best avian baseball film are\n";
	for (int i = 0; i < 5; i++)
		cout << *films[i] << endl;
	cout << "The winner is " << *pwin << "!\n";
	//对于auto_ptr,#3是允许的,但是程序员可能不小心使用p1,但是p1对于该对象没有权限,会导致错误,这也是取缔auto_ptr的原因
	auto_ptr<string> p1(new string("auto")); //#1
	auto_ptr<string> p2; //#2
	p2 = p1; //#3
	//cout << *p1;//这句会出错

	//对于unique_ptr,就直接不允许#6,所以更安全
	unique_ptr<string> p3(new string("auto")); //#4
	unique_ptr<string> p4; //#5
	//p4 = p3; //#6---这句会出错
	//但是对于以下这种情况是允许的,因为临时智能指针temp在函数运行结束时会删除对象,不会导致auto_ptr以上出现的问题。
	unique_ptr<string> ps;
	ps = demo("Uniquely special");//allowed
	cout << *ps<<endl;//Uniquely special
	unique_ptr<string> pu3;
	pu3 = unique_ptr<string>(new string("Yo!")); //#2 allowed
	//std::move()
	unique_ptr<string> ps1, ps2;
	ps1 = demo("Uniquely special");
	ps2 = move(ps1); // enable assignment
	ps1 = demo(" and more");
	cout << *ps2 << *ps1 << endl;//Uniquely special and more

	unique_ptr<int> pup(make_int(rand() % 1000)); // ok
	//shared_ptr<int> spp(pup); // not allowed, pup an lvalue
	shared_ptr<int> spr(make_int(rand() % 1000)); // ok

	return 0;
}

unique_ptr<string> demo(const char* s)
{
	unique_ptr<string> temp(new string(s));
	return temp;
}
unique_ptr<int> make_int(int n)
{
	return unique_ptr<int>(new int(n));
}

运行结果:

auto_ptr, unique_ptr, shared_ptr*****************************************************
Object created!
using auto_ptr
Object deleted!
Object created!
using shared_ptr
Object deleted!
Object created!
using unique_ptr
Object deleted!
多个指针指向统一内存空间,各智能指针的策略**************************************************
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Chicken Runs
Turkey Errors
Goose Eggs
The winner is Chicken Runs!
Uniquely special
Uniquely special and more

D:\Prj\_C++Self\_20Smart_pointers\Debug\_20Smart_pointers.exe (进程 5256)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

README

此为本人读C++ Primer总结的笔记,如有错误或知识缺口,请在评论区告知。如本文有在实践中帮到您,是本人的荣幸。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jasmine-Lily

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值