指针与数组的知识小结

他们之间看似简单实则暗藏玄机!来看实践!

#include<iostream>
using namespace std;
int main() {

	//指针知识小结:  指针提供了一种间接访问数据的方式
	//1、指针的定义
	int i = 100;
	int* p = &i;       //定义一个指向int类型对象的指针对象p,把i的地址放到p中,也就是说p指向了i
	cout << *p << endl;   //解引用操作符*,访问i的内容,输出为i的值
	cout << p << endl;    //输出内容为i的地址

	///注意点:(1)若 * 或 & 紧跟在类型说明后,则定义的对象为指针或引用;int *p=&i;int &ref=*p; 
	//若出现在表达式中,则为解引用或取址符
	///(2)定义指针对象时指针要和所指对象类型一致,void和基类指针除外;
	//int i=0;
	//double *p=i; 错误,类型不一致
	///(3)定义多个指针时,对象名前面都要加上 * 号;
	//int i, *p, *r;
	///(4)定义指针时,若没有具体指向对象,则需要用 nullptr 来初始化;若一个未初始化的指针在一个语句块内部进行定义,则它存放的为一个随机值
	int* ptr = nullptr; //空指针,没有指向任何对象
	int* ptr1;    //野指针,有潜在危险

	//2、改变指向
	int a = 10, j = 100;
	int* p1 = &a, * p2 = &j;
	p1 = p2;   //改变p2的指向,也就是改变其地址
	p1 = nullptr;  //改变p1的指向,将其变成空指针
	//注意:指针是可以加减来改变地址的,++p1;与p1=pi+1相同,增加一个地址单位(int为4byte),也叫指针移动,后面会详细讨论指针和数组之间的应用



	//3、const与指针的使用
	//(1)const可以修饰一个指针对象,使其成为一个指向const对象的指针,表明不能通过指针来修改对象的值(定义一个常量指针,* 号在const后)
	const int ci = 10, cj = 1;
	const int* p3 = &ci;
	//*p3 = 0;  不能通过指针来改变对象的值
	p3 = &cj;  //但可改变指针的指向(也就是地址)
	int r = 0;
	p3 = &r;
	//   int* p3 = &ci;   一个普通指针不能指向const对象,常量地址只能赋给常量指针

	//(2)定义一个指针常量,指向不能改变,但可通过指针来改变对象的值(* 号在const前)
	int* const cptr = &i;
	// cptr = &j;   不能改变指向
	*cptr = 10;   //但可改变对象的值

	///(3)定义一个指向常量的指针常量(也就是指向和指针都不能改变,使用双const)
	const int* const cp = &i;

	//注:不要将指针常量和常量指针搞混了,记忆法: * 在前为指针常量(指向不能改),const在前为常量指针(值不能改)
	//  * 号在中间,const在两边,则为指向常量的指针常量(指向,值都不能改)


	//4、类型推导
	//(1)如果表达式的值是地址值,则auto可用来推导指针类型。auto可根据表达式的值推导出用户想要定义的数据类型,并用表达式的值初始化定义的对象
	int u = 0;
	const int ci1 = 100;
	//auto p5 = &u;
	auto p5 = &ci1; //光标放p处可查看其类型,auto将自动推导出指针类型
	//注意:& 和 * 从属于对象名,而不是类型名的一部分,auto只是一个占位符
	auto& re = u, * p6 = &u;  //此处推导都为int型
	//auto& r = i, p = &i;  //此处显示有错误前后两个对象的类型不一致,不可用auto进行推导,可分开定义或像上面的方法加上*

	//(2)用decltype进行指针类型推导(只想用表达式的类型而不想用其值来定义对象的关键字)  它会分析()内表达式或对象的类型来推导出后面对象的类型,但不会执行运算

	int h = 0, * p7 = &h;
	decltype (p7)pr1;  //推导出pr1为int * 类型
	decltype (*p7)ref = h;  //ref为int & 类型,必须要初始化
	decltype (*p7 + 1)s;  //s为int类型

	//5、void指针
	// void指针是一类特殊指针,它可以指向任何类型的对象;并且它只是简单的将对象地址储存起来,但对对象的类型并不感兴趣
	double x = 1.0;
	int y = 20;
	void* p4 = &y;
	p4 = &y;    //储存对象类型不限,可以存放其地址
	//注:不能将void随意赋给一个普通指针,需要保证他们指向的类型要相同
	double* ptrd = &x;
	void* ptr2 = &x;
	//ptrd = ptr; 此处显示类型不同无法转换,要借用强制类型转换(显性类型转换)使用static_cast 来执行操作
	ptrd = static_cast<double*>(ptr2); //强制转换为double * 类型,当然两个指针所指的对象必须一致,不然会出现运行错误

	//补充小结static_cast:
	//(1)可执行浮点数操作
	int i1 = 5, j1 = 3;
	double k = i1 / static_cast<double>(j1);  //强制将j1转化为double类型
	//(2)有意将宽类型转化成窄类型
	int f = static_cast<int>(i1 / j1);    //将i/j的结果转化为int类型
	//(3)const_cast常用来去掉对象的const属性,把一个const对象转化为非const对象,一般不提倡用这种方法。

	//6、多级指针
	//以二级指针为例,把一个指针对象的地址储存到另一个指针对象里,就成了指向指针的指针,这就为二级指针。
	int i2 = 68, * ptrr = &i;
	int** pptr = &ptrr;
	cout << i << '\t' << *ptrr << '\t' << **pptr << '\n';   //以上方式均可访问i的值
	//三级指针***,多级指针依次可推。


	//数组小结:
	//数组是有限个同类型元素的有序集合,并且所以元素都顺序存放在一段连续的空间内。
	//1、数组的定义和初始化
	int arr[5] = { 1,2,3,4,5 };  //定义一个含五个int类型的元素的数组  (直接定义)  int arr[5]={0};或int arr[5];
	const int sa = 10;  //或者 constexpr int sa=10;
	int ard[sa];
	short things[] = { 1,3,5,7 };
	//注意:(1)sa必须是一个常量,不可以为变量,所以要用const或constexper;并且数组长度(下标)必须为整型。
	//(2)数组下标为常量,元素下标可为变量,ard[i],ard[2]。
	//(3)数组元素从[0]开始,到[sa-1]结束,不包括ard[sa]。
	int arr1[] = { 1,2,3 };
	//(4)定义时可不用写下标,系统会自动推导元素个数,未给出的元素默认为0。
	//(5)只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组。
	//(6)数组名就可代表数组的首地址(一种映射)。
	//补充C++11的数组初始化方法
	//(1)初始化时,可以省略等号
	short thing[10]{};
	int counts[]{ 0,1,2,3,4 };
	//(2)列表初始化禁止缩窄转换
	long plifs[] = { 25,92,3.2 };  //这条语句是不能通过编译的,将浮点数转换为整型是缩窄操作
	char slifs[4]{ 'h','i',1122011,'\0' };  //这句也是不能通过编译的,1122011超出了char变量的取值范围,只能打印出前两个字符
	char tlifs[4]{ 'h','i',112,'\0' };   //可以通过编译,112在char的范围内
	cout << '\t' << tlifs << endl;  //输出为 h i p(112)



	//2、复杂数组的定义
	int* arrp[5];   //定义一个含5个int类型元素的数组,每个元素都是指针(都是int * 类型)
	int(*parr)[5] = &arr;   //定义一个指向含5个int类型元素的数组的指针;parr的初始值为arr的地址(为第一个元素的首地址)
	int(&rarr)[5] = arr;    //定义arr的一个引用
	int* (*parrp)[5] = &arrp;   //parrp为指向指针数组arrp的指针;arrp的类型就为int * 所以前面要用int *
	int* (&rarrp)[5] = arrp;    //rarrp为指针数组arrp的引用

	//3、访问数组元素(一维)
	//(1)可以通过下标操作符[]来访问数组元素
	arr[0] = 10;   //对第一个元素进行写操作
	cout << arr[0] << '\t' << arr[2] << endl;  //读操作,输出第一个元素和第三个元素
	//(2)由于数组元素数量是确定的,所以也经常会使用for语句来访问数组元素,传统的for循环语句可以实现,
	//C++11中进入了范围for(range for)语句,其用法更为简洁和快捷,可用来遍历数组或其他数列的所以元素
	// for (decl : expr) {     exper必须为一个对象序列,数组,容器(vector)或字符串(string),
	//		statement;         deal是与对象序列中元素相同的对象,一般用auto来自动推导数据元素类型       
	//}                        statement为执行语句
	for (auto i : arr) {
		cout << i << endl;  //i为arr中当前元素的副本,注意这里只是对arr进行了读操作,i不能对其元素进行写操作
	}
	//若要对其进行写操作,就要将i声明为引用
	for (auto& i : arr) {
		i = 'a';        //写操作,将每个元素都变为97
		cout << '\t' << i << '\n';
	}


	//4、多维数组(指数组中的元素类型都是数组类型)
	int a2d[3][5];   //定义一个二维数组,可看做三行五列,但他们还是在一个空间内连续储存
	int a3d[2][3][5];   //定义一个三维数组
	//注意:多维数组中最左边的对应最高维(第一维,第二维....),二维数组可对应矩阵(第一维为行,第二维为列)
	//(1)多维数组的初始化(依然可以使用列表方式来初始化)
	int a4d[3][5] = {
		{0,1,2,3,4},
		{3,4,5,6,7},
		{2,1,7,9,3} };  //也可省去内部的{},直接打出元素(按顺序) 直接初始化
	int a5d[3][5] = { 0,1,2 };   //显式初始化部分数组元素,只初始化了第一维中的前三个元素,则其他元素默认为0
	int arrd[][5] = { {0},{1},{2} };    //显式初始化每一维的第一个元素,也就是每一行中的第一个元素
	//注:第一维的下标可以不写,但要明确(在后面的{}中)说明维数和每维中元素的个数(未给默认0),才能自动推导出下标;且仅第一维的下标可以省略
	//例:a5d中第一个下标不能省略,若省略则前面自动推出是[1],而不是[3]

	//(2)访问多维数组元素
	//<1>依然可以像一维一样用下标来访问元素
	cout << a4d[2][1] << endl;  //访问第二行中的第二个元素
	//<2>利用范围for语句来处理多维数组,注意:除最内层的循环外,其他各层循环中必须使用引用
	for (auto& r : a4d) {       //r被推导为int (&)[5]类型
		for (auto c : r) {      //r为一维数组的引用,r为列表类型才能用于范围for语句中,所以外层都要用引用
			cout << '\t' << c << endl;
		}
	}
	//注意:for语句的用法要正确,后面对象要为列表类型;中间为 : 号,不是 ; 号。


	//5、指针与数组
	//指针指向数组一般都是指向第一个元素的地址;并且数组名通常会转化为第一个元素的地址,是个右值
	//(1)指针指向一维数组
	int ars[]{ 1,2,3,4 };
	int* ppr = ars;   //此处指针指向数组时,不需要用&,数组名会转化为第一个元素的地址
	//上述式子也等价于 int *ppr=&arr[0];
	cout << ars << '\t' << &ars[0];   //这两个输出结果都是第一个元素的地址,也可得出一个小结论:& 与 [] 有相互抵消的作用
	// 例:二维数组中,arr2[0]与&arr2[0][0]是相同的(也就是说第一个元素的地址就代表第一行的地址)
	auto pa = ars; //可用auto来自动推导出pa是一个int *类型的指针,指向arr[0]
	auto pi = &ars; //若在数组前加上 & ,则推导出来pi是一个int (*pi)[4]类型,为一个指向含有4个整型元素的数组的指针
	auto po = *ars;  //此时po的类型就为int,* 用在表达式中为解引用,*ars就为int ars[4]类型,准确来说,ars代表着第一个元素的地址,所以*ars=ars[0]
	cout << '\n' << po << endl;  //输出内容为ars[0]=1
	
	//(2)指针指向二维数组
	int atd[3][4] = { {3},{0} };
	int(*prt)[4] = atd;  //指向atd的第一个元素,注意:此处和一维数组定义是不同,一维定义需在atd前加上 & 号,但二维却不用;因为一维不加的话类型不匹配
	//上述语句也等价于 int (*prt)[4]=&atd[0];  第一个元素的地址
	//注:上面语句中的()不可缺少,若缺少,则会变为另一种定义
	int* prt1[4] ;  //这时prt1就为一个含有4个指向整型对象的指针类型的元素的数组
	//注:一个数组名可以理解为一个const指针,但两者并不完全等价
	int* const pl = atd[0]; //二维数组中,其实就是指向atd中的第一个元素
	int* const pk = &ars[0]; //一维数组中 所以arr也可以理解为const指针pk,此指针输出为第一个元素
	cout << *pl << '\t' << *pk << endl;   //输出各数组中的第一个元素

	//(3)指针访问数组
	//<1>访问一维数组   当一个指针和一个数组关联起来时,就可以通过指针来访问数组了
	int arr2[] = { 1,2,3,6,5 };
	int* pn = arr2;
	//注:若想用一个指针指向一个数组,则不需要用 & 号,直接写数组名就可;若指向单个元素或单个数时,要加上&
	///此处引入指针运算
	//[1]指针的移动
	int* pn1 = pn + 2;  //将pn指针的地址向后推两个单位,再赋值给pn1指针,此时pn1指针指向arr2[2],即第三个元素
	int* pn2 = pn++;    //注意++的位置,此时先把pn的地址赋给pn2,再将其向后推一个单位,此时pn指向arr2[1]
	int* pn3 = ++pn;    //此时pn继续在向后移一个位置,pn3和pn均指向arr2[2](上面的语句执行后pn已经向后移了一个位置,注意不要忽略)
	//[2]指针的关系运算  (支持所有关系运算符)
	//例:p==p2,p>p2,p3<=p;
	//[3]指针相减
	//两个指针相减的结果为所指数组元素的位置距离,例:pn3-pn2的结果为2
	///注意点:(1)指针在运算时,下标不能越界
	pn =&arr2[0];
	pn = &arr2[5];  //指向尾元素后面的一个位置,也就是pn=arr2+5; 虽然不存在arr2[5]这个元素,但仍可计算出该位置的地址
	//(2)仅当两个指针指向同一个数组时,它们之间的运算才有意义

	//  访问实践
	int val = *(pn + 2) + 1;   //此语句等价于:int val=arr2[2]+1;
	int val2 = pn[1];    //可将下标应用于指针  int val2=arr2[2];
	cout << '\n' << val << '\t' << val2<<endl;

	//<2>访问二维数组
	//[1]可以利用指针访问一维数组类似的方法,来访问二维数组
	int aed[3][5] = {0};
	int(*ped)[5] = aed;  //定义一个指向二维数组aed的第一个元素  
	ped[1][1] = 1;
	*(*(ped + 1) + 1) = 1; //上述两个语句表达的意思相同,把第二行第二个元素赋值为1,下面的语句从里往外看
	//(ped+1)代表移动一行,此时指针地址变为第二行第一个元素,*(ped+1)+1 代表第二行第二个元素的地址,*(*(ped+1)+1)代表第二行第二个元素
	cout << ped << '\t' << *ped << '\t' << (ped + 1) << '\t' << *(ped + 1) << '\t' << (*(ped + 1) + 1) << '\t' << *(*(ped + 1) + 1) << endl;
	//注意:输出内容,ped和*ped都为第一行第一个元素地址;(ped+1)和*(ped+1)均为第二行第一个元素的地址;(*(ped+1)+1)为第二行第二个元素的地址,*(*(ped + 1) + 1)为第二行第二个元素的值
	//上述有的 加* ,有的不加* ,却输出一样,这和定义的指针数组有关,要注意和其他情况区分
	
	//[2]可用auto和for语句来访问数组
	int a22d[3][5]{ {1},{2},{3} };
	for (auto p = a22d; p < a22d + 3; ++p) {
		for (auto q = *p; q < *p + 5; ++q) {
			cout << *q;
		}
		cout << endl;
	}
	//同时还可利用数组元素的连续性来访问
	for (auto p = &a22d[0][0]; p < a22d[0] + 15; ++p) {
		if ((p - a22d[0]) % 5 == 0) {
			cout << endl;
		}
		cout << *p;
	}


	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值