C语言第七次培训

------指针

目录

------指针

一.初识指针:

1.基础指针知识归纳:

2.如何定义和使用指针:

2.1指针变量的定义和使用:

2.2实例练习:

3.通过指针引用数组:

3.1讲解:

3.2实例练习:

4.通过指针引用字符串

4.1回顾:

4.2指针引用字符串数组:

4.3实例练习:

5.自增,自减与指针:

二.指针的大小:(自行深入了解)

三.深入指针:

1.三种指针的运算:(掌握)

1.1指针 +, - 整数:

1.2指针 - 指针:

(1)数组中指针减指针得到的是数组元素的个数:

(2)字符串数组中指针减指针得到的是字符串的个数:

2.指针引用多维数组:

2.1地址举例说明:

2.2地址详解:(做了解)

2.3指向数组元素的指针变量

2.4实例练习:

3.指向函数的指针:

3.1什么是函数的指针:

3.2定义和使用函数指针:

3.3实例练习:

4.返回指针值的函数:

4.1使用返回指针值的函数:

5.指针数组与数组指针:

5.1定义和使用指针数组:

5.2定义和使用数组指针:

6.多重指针:

四.作业:


一.初识指针:

1.基础指针知识归纳:
  1. 指针就是地址,地址就是指针。(举例:一把钥匙对应一个房间,一个房间对应一把钥匙)

  2. 指针变量就是存放地址的变量。

  3. 如果一个指针变量指向一个普通变量,则 *指针变量 就完全等普通变量。

  4. 地址就是内存单元的编号。

  5. 指针是一个操作受限的非负整数(只能减,不能加、乘除)

所以指针的值就是某一个变量的内存地址指针变量就是用来存放某个变量的内存地址的变量,和广义的变量没有什么区别。

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;
 }

1.2

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 个一维数组所组成的。

img

从二维数组的角度来看,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]。

img

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),*a0 行 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

img

其中,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;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值