------指针
目录
一.初识指针:
1.基础指针知识归纳:
-
指针就是地址,地址就是指针。(举例:一把钥匙对应一个房间,一个房间对应一把钥匙)
-
指针变量就是存放地址的变量。
-
如果一个指针变量指向一个普通变量,则 *指针变量 就完全等普通变量。
-
地址就是内存单元的编号。
-
指针是一个操作受限的非负整数(只能减,不能加、乘除)
所以指针的值就是某一个变量的内存地址,指针变量就是用来存放某个变量的内存地址的变量,和广义的变量没有什么区别。
2.如何定义和使用指针:
2.1指针变量的定义和使用:
定义格式:
类型 *指针变量名 指针的格式字符串%p:表示输出一个变量对应得内存地址编号,通常是十六进制的形式
例:
#include <stdio.h>
int main()
{
int a = 2, b = 4, c;
int *p1, *p2, *p3;
p1 = &a;//看总结第1点,地址就是指针,指针就是地址,所以a的地址&a与指针p1等价,所以我们让指针p1指向&a
p2 = &b;//同理
p3 = &c;
*p3 = 6;//看总结第3点,如果一个指针变量指向一个普通变量,则 *指针变量 就完全等普通变量
printf("a = %d\tb = %d\n", a, b);//正常输出
printf("*p1 = %d\t*p2 = %d\n", *p1, *p2);//看总结第3点,如果一个指针变量指向一个普通变量,则 *指针变量 就完全等普通变量
//在输出时我们也可以认为*p1就是取值,将指针p1指向的变量的值取出来
printf("*p3 = %d\n", *p3);
return 0;
}
总结:如何使用指针变量呢?第一,定义;第二,指向;第三,使用。
2.2实例练习:
输入a,b的值,从小到大输出。
#include <stdio.h>
int main()
{
int a, b, temp;
int *p1, *p2; //定义
scanf("%d %d", &a, &b);
p1 = &a; //引用
p2 = &b; //引用
if(a > b) //等价于if(*p1 > *p2)
{
//把指针当作普通变量使用
temp = *p1; //使用
*p1 = *p2;
*p2 = temp;
}
printf("%d %d\n", *p1, *p2);
return 0;
}
#include <stdio.h>
int main()
{
int a, b, temp;
int *p1, *p2, *p3; //定义
scanf("%d %d", &a, &b);
p1 = &a; //引用
p2 = &b; //引用
if(*p1 > *p2)
{
//正常使用指针
p3 = p1;//使用
p1 = p2;
p2 = p3;
}
printf("%d %d\n", *p1, *p2);
return 0;
}
3.通过指针引用数组:
3.1讲解:
在第五次培训中我们学习了数组,现在我们需要对数组有进一步的了解;
在初学C语言时,我们可以认为数组就是指针,指针就是数组,数组是一段连续的空间的地址,而指针就是地址。所以两者可以等价。
首先我们需要了解一个概念:
#include <stdio.h>
int main()
{
int *p1, *p2;
int a[3] = {1, 2, 3};
p1 = &a[0];
p2 = a;
printf("%d %p\n", *p1, p1);
printf("%d %p\n", *p2, p2);
return 0;
}
数组a[0]就为数组的首地址,也就是我们的数组名
然后我们便可以进一步了解数组就是指针,指针就是数组:
#include <stdio.h>
int main()
{
int a[5] = {168, -2, 0, 4, -500};
for(int i = 0; i < 5; i++)
{
printf("%-5d", *(a+i)/*a[i]*/);//我们认为数组就是指针,即a[0]就为数组的首地址,也就是我们的数组名
}
putchar('\n');
for(int i = 0; i < 5; i++)
{
printf("%-5d", a[i]/*(*(a+i))*/);
}
putchar('\n');
//编译器为数组a分配了一段连续的5个空间,每个空间对应一个地址编号
printf("%p\n", a[0]);
printf("%p\n", a[1]);
printf("%p\n", a[2]);
printf("%p\n", a[3]);
printf("%p\n", a[4]);
return 0;
}
/*
代码输出:
168 -2 0 4 -500
168 -2 0 4 -500
00000000000000A8
00000000FFFFFFFE
0000000000000000
0000000000000004
00000000FFFFFE0C
*/
代码中我们可以看到,我们认为*(a+i)就是指针,并且随着变量i的增加,数组的地址也在增加,也就是说a是一个地址,+i是一个地址偏移量, a+i也是一个地址。所以*(a+i)就是取a+i这个地址里面的值。同时我们可以可以看到,在两个for循环中*(a+i)与a[i]等价,所以这里引出一个概念:
我们已经强调过,初学C语言时,数组就是指针,指针就是数组,所以我们可以这样理解:
因为*(a+i)与a[i]等价,所以我们通过简单的加法结合律,就可以变形得到:
*(a + i) = a[i]; *(i + a) = a[i]; *(a + i) = i[a]; *(i + a) = i[a];
大家可以自己试一试,敲一敲代码,看看结果是否全部一样。
3.2实例练习:
1.通过引用指针修改数组a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}里的奇数项为n的平方。
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p;
p = a;//等价于p = &a[0]
for(int i = 0; i < 10; i++)
{
if((i+1) % 2 != 0)
{
/*(p[i])*/*(p+i) = (i+1)*(i+1);//其实等价于*(a+i) = (i+1)*(i+1);
}
}
for(int i = 0; i < 10; i++)
{
printf("%-3d", *(a+i));//a[i]
}
return 0;
}
/*效率更高
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p;
for(p = a; p < a+10; p++)
{
if(*p % 2 != 0)
{
*p = (*p)*(*p);//其实等价于*(a+i) = (i+1)*(i+1);
}
}
for(p = a; p < a+10; p++)
{
printf("%-3d", *p);
}
return 0;
}
*/
/*代码输出:
1 2 9 4 25 6 49 8 81 10
*/
2.写一个函数,将上一题的数组作为参数,将每项变成它的阶乘。
#include <stdio.h>
#include <string.h>
int factorial(int n)
{
int result;
if(n < 0)
{
result = -1;
}
if(n == 1 || n==0)
result = 1;
else
{
result = n*factorial(n-1);
}
return result;
}
void fac(int a[], int n)
{
int *p;
p = a;
for(int i = 0; i < n; i++)
{
/*(p[i])*/*(p+i) = factorial(*(p+i));//其实等价于*(a+i) = factorial(*(a+i));
}
for(int i = 0; i < n; i++)
{
printf("%d\t", *(p+i));
}
}
/*效率更高
void fac(int a[], int n)
{
int *p;
for(p = a; p < a + n; p++)
{
*p = factorial(*p);//其实等价于*(a+i) = factorial(*(a+i));
}
for(p = a; p < a + n; p++)
{
printf("%d\t", *p);
}
}
*/
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
fac(a, 10);
return 0;
}
/*代码输出:
1 2 6 24 120 720 5040 40320 362880 3628800
*/
4.通过指针引用字符串
4.1回顾:
在 C 程序中,字符串是存放在字符数组中的。想引用一个字符串,可以用以下两种方法。
(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明 “ %s ” 输出该字符串。
举例:定义一个字符数组,在其中存放字符串 " I love China! " ,输出该字符串和第8个字符。
#include <stdio.h>
int main()
{
char string[] = "1 love China!"; //定义字符数组string
printf("%s\n", string); //用%s格式声明输出string,可以输出整个字符串
printf("%c\n", string[7]); //用%c格式输出一个字符数组元素
return 0;
}
在定义字符数组 string 时未指定长度,由于对它初始化,因此它的长度是确定的,长度应为14,其中 13 个字节存放 " l love China! " 13个字符,最后一个字节存放字符串结束符 '\0'。数组名 string 代表字符数组首元素的地址。题目要求输出该字符串第 8 个字符,由于数组元素的序号从 0 起算,所以应当输出 string[7],它代表数组中序号 7 的元素的值(它的值是字母 C )。实际上 string[7] 就是 *(string+7),string+7 是一个地址,它指向字符 “ C ”。
(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。<已弃用>
[Warning] deprecated conversion from string constant to 'char*' [-Wwrite-strings]
举例:通过字符指针变量输出一个字符串。
#include <stdio.h>
int main()
{
char *string = "1 love China!"; //定义指针变量string
printf("%s\n", string); //用%s格式输出整个字符串
printf("%c\n", *(string + 7)); //用%c格式输出一个元素
return 0;
}
在程序中没有定义字符数组,只定义了一个 char* 型的指针变量(字符指针变量) string,用字符串常量 " I love China! " 对它初始化。C 语言对字符串常量是按字符数组处理的,在内存中开辟了一个字符数组用来存放该字符串常量,但是这个字符数组是没有名字的,因此不能通过数组名来引用,只能通过指针变量来引用。
对字符指针变量 string 初始化,实际上是把字符串第 1 个元素的地址(即存放字符串的字符数组的首元素地址)赋给指针变量 string,使 string 指向字符串的第 1 个字符,由于字符串常量 " I love China! " 已由系统分配在内存中连续的 14 个字节中,因此,string 就指向了该字符串的第一个字符。在不致引起误解的情况下,为了简便,有时也可说 string 指向字符串 " I love China! " ,但应当理解为 “ 指向字符串的第 1 个字符 ”。
注意:
string 被定义为一个指针变量,基类型为字符型。请注意它只能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把 " I love China! " 这些字符存放到 string 中(指针变量只能存放地址),也不是把字符串赋给 *string。只是把 " I love China! " 的第 1 个字符的地址赋给指针变量。
%s 是输出字符串时所用的格式符,在输出项中给出字符指针变量名 string,则系统会输出 string 所指向的字符串第 1 个字符,然后自动使 string 加 1,使之指向下一个字符,再输出该字符……如此直到遇到字符串结束标志 '\0' 为止。在内存中,字符串的最后被自动加了一个 '\0' ,因此在输出时能确定输出的字符到何时结束。
4.2指针引用字符串数组:
字符串数组的引用同指针引用数组差不多,需要特别注意的是结束条件。
举例:将字符串 a 复制为字符串 b,然后输出字符串b。
#include <stdio.h>
int main()
{
char a[] = "I am a student.", b[20];//定义字符数组
int i;
for (i = 0; *(a + i) != '\0'; i++)
*(b + i) = *(a + i); //将a[i]的值赋给b[i]
*(b + i) = '\0'; //在b数组的有效字符之后加'\0'
printf("string a is:%s\n", a); //输出a数组中全部有效字符
printf("string b is:");
for (i = 0; b[i] != '\0'; i++)
printf("%c", b[i]); //逐个输出b数组中全部有效字符
//printf("string b is:%s\n", b); //输出b数组中全部有效字符
printf("\n");
return 0;
}
注意:字符数组与数字型数组的区别,字符数组可以用printf输出整个数组,而数字型数组则不行。在前面的培训中我们讲过。
4.3实例练习:
1.输入一串字符串,统计数字个数
复习fgets
#include <stdio.h>
#include <string.h>
#define MAXSIZE 100
int main()
{
char str[MAXSIZE];
int n = 0;
fgets(str, MAXSIZE, stdin);
//int x = strlen(str);
//printf("%s\n%d\n", str, x);
for(int i=0;*(str+i) != '\n'; i++)
if(*(str+i)>='0' && *(str+i)<='9')
n++;
printf("%d\n", n);
return 0;
}
复习getchar
#include <stdio.h>
#define MAXSIZE 100
int main()
{
char str[MAXSIZE], s;
int n = 0, i=0;
char *p;//定义字符指针
while((s = getchar()) != '\n')//输入字符串
{
*(str+i) = s;
i++;
}
str[i] = '\0';//添加末尾结束标志
for(p = str;*p!='\0';p++)//判断是否为数字
if(*p>='0' && *p<='9')
n++;
printf("%d\n", n);
return 0;
}
5.自增,自减与指针:
口诀:自增,自减与指针的优先级相同,所以当两个放一起时自右向左运行。
*p++ = *(p++) = p[i++] *++p = *(++p) = p[++i] *p-- = *(p--) = p[i--] *--p = *(--p) = p[--i]
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p, i;
for(p = a; p < a+10;)
{
printf("%-3d", *p++);//*p++ = *(p++) = p[i++]
}
putchar('\n');
for(p = a, i = 0; i<10;)
{
printf("%-3d", p[i++]);//*p++ = *(p++) = p[i++]
}
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p, i;
for(p = a; p < a+9;)//因为开始已经跳过了一个元素,所以p<a+9而不是p<a+10
{
printf("%-3d", *++p);//*++p = *(++p) = p[++i]
}
putchar('\n');
for(p=a, i=0; i<9;)
{
printf("%-3d", p[++i]);//*++p = *(++p) = p[++i]
}
return 0;
}
自减同理;
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p, i;
for(p = a+9; p >= a;)
{
printf("%-3d", *p--);//*p-- = *(p--) = p[i--]
}
putchar('\n');
for(p=a, i = 9; i >= 0;)
{
printf("%-3d", p[i--]);//*p-- = *(p--) = p[i--]
}
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p, i;
for(p = a+9; p >= a+1;)//因为开始已经跳过了一个元素,所以p>=a+1而不是p>=a
{
printf("%-3d", *--p);//*--p = *(--p) = p[--i]
}
putchar('\n');
for(p = a, i=9; i >= 1;)
{
printf("%-3d", p[--i]);//*--p = *(--p) = p[--i]
}
return 0;
}
二.指针的大小:(自行深入了解)
在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的。这是因为操作系统的位数与其所能支持的最大内存有直接的关系。由于计算机是按照字节寻址的,如在 32 位操作系统下,32位比特位一共能描述 232 个状态,一个状态标记大小为 1B(一般定义8位(bit,比特)为一字节),所以一共有 2321B = 4GB。因此 32 位系统所能支持的最大内存为 4GB。而对于 64 位操作系统(目前主流操作系统),所能支持的最大内存为 2641B = 17179869184GB,这是一个很大的数,基本上可以支持任何现实中任意存在的内存。
而指针最小就是以字节为单位进行指引,因此对于 32 位操作系统,它也要是 32 位的,即 4 字节大小;而对于 64 位操作系统,它就得是 64 位 即 8 字节大小。
例:
#include <stdio.h>
int main()
{
int a = 4;
int *p = &a;
printf("%zu\n", sizeof(p));
// 指针变量占据 8 个字节
printf("%p\n", p);
// 变量 a 存放在以内存地址为000000000062FE14开头的内存单元中
}
/*
代码输出:
8
000000000062FE14
*/
注意:在 C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。
#include <stdio.h>
int main()
{
int a = 0, b = 1;
int *p1 = &a, *p2 = &b;
printf("%p\n%p\n", p1, p2);
printf("%zu\n%zu\n", sizeof(p1), sizeof(p2));
printf("%zu\n%zu\n", sizeof(a), sizeof(b));
return 0;
}
/*
代码输出:
000000000062FE0C
000000000062FE08
8
8
4
4
*/
这是一个很简单的例子,程序中创建了两个局部变量,然后利用指针输出它们的地址。其中&是取地址符,取出来以后赋给两个指针变量p1、p2,并将其打印出来。 细心的话可以发现这两个地址由大到小,相差为 4。(16进制,C为12,比8大4)这说明栈中的这两个变量恰好相邻,且一个int类型的变量占用 4B大小空间,同理char类型的变量占用1B大小空间,float类型的变量占用8B大小空间。而且还说明栈空间的增长方向是由大到小的,当然我们的主题是指针,这里不再赘述。
#include <stdio.h>
#include <string.h>
int main()
{
int a[] = {1, 2, 3, 4};
printf("%d\n", sizeof(a+0)); //数组名默认首元素地址:加0还是首元素地址,地址为一个指针,因此结果为8字节(64位)
printf("%d\n", sizeof(*a));//数组名默认首元素地址,解引用也就是数组中的1,其为整型数据,所以为4字节
printf("%d\n", sizeof(a+1));//数组名默认首元素地址:加1是数组中第二个元素2的地址,地址8字节 (64位)
printf("%d\n", sizeof(a[1]));//数组中第一个元素1的大小,其为整型所以为4字节
printf("%d\n", sizeof(&a));//&a:为整个数组的地址,既然为地址,就占8个字节(64位)
printf("%d\n", sizeof(*&a));//对整个数组进行解引用,因此大小为4*4=16字节
printf("%d\n", sizeof(&a+1));//整个数组的下一位元素的大小:8字节 (64位)
printf("%d\n", sizeof(*&a[0]));//取出第一个元素的地址并解引用,整型数据1,4字节
printf("%d\n", sizeof(*&a[0]+1));//取出第一个元素的地址并解引用,整型数据1,再加1为2,整型2的大小为4字节
return 0;
}
//*的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。也就是说,解引用是返回内存地址中对应的对象。
%d输出int型。 %zu输出size_t型。size_t在库中定义为unsigned int。//计算时间更长 一个是整型,一个是无符号整型。 补充:如果%zu不能使用,可以用%u取代。%zu不能输出负数。
sieof是求一个数据的所占用的内存空间的函数,其单位为字节。
#include<stdio.h>
int main()
{
char a[] = {'a', 'b', 'v', 'c', 't'};
printf("%d\n", sizeof(a)); //数组名放入sizeof内,其代表整个数组,每个字符占一个字节,因此,一共5个字节
printf("%d\n", sizeof(a+0));//此时a不再代表整个数组,而是数组首元素的地址,加0 还是首元素地址,地址即为8字节(64位)
printf("%d\n", sizeof(*a));//数组名代表首元素地址,也引用即为字符a,其大小为1字节
printf("%d\n", sizeof(a+1));//数组名代表首元素地址,加1代表字符b的地址,地址即为8字节(64位)
printf("%d\n", sizeof(a[1]));//此时表示数组中字符b的大小,1字节
printf("%d\n", sizeof(&a));//&a代表整个数组的地址,地址即为8字节(64位)
printf("%d\n", sizeof(*&a));//&a代表整个数组的地址,整个数组解引用即5个字节
printf("%d\n", sizeof(&a+1));//&a代表整个数组的地址,加1代表此数组后的下一个元素的地址,地址即为8字节(64位)
printf("%d\n", sizeof(*&a[0]));//取出数组中字符a的地址并解引用,也就是字符a,1字节
printf("%d\n", sizeof(&a[0]+1));//取出数组中字符a的地址,加1表示字符b的地址,地址即为8字节(64位)
return 0;
}
strlen所作的仅仅是计数器一个的工作,它从内存的某个位置,可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域开始扫描,直到碰到第一个字符串结束'\0为止,然后返回计数器值,长度不包含\0。
#include<stdio.h>
#include<string.h>
int main()
{
char a[] = {'a','b','v','c','t','r'};
//在内存中实际存储: 'a','b','v','c','t','r','\0'
printf("%d\n",strlen(a)); //由于上述这种字符串定义方式系统会默认补充字符串结束标志'\0'
//因此,其将会直至找到\0位置
printf("%d\n",strlen(a+0));//数组名默认为书元素地址,加0仍然是首元素地址,然后去找'\0'
printf("%d\n",strlen(a+1));//首元素地址加1,则变为了字符数组中字符b的地址,因此将从b开始寻找结束标志'\0'
////因此,长度减一
//printf("%d\n",strlen(a[1]));//访问数组中的第二个元素,则是字符b,因此将会其对应的ASCII码值的地址开始寻找\0,会产生错误
printf("%d\n",strlen(*&a));//&a代表整个数组的地址,从此地址开始找\0,整个数组解引用即长度为6
printf("%d\n",strlen(*&a+1));//从整个数组后的下一个字节开始寻找\0, 数组解引用即长度为5
printf("%d\n",strlen(&a[0]+1));//&a[0]取出第一个元素的地址,再加1为第二个元素的地址,从此开始寻找\0,数组解引用即长度为5
return 0;
}
三.深入指针:
1.三种指针的运算:(掌握)
1.1指针 +, - 整数:
//指针 + 整数
#include <stdio.h>
int main()
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
int sz = sizeof(a) / sizeof(a[0]);
//printf("%d\n", sz);
int *p = a;
for (i = 0; i < sz; i++)
{
//printf("%d ", *(p + i));
printf("%d ", *p);
p = p + 1; //指针加1
//p++;
}
}
//指针 - 整数
#include <stdio.h>
int main()
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
int sz = sizeof(a) / sizeof(a[0]);
int* p = &a[9]; //p指针指向数组a第九个元素
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p = p - 1; //p指针-1
}
return 0;
}
通过两个例子我们可以发现,指针加减整数就是指针的移动。
1.2指针 - 指针:
(1)数组中指针减指针得到的是数组元素的个数:
举例如下:
#include <stdio.h>
int main()
{
int a[6] = { 1, 2, 3, 4, 5, 6 };
printf("%d\n", &a[6] - &a[0]);
/*
int* p = &a[6]-&a[0];
printf("%d\n", p);
*/
return 0;
}
(2)字符串数组中指针减指针得到的是字符串的个数:
举例如下:
//模拟strlen()函数,用指针的方式求字符串的长度
#include <stdio.h>
int my_strlen(char* str)//用字符指针p接收字符数组a的首地址,返回int类型
{
char* start = str;//定义开始指针,指向字符元素的首地址
char* end = str;//定义结束指针,遇到\0就结束
while (*end != '\0') //*end等于'\0'后就跳出循环
{
end++;
}
return end - start;
}
int main()
{
char a[] = "nice"; // n i c e \0
int len = my_strlen(a); //传的是字符数组a的首地址
printf("%d\n", len);
return 0;
}

