【c/c++】指针数组和数组指针

实例放在最上面的,这个能全部看懂,下面的知识也就不要再往下看的。如果不行,那么就好好看下去然后回来温习这个实例

#include<iostream>
#include<string.h>
using namespace std;

/*
char * cp = "hello world";
cout << cp << endl;  //输出“hello world”
cout << *cp << endl;  //输出h单个字符
这个结果和之前char a[10],直接是cout<<a,结果是整个字符数组.但是别的类型的数组,其结果是首地址。
*/
char *max(char *ps[], int n);
int main(){
	char *s[5] = { "aaa", "bbb", "ccc", "ddd", "ccs" };
	char *p;
	char(*q)[3];
	p = max(s, 5);
	//q = max(s,5);这样的出错原因是指针指向的类型不一样,前者是数组,后者是字符,因此出错
	//*p的时候只出现一个值d,p的时候结果是ddd;要了解原因
	//首先看到指针p定义最后指向的是一个字符,因此*p最后的结果肯定是单个字符,不是我们想要字符串
	cout << "the max string:" << *p << endl;
	cout << "the max string:" << p << endl;

	return 0;
}

char * max(char *ps[], int n){
	char *q;
	//这样会报错,说不能将char**赋值给char*,也就是说指针数组的数组名不能像正常数组那样进行赋值,而是要用数组的第一个元素吗?
	//这里终于明白了为什么ps不可以,而ps[0]就可以了。虽然ps是数组名可以代表首地址,但是你想想ps指向的数据类型是数组,而ps指向的数据类型是指针
	//这样当然是不可以赋值的,指针指向的类型的不同就不同进行赋值操作
	//那为什么ps[0]就可以呢,它不是一维数组吗,p=&a[0].那这里为什么不加取地址符啊,你看看之前a[0]存储的是数据,而这里的ps[0]里面
	//存储是指针,也就是说ps[0]也是指针变量,最后指向的类型也是相同的,因此是可以赋值的。
	q = ps[0];
	for (int i = 0; i < n; i++){
		//看strcmp的源代码定义:int     __cdecl strcmp(_In_z_ const char * _Str1, _In_z_ const char * _Str2);
		if (strcmp(q, ps[i]) < 0)
			q = ps[i];
	}
	return q;
}



1、指针数组和数组指针的内存布局

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。


下面到底哪个是数组指针,哪个是指针数组呢:
A)
int *p1[10];
B)
int (*p2)[10];
这里需要明白一个符号之间的优先级问题。

“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:

2.论a 和&a 之间的区别

既然这样,那问题就来了。前面我们讲过a 和&a 之间的区别,现在再来看看下面的代码:
int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[5] = &a;
   char (*p4)[5] = a;
   return 0;
}
上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。 &a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:
   warning C4047: 'initializing' : 'char (*)[5]' differs in levels of indirection from 'char *'。
还好,这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。但是在最新的c++14中已经禁止这样写代码了,因此下面例子中关于p4+1这种情况就就不分析了

既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。
示例:

#include <iostream>
using namespace std;

int main() {
	char a[5] = { 'A', 'B', 'C', 'D' };
	char(*p3)[10] = &a;
	// char (*p4)[5] = a;
	cout << a << endl;
	cout << &a << endl;
	cout << p3 << endl;
	cout << p3 + 1 << endl;
	//   输出结果:ABCD
	// 0xbfdf79fb
	// 0xbfdf79fb
	// 0xbfdf7a00  加一也就说加一整个字符数组的长度,即5个字节
	//现在疑惑点来了,是用cout输出数组名的时候 居然能够直接输出数组中的内容而不是其首地址
	//解答:
	//字符数组是可以用这种方式输出的, 因为数组名即代表首地址, 数组是一块连续的内存空间, 所以能直接输出.
	//如果是其他类型的数组则输出的是一个地址.
	int b[2] = {1,12};
	cout<<b<<endl;//这个时候输出的还真的是一个地址

	return 0;
}

这里有一个问题就是将char(*p3)[5]中的5改成别的数字可以吗?答案是肯定的,结果是程序出错,不允许运行

3.地址将强制类型转换

struct Test
{
   int Num;
   char *pcName;
   short sDate;
   char cha[2];
   short sBa[4];
}*p;


假设p 的值为0x100000。如下表表达式的值分别为多少?
   p + 0x1 = 0x___ ?
   (unsigned long)p + 0x1 = 0x___?
   (unsigned int*)p + 0x1 = 0x___?
我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?


还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。 指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。这个就好比地址对数字的加减运算是和数据类型是相关联的,只不过现在是换成结构体了
至于此结构体的大小为20byte,前面的章节已经详细讲解过。有边界补充,因此按照最大存储的字节乘以数量
所以p +0x1 的值为:0x100014。


(unsigned long)p + 0x1 的值呢? 这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。


(unsigned int*)p + 0x1 的值呢? 这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。当指针指向的数据类型发生变化,那么其关联数据类型基本单位存储大小也已经发生改变,因此,这里我们计算的基本单位的存储单元大小是要发生变化的。

小结:
首先去看指针指向数据的存储大小是多少,接着看地址要移动的数据大小是多少。然后相乘的结果加上原先的地址就是最后的结果。当然如果像中间的数据变换了,例如上面将其变为长整型的数据,那么你加减的结果就是常规的数据加减了


上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?
intmain()
{
   int a[4]={1,2,3,4};
   int *ptr1=(int *)(&a+1);
   int *ptr2=(int *)((int)a+1);
   printf("%x,%x",ptr1[-1],*ptr2);
   return 0;
}
如果懂汇编,这道题目很简单
根据上面的讲解,&a+1 与a+1 的区别已经清楚。


ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。


其内存布局如下图:


好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到 系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。
int checkSystem( )
{
   union check
   {
      int i;
     char ch;
   } c;
   c.i = 1;
   return (c.ch ==1);
}
如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值