您的位置:首页 > 编程语言 > C语言/C++

关于C语言中的数组指针、指针数组以及二级指针

2017-07-06 21:00 337 查看

概念解释

数组指针:首先它是一个指针,它指向一个数组,即指向数组的指针;在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称,即每个元素都是指针。
二级指针 : 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

实例解释

判断哪个为指针数组哪个为数组指针?

int *p1[10];
int (*p2)[10];


解析

“[]”的优先级比“”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int 修饰的是数组的内容,即数组的每个元素.因此这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组

“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。因此p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针



关于p2的定义问题

平时我们定义指针不都是在数据类型后面加上指针变量名么?这个指针p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义p2:
int (*)[10] p2;
int (*)[10]是指针类型,p2 是指针变量。这样看起来的确不错,不过就是样子有些别扭。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量p2 前移了而已。

利用指针遍历数组元素

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1, 3, 5, 7, 9};
int len = sizeof(arr) / sizeof(int);  //求数组长度
int i;
for(i=0; i<len; i++)
{
printf("%d  ", *(arr+i) );  //*(arr+i)等价于arr[i]
}
printf("\n");
return 0;
}


(arr+i)这个表达式,arr 是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。其中arr 是int*类型的指针,每次加 1 时它自身的值会增加 sizeof(int),加 i 时自身的值会增加 sizeof(int) * i

还可以如此表示

int arr[] = { 1, 3, 5, 7, 9};
int *p = arr;


arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

利用数组指针遍历数组

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1, 3, 5, 7, 9};
int len = sizeof(arr) / sizeof(int);  //求数组长度
int i, *p = arr;
for(i=0; i<len; i++)
{
printf("%d  ", *(p+i) );
}
printf("\n");
return 0;
}


数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用sizeof(p) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。

根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。

对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的。如果一个指针变量 p 指向了数组的开头,那么 p+i 就指向数组的第 i 个元素;如果 p 指向了数组的第 n 个元素,那么 p+i 就是指向第 n+i 个元素;而不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素

更改上面的代码,让 p 指向数组中的第二个元素:

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1, 3, 5, 7, 9};
int *p = &arr[2];  //也可以写作 int *p = arr + 2;
printf("%d, %d, %d, %d, %d\n", *(p-2), *(p-1), *p, *(p+1), *(p+2) );
return 0;
}


会发现结果和上面的一致

总结

引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。
1. 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
2. 使用指针
也就是使用 (p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 (arr+i) 来访问数组元素,它等价于 *(p+i)。
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

借助自增运算符来遍历数组元素

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1, 3, 5, 7, 9};
int i, *p = arr, len = sizeof(arr) / sizeof(int);
for(i=0; i<len; i++)
{
printf("%d  ", *p++ );
}
printf("\n");
return 0;
}


解释

p++ 应该理解为 (p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的

关于数组指针的几个问题

假设 p 是指向数组 arr 中第 n 个元素的指针,那么 p++、++p、(*p)++ 分别是什么意思呢?
1. *p++上面已经叙述
2. ++p 等价于 (++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值
3. (*p)++ 会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 1,执行完该语句后,第 0 个元素的值就会变为 2

实例中的指针数组和二级指针

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
int a = 1, b = 2, c = 3;
//定义一个指针数组
int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]
//定义一个指向指针数组的指针,即二级指针
int **parr = arr;
printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
return 0;
}


arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组很类似。

parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int (*parr),括号中的表示 parr 是一个指针,括号外面的int 表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int ,所以在定义 parr 时要加两个 *,即可称parr为二级指针,或者指向指针的指针

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
char *lines[5] =
{
"COSC1283/1984",
"Programming",
"Techniques",
"is",
"great fun"
};
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("  c1 = %c\n", c1);
printf("  c2 = %c\n", c2);
printf("  c3 = %c\n", c3);
return 0;
}


运行结果

str1 = Programming
str2 = is
c1 = f
c2 = 9
c3 = E


为了更加直观,将上述代码改成下面的形式

#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
char *string0 = "COSC1283/1984";
char *string1 = "Programming";
char *string2 = "Techniques";
char *string3 = "is";
char *string4 = "great fun";