2.指针引用多维数组:
先定义一个二维数组
int a[3][4] = { {1,3,5,7},{9,11,13,15},{17,19,21,23} };
a 是二维数组名,a 数组包含 3 行,即 3 个行元素:a[0],a[1],a[2]。而每一个行元素又是一个一维数组,它包含 4 个元素(即 4 个列元素)。例如, a[0] 所代表的一维数组又包含 4 个元素:a[0] [0],a[0] [1],a[0] [2],a[0] [3]。可以认为二维数组是 “数组的数组”,即二维数组 a 是由 3 个一维数组所组成的。

从二维数组的角度来看,a 代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素,而是由 4 个整型元素所组成的一维数组,因此 a 代表的是首行(即序号为 0 的行)的起始地址。a+1代表序号为 1 的行的起始地址。如果二维数组的首行的起始地址为2000,一个整型数据占 4 个字节,则 a+1 的值应该是 2000+4×4=2016 (因为第 0 行有 4 个整型数据)。a+1 指向a[1],或者说,a+1 的值是 a[1] 的起始地址。a+2 代表 a[2] 的起始地址,它的值是2032。
a[0],a[1],a[2] 既然是一维数组名,从前面已知,数组名代表数组首元素地址,因此 a[0] 代表一维数组 a[0] 中第 0 列元素的地址,即 &a[0] [0]。同理,a[1] 的值是 &a[1] [0],a[2] 的值是 &a[2] [0]。

a[0] 是一维数组名,该一维数组中序号为 1 的元素的地址显然应该用 a[0]+1 来表示。此时 “a[0]+1” 中的 1 代表 1 个列元素的字节数,即4个字节。a[0] 的值是2000,a[0]+1 的值是2004(而不是2016)。这是因为现在是在一维数组范围内讨论问题,正如有一个一维数组x,x+1 是其第 1 个元素x[1] 的地址一样。a[0]+0,a[0]+1,a[0]+2,a[0]+3 分别是a[0] [0],a[0] [1],a[0] [2],a[0] [3] 元素的地址(即 &a[0] [0],&a[0] [1],&a[0] [2],&[0] [3])。
在上面我们说过*(a+i)与a[i]等价,因此,a[0]+1和 *(a+0)+1 都是 &a[0] [1] (即地址都为2004)。a[1]+2 和 *(a+1)+2 的值都是 &a[1] [2] (即地址为2024)。请注意不要将*(a+1)+2 错写成 *(a+1+2),后者变成 *(a+3) 了,相当于 a[3] 。进一步分析,欲得到 a[0] [1] 的值,用地址法怎么表示呢?既然 a[0]+1 和 (a+0)+1 是 a[0] [1] 的地址,那么,(a[0]+1) 就是 a[0] [1] 的值。同理,((a+0)+1) 或 (a+1) 也是 a[0] [1] 的值。*(a[i]+j) 或 ((a+i)+j)是 a[i] [j] 的值。务请记住 *(a+i) 和 a[i] 是等价的。
注意:a[i] 从形式上看是 a 数组中序号为 i 的元素。如果a是一维数组名,则 a[i] 代表 a 数组序号为 i 的元素的存储单元。a[i] 是一个有确定地址的存储单元。但如果 a 是二维数组,则 a[i] 是一维数组名,它只是一个地址,并不代表一个存储单元,也不代表存储单元中的值(如同一维数组名只是一个指针常量一样)。a,a+i,a[i],* (a+i),* (a+i)+j,a[i]+j 都是地址。而 *(a[i]+j) 和 ((a+i)+j) 是二维数组元素 a[i] [j] 的值。
简单来说除了*(a[1]+2),*(*(a+1)+2),a[1] [2]以外的其他形式都是地址。
2.1地址举例说明:
| 表现形式 | 含义 | 值 |
|---|---|---|
| a | 二维数组名,指向行,即一维数组a[0],即0行的起始地址 | 2000 |
| a[0] | 一维数组名,指向列,即整型常量a[0] [0],即0行0列元素的地址 | 2000 |
| *(a+0),*a | 0 行 0 列元素的地址 | 2000 |
| a+1,&a[1] | 第 1 行 (a[1]) 的起始地址 | 2016 |
| a[1],*(a+1) | 第 1 行 0 列元素 a[1] [0] 的地址 | 2016 |
| a[1]+2,*(a+1)+2,&a[1] [2] | 第 1 行 2 列元素 a[1] [2] 的地址 | 2024 |
| *(a[1]+2),*(*(a+1)+2),a[1] [2] | 第 1 行 2 列元素 a[1] [2] 的值 | 13 |
注意:2000只是随便写的地址作用是方便理解,具体是多少得看实际情况。
//代码讲解
#include<stdio.h>
int main()
{
int a[3][4] = { {1,3,5,7},{9,11,13,15},{17,19,21,23} };
//输出的是地址
printf("%d\n", a);
printf("%d\n", a[0]);
printf("%d\n", *a);
printf("%d\n", *(a+0));
putchar('\n');
printf("%d\n", *(a+1));
printf("%d\n", &a[1]);
printf("%d\n", a+1);
printf("%d\n", a[1]);
putchar('\n');
printf("%d\n", a[1]+2);
printf("%d\n", *(a+1)+2);
printf("%d\n", &a[1][2]);//*(a[1]+2),*(*(a+1)+2),a[1][2]
putchar('\n');
printf("%d\n", *(a[1]+2));
printf("%d\n", *(*(a+1)+2));
printf("%d\n", a[1][2]);
}
/*代码输出
6487536
6487536
6487536
6487536
6487552
6487552
6487560
6487560
6487560
13
13
13
*/
关于a+1与a[1]的讲解:
a+1 是二维数组 a 中序号为 1 的行的起始地址(序号从 0 起算),而 (a+1) 并不是 a+1 单元的内容(值),因为 a+1 并不是一个数组元素的地址,也就谈不上存储单元的内容了。(a+1) 就是 a[1],而a[1] 是一维数组名,所以也是地址,它指向a[1] [0]。a[1]和 *(a+1) 都是二维数组元素 a[1] [0] 的地址的不同的表示形式。
2.2地址详解:(做了解)
2.3指向数组元素的指针变量
实列:有一个3×4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include<stdio.h>
int main()
{
int a[3][4] = { 1,3,5,7,9,11,13,15,17,19,21,23 };
int *p; //p是int型的指针
for (p = a[0]; p < a[0] + 12; p++)//使p依次指向下一个元素
{
if (p != a[0] && ((p - a[0]) % 4 == 0)) printf("\n");//p移动4次后换行
printf("%4d ", *p); //输出p所指向元素的值
}
}
/*代码输出:
1 3 5 7
9 11 13 15
17 19 21 23
*/
如何理解代码呢?相信大家会对for循环和if的理解有点困难。
首先需要记住计算 a[i] [j] 在数组中的相对位置的计算公式为:i * m + j

其中,m 为二维数组的列数(二维数组大小为 n×m )。例如,对上述 3×4 的二维数组,它的 2 行 3 列元素 a[2] [3] 对 a[0] [0] 的相对位移量为 2×4+3=11 元素。如果一个元素占 4 个字节,则 a[2] [3] 对 a[0] [0]的地址差为 11×4=44 字节。若开始时指针变量 p 指向 a[0] [0],a[i] [j] 的地址为 “&a[0] [0]+(i*m+j)” 或 “p+(i*m+j)” 。a[2] [3] 的地址是 (p+2*4+3),即 (p+11)。a[2] [3] 的值为 *(p+11)。
从图中可以看到在 a[i] [j] 元素之前有 i 行元素(每行有 m 个元素),在 a[i] [j] 所在行,a[i] [j] 的前面还有 j 个元素,因此 a[i] [j] 之前共有 i×m+j 个元素。例如,a[2] [3]的前面有两行,共 2×4=8 个元素,在它本行内还有 3 个元素在它前面,故共有 8+3=11 个元素在它之前,可用 p+11 表示其相对位置。
2.4实例练习:
在示例的基础上,如果二维数组a[i] [j]的下标i+j值为偶数则变为1,输出格式不变。
//常规思路:
#include<stdio.h>
int main()
{
int a[3][4] = { { 1, 3, 5, 7 }, { 9, 11, 13, 15 }, { 17, 19, 21, 23 } };
int i, j;
int *p, *ptr;
printf("原始数组 a:\n");
for (p = a[0]; p < a[0] + 12; p++) //使p依次指向下一个元素
{
if (p != a[0] && ((p - a[0]) % 4 == 0)) printf("\n"); //p移动4次后换行
printf("%-5d ", *p); //输出p所指向元素的值
}
putchar('\n');
printf("处理后的数组 a:\n");
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
if (((i + j) % 2 == 0))
{
a[i][j] = 1;
}
printf("%-5d ", a[i][j]);
}
printf("\n");
}
return 0;
}
//使用指针:
#include<stdio.h>
int main()
{
int a[3][4] = { { 1, 3, 5, 7 }, { 9, 11, 13, 15 }, { 17, 19, 21, 23 } };
int i, j;
int *p, *ptr;
printf("原始数组 a:\n");
for (p = a[0]; p < a[0] + 12; p++) //使p依次指向下一个元素
{
if (p != a[0] && ((p - a[0]) % 4 == 0)) printf("\n");//p移动4次后换行
printf("%-5d ", *p);//输出p所指向元素的值
}
putchar('\n');
printf("处理后的数组 a:\n");
for (ptr = &a[0][0]; ptr <= &a[2][3]; ptr++)
{
if (((ptr - &a[0][0]) / 4 + (ptr - &a[0][0]) % 4) % 2 == 0)
{
*ptr = 1;
}
printf("%-5d ", *ptr);
if ((ptr - &a[0][0] + 1) % 4 == 0)//每行输出完成后换行
{
printf("\n");
}
}
return 0;
}
//(ptr - &a[0][0])计算了指针相对于数组起始地址的偏移量。这个偏移量告诉我们当前指针位于数组中的哪个位置。
//(ptr - &a[0][0]) / 4计算了当前元素的行号(i值),因为在这个数组中,每行有4个元素。这个操作会将偏移量除以4,得到行号。
//(ptr - &a[0][0]) % 4计算了当前元素的列号(j值),因为每行有4个元素,所以取偏移量除以4的余数即可得到列号。
//最后,计算了当前元素的值。这个值表示了元素在二维数组中的位置之和。((ptr - &a[0][0]) / 4 + (ptr - &a[0][0]) % 4)i + j
3.指向函数的指针:
3.1什么是函数的指针:
如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。 函数名就是函数的指针,它代表函数的起始地址。
总结:简单来说,函数的指针就是指向函数首地址的一个指针,通过访问指针来找到并使用函数。
例如:
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
int (*p)(int,int); //定义p是一个指向函数的指针变量,可以指向函数类型为整型且有两个整型参数的函数。指针变量p的类型用int (*)(int,int)表示。
3.2定义和使用函数指针:
如何定义并使用指针呢?我们通过一个例子来学习:输入a,b,c的值,输出最大者:
#include <stdio.h>
int max(int a, int b, int c)
{
int temp;
if(a > b)
{
if(a > c)
temp = a;
else
temp = c;
}
else
{
if(b > c)
temp = b;
else
temp = c;
}
return temp;
}
int main()
{
int a, b, c, t;
int (*p)(int, int, int); //定义指向函数的指针变量p
scanf("%d%d%d", &a, &b, &c);
p = max; //引用指针,让它指向max函数的起始地址,也就是将max函数的起始地址赋值给指针变量p
t = (*p)(a, b, c); //使用指向max函数的指针变量p(*p替换函数名)
printf("max = %d\n", max(a, b, c));
printf("max = %d\n", t);
return 0;
}
首先,我们先编写好我们的max函数,然后我们便可以在主函数里定义我们的函数指针:
//格式:类型名 (*指针变量名)(函数参数表列) int (*p)(int,int);
注意事项:
(1) 定义指向函数的指针变量,并不意味着这个指针变量可指向任何函数,它只能指向在定义时指定的类型的函数。
(2) 如果要用指针调用函数,必须先使指针变量指向该函数。
(3) 在给函数指针变量赋值时,只须给出函数名而不必给出参数。
(4) 用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。
(5) 对指向函数的指针变量不能进行算术运算,如p+n,p++,p--等运算是无意义的。
(6) 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
易错点:
(1)指针必须加括号,表示p先与*结合,是指针变量,再与后面的()结合,()表示函数部分,即该指针变量不是指向一般的变量,而是指向函数,如果
int*p(int, int);
则表示声明了一个名字为p的函数,返回值为指向整型变量的指针。
总结:如何使用指向函数的指针呢,和常规的使用差不多:
1.定义;2.引用;3.替换使用。
3.3实例练习:
输入一个小于100的十进制数,编写一个函数将十进制数转换为二进制并输出,要求通过指针来引用函数
#include <stdio.h>
void dec_bin(int x)
{
int a[10];
for(int j=0; j<10; j++)
a[j] = -1;
while(x)
{
a[i] = x % 2;
x = x / 2;
i++;
}
for(int i = 10; i>=1; i--)
if(a[i-1]!=-1)
printf("%-2d", a[i-1]);
}
int main()
{
int dec;
scanf("%d", &dec);
void (*p)(int);
p = dec_bin;
(*p)(dec);
return 0;
}
4.返回指针值的函数:
4.1使用返回指针值的函数:
格式:
类型名 *函数名(参数表列)
我们通过一个例子来学习:
输入x, y的值,编写一个函数,将x, y作为参数,若不相同则返回指针输出最大者,否则指针为空。
#include <stdio.h>
int *max(int x, int y)
{
int *p, max;//定义int类型的指针变量p和int类型的整形变量max
p =NULL;//将指针p置空
if(x != y)
{
max = x>y?x:y;
p = &max;
}
return p;
}
int main()
{
int x, y,*q;
scanf("%d%d", &x, &y);
q = max(x, y);
if(q)//q != NULL
{
printf("max = %d\n", *q);
}
else
{
printf("x and y is same.\n");
}
return 0;
}
强化理解1:
存有字母A到Z,26个字母的字符串数组a[27],我们随机输入一个数字n(1~26),通过访问数组a来输出对应的字母A~Z。
#include <stdio.h>
//我们也可以这样定义函数
//char* search(char a[], int n)
//原因:我们讲过数组就是指针,指针就是数组
char *search(char *a, int n)//定义一个char*类型的search函数,参数为数组首地址和所需的字母位数
{
char *c;
c = (a+(n-1));//a为数组首地址,我们认为是指针,然后加上(n-1)就是指针的移动
//如果不好理解,我们可以这样想:
// for(int i; i<n; i++)
// {
// c = (a+i);
// }
return c;//返回指针
}
int main()
{
int n;
char a[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//定义存有26个字母的数组a
char *x;//定义一个指针变量x
scanf("%d", &n);
x = search(a, n);//调用函数search,把返回的指针赋值给指针变量x
printf("%c", *x);//输出指针x对应的值
return 0;
}
在刚才的练习下加深理解:
强化理解2:
存有字母A到Z,26个字母的字符串数组a[27],我们随机输入一个数字n(1~26),通过访问数组a来输出第n个字母后面的所有字母。
#include <stdio.h>
//我们也可以这样定义函数
//char* display(char a[], int n)
//原因:我们讲过数组就是指针,指针就是数组
char *display(char *a, int n)//定义一个char*类型的display函数,参数为数组首地址和字母位数
{
char *c;
c = NULL;//先让指针为空
if(n<=25)//如果为前25个字母,则表示后面有字母,指针不为空
{
c = (a+n);//a为数组首地址,我们认为是指针,然后加上n就是指针的移动,移动到第n个字母后面
// //如果不好理解,我们可以这样想:
// for(int i; i<=n; i++)
// {
// c = (a+i);
// }
}
return c;//返回指针
}
int main()
{
int n;
char a[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//定义存有26个字母的数组a
char *x;//定义一个指针变量x
scanf("%d", &n);
x = display(a, n);//调用函数display,把返回的指针赋值给指针变量x
if(x)//x != NULL 指针不为空
{
for(int i=0; x+i<&a[26]; i++)//输出第n个字母后面的所有字母
{
printf("%-2c", *(x+i));
}
putchar('\n');
}
else//指针为空
{
printf("无字母\n");
}
return 0;
}
5.指针数组与数组指针:
5.1定义和使用指针数组:
指针数组,顾名思义就是指针类型的数组,首先是一个数组,数组元素存储的是指针。那么如何使用呢?
格式:
类型名 * 数组名[数组长度]
注意:(注意优先级:( )>[ ]> *),所以:
int* p[n];
[ ]> * ,所以p是数组,是一个由n个指针类型元素组成的指针数组,或者说这个当一个数组里含有的元素为指针类型的时候,它就被成为指针数组。当p+1时,则p指向下一个数组元素。(需注意,p=a;这种赋值方法是错的,因为p是一个不可知变量,只存在p[0],p[1],p[2],但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值) 将二维数组赋值给指针数组:
#include <stdio.h>
int main(void)
{
int *p[3], i; //定义指针数组
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i]; //通过循环将a数组每行的首地址分别赋值给p里的元素
return 0;
}
这里int * p[3]表示一个一维数组内存放三个指针变量,分别是p[1],p[2],p[3]。如果要引用二维数组时,可以有多种表达方式:如表示数组中i行j列一个元素:
*(* (p+i)+j)、(* (p+i) )[j]、p[i] [j]、 * (p[i]+j)
指针数组最重要的用途是对多个字符串进行处理操作<已弃用>,因为字符指针比二维数组更快更有效。
原因:
[Warning] deprecated conversion from string constant to 'char*' [-Wwrite-strings]
[警告]已弃用从字符串常量到'char*'的转换[-Wwrite-strings]
例:
#include <stdio.h>
void func(char **p)
{
char *t;
t = (p += 3)[-1];
printf("%s\n",*p);
printf("%s\n",t);
}
int main()
{
char *arr[] = {"ab","ce","ef","gh","ij","kl"};//指针数组
func(arr);
return 0;
}
5.2定义和使用数组指针:
数组指针也称为行指针,格式:
类型名 (*数组名)[数组长度]
注意:(注意优先级:( )>[ ]> *),所以:
int (*p)[n];
( )>[ ],所以首先p 和*结合,说明 p是一个指针,然后int 和[n]都是修饰这个指针的,意思是,这个指针指向了一个里面有n个int型元素的数组。
当数组指针指向一个一维数组时:
#include <stdio.h>
#define n 10
int main()
{
int (*p)[n]; //定义了指向含有n个元素的一维数组的指针
int a[n]; //定义数组
p=a; //将一维数组首地址赋值给数组指针p
return 0;
}
()优先级高,说明p是指针,指向一个整型的一维数组。这个一维数组的长度是n,也可以说p的步长为n。当p+1时,p指针会跨过n个整型数据的长度。
当数组指针指向一个二维数组时:
#include <stdio.h>
int main()
{
int (*p)[4]; //定义了指向含有4个元素的一维数组的指针
int a[3][4];
p=a; //将二维数组的首地址赋值给p,也可是a[0]或&a[0][0]
p++; //表示p跨过行a[0][],指向了行a[1][]
return 0;
}
所以,数组指针也成为指向一维数组的指针,也就是行指针。
#include <stdio.h>
int main()
{
int arr[][3] = {1,2,3,4,5,6};
int (*ptr)[3] = arr;
printf("%d,%d\n",(*ptr)[0],(*ptr)[1]);
ptr++; //ptr = ptr + 1;ptr跨过了a[0][],指向了a[1][]
printf("%d,%d\n",(*ptr)[0],(*ptr)[1]);
return 0;
}
ok,到这里,数组指针和指针数组的区别就很明了,数组指针(行指针)是一个指针变量,似乎是C语言里专门用来指向二维数组的,它的p+1表示到下一行。指针数组是指针类型的数组,里面的元素是指针类型的数据。
关于它们的使用有点困难就不举例了。
6.多重指针:
我们简单讲一下:
#include<stdio.h>
int main()
{
int a = 10;
int *p, **p1;
p = &a;
p = &p;
printf("%d\n", **p1);
return 0;
}
代码中 *p 就是正常的指针,**p 就表示这是一个指向指针的指针。*p 是指向变量,而**p1 就是表示指向指针。其实指针也是一个变量,他也有一个地址,**p1 就表示他将会指向一个指针,而指向的那个指针则会指向一个变量,操作 **p1就等于操作那个最终指向的变量。
也就是说,多重指针就是套娃式的操作。
#include<stdio.h>
int main()
{
int a = 10;
int *p, **p1, ***p2;
p = &a;
p1 = &p;
p2 = &p1;
printf("%d\n", ***p2);
return 0;
}
我们看这个代码就如同上面一样的意思。
更深入的知识就需要自己去了解了。
四.作业:
1.输入两个小写字母,通过引用指针输出更后面的字母;
2.通过引用指针输出数组a[9] = "ntaiiatn";的偶数项;
3.输入n个1~24的数字,通过引用指针和函数将他们转换为对应的字母输出后并排序输出。
第一题:
#include <stdio.h>
int main()
{
char c1, c2;
char *p1, *p2, *p3;
scanf("%c %c", &c1, &c2);
p1 = &c1;
p2 = &c2;
p3 = (*p1 - *p2) > 0 ? p1 : p2;
printf("%c\n", *p3);
return 0;
}
第二题:
#include <stdio.h>
int main()
{
char a[9] = "ntaiiatn";
char *p;
p=a;
for(int i = 0;i<8;i++)
{
if((i+1) % 2 == 0)
{
printf("%c",*(p+i));
}
}
return 0;
}
第三题:
#include <stdio.h>
// 定义一个函数,将数字映射为字母
char mapNumberToLetter(int num)
{
if (num >= 1 && num <= 26)
{
return 'A' + num - 1; // A对应1,B对应2,以此类推
}
return '?'; // 如果数字不在1~26范围内,返回?
}
// 定义一个函数,接受一个整数数组和其大小,将数字转换为字母并排序输出
void convertAndSortNumbers(int* numbers, int size)
{
char letters[size]; // 用于存储转换后的字母
// 遍历输入的数字数组,将数字转换为字母并存储在动态分配的内存中
for (int i = 0; i < size; ++i)
{
letters[i] = mapNumberToLetter(numbers[i]);
}
printf("排序前的字母:");
for (int i = 0; i < size; ++i)
{
printf("%c ", letters[i]);
}
printf("\n");
// 对字母进行排序
for (int i = 0; i < size - 1; ++i)
{
for (int j = 0; j < size - i - 1; ++j)
{
if (letters[j] > letters[j + 1])
{
char temp = letters[j];
letters[j] = letters[j + 1];
letters[j + 1] = temp;
}
}
}
// 输出排序后的字母
printf("排序后的字母:");
for (int i = 0; i < size; ++i)
{
printf("%c ", letters[i]);
}
printf("\n");
}
int main()
{
int n;
printf("请输入数字个数n:");
scanf("%d", &n);
int numbers[n];
printf("请输入%d个1~24之间的数字:\n", n);
for (int i = 0; i < n; ++i)
{
scanf("%d", &numbers[i]);
}
convertAndSortNumbers(numbers, n);
return 0;
}
572

被折叠的 条评论
为什么被折叠?