char *lines[5];
lines[0] = string0;
lines[1] = string1;
lines[2] = string2;
lines[3] = string3;
lines[4] = string4;
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("  c1 = %c\n", c1);
printf("  c2 = %c\n", c2);
printf("  c3 = %c\n", c3);
return 0;
}


1. char *lines[5]; 定义了一个指针数组,数组的每一个元素都是指向char类型的指针。最后5行,为数组的每一个元素赋值,都是直接赋给指针。
2. 而lines,是一个指向指针的指针,它的类型为 char **,所以 *lines 是一个指向字符的指针,**lines是一个具体的字符。这一点很重要,一定要明白。
3. 指针是可以进行运算的,lines 为lines[5]数组的首地址,即第0个元素的地址;lines+0, lines+1, lines+2 ... 分别是第0, 1, 2 ...个元素的首地址,*(lines+0)或lines[0], *(lines+1)或lines[1], *(lines+2)或lines[2] ... 分别是字符串 str0, str1, str2 ... 的首地址。所以:
*lines == *(lines+0) == lines[0] == str0
*(lines+1) == lines[1] == str1
*(lines+2) == lines[2] == str2


注意
lines为指向指针的指针,所以* (lines+n)为指针,**(lines+n)才为具体的字符。

解析

1. lines[1]:它是一个指针,指向字符串string1,即string1的首地址。
2. *(lines + 3):lines + 3 为lines[5]数组第3个元素的地址,  *(lines + 3)为第3个元素,它是一个指针,指向字符串string3。
3. *(*(lines + 4) + 6):*(lines + 4) + 6 == lines[4] + 6 == string4 + 6,为字符串string4第6个字符的地址,即 f 的地址,*(*(lines + 4) + 6) 就表示字符 f。
4. (*lines + 5)[5]:*lines + 5 为字符串 string0 第5个字符的地址,即 2 的地址,(*lines + 5)[5]等价于*(*lines + 5 + 5),表示第10个字符,即9。
5. *lines[0] + 2:*lines[0] 为字符串string0 第0个字符的地址,即C的地址。字符与整数运算,首先转换为该字符对应的ASCII码值,然后再运算,所以 *lines[0] + 2 = 67 + 2 = 69。不过要求输出字符,所以还要转换成69所对应的字符,即E。


1

2

3

4

5

1

2

3

4

5

输入5个国名并按字母顺序排列后输出

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

void sort(char *name[],int n)
{
char *pt;
int i,j,k;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0)
k=j;
if(k!=i)
{
pt=name[i];
name[i]=name[k];
name[k]=pt;
}
}
}

void print(char *name[],int n)
{
int i;
for (i=0;i<n;i++)
printf("%s\n",name[i]);
}

int main()
{
static char *name[]={ "CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};
int n=5;
sort(name,n);
print(name,n);
return 0;
}


说明:
1. 在以前的例子中采用了普通的排序方法,逐个比较之后交换字符串的位置。交换字符串的物理位置是通过字符串复制函数完成的。反复的交换将使程序执行的速度很慢,同时由于各字符串(国名)的长度不同,又增加了存储管理的负担。用指针数组能很好地解决这些问题。把所有的字符串存放在一个数组中,把这些字符数组的首地址放在一个指针数组中,当需要交换两个字符串时,只须交换指针数组相应两元素的内容(地址)即可,而不必交换字符串本身。
2. 本程序定义了两个函数,一个名为sort完成排序,其形参为指针数组name,即为待排序的各字符串数组的指针。形参n为字符串的个数。另一个函数名为print,用于排序后字符串的输出,其形参与sort的形参相同。主函数main中,定义了指针数组name 并作了初始化赋值。然后分别调用sort函数和print函数完成排序和输出。值得说明的是在sort函数中,对两个字符串比较,采用了strcmp函数,strcmp函数允许参与比较的字符串以指针方式出现。name[k]和name[j]均为指针,因此是合法的。字符串比较后需要交换时,只交换指针数组元素的值,而不交换具体的字符串,这样将大大减少时间的开销,提高了运行效率。
3. 这题用algorithm中的sort()也可以很好的解决。

原:http://blog.csdn.net/u014265347/article/details/54882661
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: