【C语言】C语言成长之路之万字长篇进阶指针——C指针的提升与总结,指针就该这样学|•‘-‘•) ✧

目录

❤️前言 

👉1.字符指针

👉2.数组指针

 2.1 数组指针的概念

2.2 &数组名 和 数组名

💡对于指针类型和权限大小的一次小总结:

2.3  数组指针的用法

👉3. 数组传参和指针传参

3.1 一维数组 和 一级指针传参 

3.2 二维数组 和 二级指针传参

👉4. 函数指针

4.1 函数指针的概念理解

4.2 函数指针数组及其用途

4.3 指向函数指针数组的指针

👉5. 回调函数

5.1 qsort 函数的使用

💡5.2 用冒泡法模拟 qsort 函数

👉6. 一些指针和数组的题目

🔎6.1 一维数组

🔎6.2 字符数组

🔎6.3 二维数组

🔎6.4 指针笔试习题

🍀 结语


❤️前言 

今日小诗:

      

歌声在空中感得无限,

  

图画在地上感得无限,

  

诗呢,无论在空中,在地上都是如此;

  

因为诗的词句含有能走动的意义与能飞翔的音乐。

                                                                                                 ——选自《飞鸟集》

       今天给大家分享的是泰戈尔《飞鸟集》中的一首短诗,希望大家无论是在生活还是学习中都要积极向上,勇往直前呀😘💕

       大家好!今天小狮子为大家带来的文章是一篇关于进阶指针的相关知识的博客,希望大家在阅读了这篇博客之后能够更好的理解指针相关的知识!๐•ᴗ•๐

       在之前的学习中,我们已经了解了初阶的指针相关知识,让我们回顾一下一些指针的基本概念

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间,一个地址管理一个字节内存
 
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)
 
3. 指针有类型的分别,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限大小
        接下来,我们正式进入针对指针的更高级主题的讨论。

1.字符指针

       字符指针类型 char* 是在之前我们已经知道的一个指针类型,现在我们要讨论的是它的另一个用法:即它可以存储常量字符串的首元素地址

       具体的使用方式可以看以下的这一道题:

#include <stdio.h>
int main()
{
	char arr1[] = "Hello world!";
	char arr2[] = "Hello world!";
	
	char* p1 = arr1;
	char* p2 = arr2;
	
	const char* p3 = "Hello world!";//p3 和 p4 储存的是常量字符串的首字符地址
	const char* p4 = "Hello world!";
	
	if (p1 == p2)
		printf("p1 和 p2 相同\n");
	else
		printf("p1 和 p2 不同\n");

	if(p3 == p4)
		printf("p3 和 p4 相同\n");
	else
		printf("p3 和 p4 不同\n");

	return 0;
}

       大家可以思考一下这道题,这两组字符指针指向的内存是否相同呢?

       稍加思索,接下来我们揭晓答案:

        p1p2 之间的不同大家肯定能够理解,毕竟它们存储的是两个不同的数组的首元素地址,只是这两个数组是以一个相同的常量字符串来初始化的,但是本质上这两个数组还是占据了不同的两块空间,因此这两者的首元素地址也是不同的,也即p1p2不相同。

       🔑这里补充一下C语言中的一条规定:由于常量字符串是不会被改变的,因此C/C++干脆就把每个常量字符串存储到单独的一个内存区域,当多个字符指针指向同一个字符串的时候,他们实际会指向同一块内存

       而在知道了字符指针可以指向常量字符串的首字符地址后,我们可以知道 p3p4 都是指向同一个常量字符串的首字符地址。这个常量字符串被单独存放在一个内存空间中,于是首字符地址固定,也即 p3 p4 相同。

2.数组指针

 2.1 数组指针的概念

       数组指针,顾名思义是指指向数组的一种指针,类比我们之前学过的指针类型,我们可以猜测其变化的单位大小和访问权限的大小在于所指向的数组的大小,而事实也正是如此。

       初识数组指针时,我们很容易将其于指针数组的表达方式混淆:

int * p1 [ 10 ];
int  ( * p2 )[ 10 ];
 
//p1, p2 分别是什么?
//p1 :指针数组
//p2 :数组指针
🔎解读:
  🔑这里我们回顾操作符优先级的知识: [ ] 的优先级是要高于 * 的,如果想要变量名先与 * 结合,应该将变量名与 * 括起来。
       从变量名开始分析,p1与 [10] 先结合表明了这是一个数组,而数组名前面的 int* 则指明了数组元素为整型指针,所以 p1 是以整形指针为元素的数组的数组名。
       p2则是与  先结合,表明其是一个指针,外面的部分则是代表它指向的内容是一个由十个整型数据组成的数组,我们将变量名挖去,留下的部分  int (*) [10] 是p2的类型,也可以表示它是一个 整型数组指针

2.2 &数组名 和 数组名

       根据之前所学的知识我们可以知道 数组名 等价于首元素地址(即 对应的地址值权限 都相同),我们常常会在关于数组的编程中用到这个知识,那么今天我们探讨我们在之前很少提及的 &数组名 ,它的意义会是什么呢?

       在这里我们也可以类比一下,& 的意义是取出相应变量的地址,根据之前我们使用 &变量 为指针变量赋值,那么 &数组名 的意义是不是取出该数组的地址呢?我们以相关的代码来验证我们的猜想。

       我们来看以下的一段代码:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

       其结果:

  

       可见 数组名 和  & 数组名 打印出的地址是一样的。 那么难道说两个东西是一样的吗?
       我们继续看下面这一段代码:
 
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr = %p\n", &arr);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr+1= %p\n", &arr + 1);
	return 0;
}
       其结果:

💡对于指针类型和权限大小的一次小总结:

       根据这段代码,我们可以初识二者的不同,它们加一过后跨过的内存大小并不相同,这一定程度上显示着此二者的差异,像之前的  地址值相同而指针类型不同  那样,结合我们在初阶指针中对地址和内存的讨论,联系我对它们的理解,我认为可以这样理解:
       由于 为保证指针变量的大小保持稳定我们使4个字节大小的指针变量存储的地址总是对应变量的最低地址 (即32位大小的一个地址值),因此 &数组名数组名 由相同的地址值来表示,但是它们所代表的指针类型并不相同,也就是对应的单位大小和权限不同。
       就和我们之前所认识的不同指针类型一样, 数组名 表示 首元素地址,其指针类型是数组元素所对应的指针类型,按照上面的代码来说就是 int*&数组名 表示 整个数组的地址,其指针类型是数组指针,按照上面的代码来说就是 int(*)[10]
       因此我感觉根据 & 的意义,可以将 数组名 等于 &首元素 作为单独作为一个概念,而 &数组名则和其他的 &变量 作为一类来看,这样理解更加清晰。
       大家可以联系之前的知识进行一次思考,我相信思考过后大家对于指针、数组、指针类型的相关知识将会理解得更加透彻。ʕ•ﻌ•ʔ

2.3  数组指针的用法

       既然我们已经了解了数组指针的相关知识,那么它有哪些应用场景呢?

       让我们一起看一段代码:

#include <stdio.h>

//第一个打印函数
void print_arr1(int arr[3][5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//第二个打印函数
void print_arr2(int(*arr)[5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素其实是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,也就是一维数组的地址
	//那么arr便可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

       其结果:

       由结果我们知道这两个函数是可以等价的。

       想必大家在充分认识了数组指针的概念后,这里的二维数组传参也就不那么难理解了吧!毕竟对于二维数组来说,它的元素就是一个个的一维数组。

       🔎在一起探讨了指针数组和数组指针后,我们来一起回顾并解读下面代码的意思吧:

//大家可以看看以下的几段代码分别是什么意思

int arr[5];//一维数组
int *parr1[10];//指针数组
int (*parr2)[10];//数组指针,作为元素的数组是具有十个整形元素的数组
int (*parr3[10])[5];//存放数组指针的指针数组,此数组十个元素,每个元素的类型为int(*)[5]
//在这里我们可以先拿去parr3和与它先结合的[10],剩下的int (*)[5]便是这个数组中的元素类型

3. 数组传参和指针传参

       当我们在写代码的时候难免要把【数组】或者【指针】传给我们设计的函数,那么函数的参数应该如何设计呢?

3.1 一维数组 和 一级指针传参 

       让我们观察以下代码:

//1、一维数组
#include <stdio.h>

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok? arr2的首元素地址是一个指针的地址,那么我们可以以一个二级指针来接收
{}                   //所以很合理

int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

//2、一级指针
#include <stdio.h>

void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
   printf("%d\n", *(p+i));
 }
}

int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

       经过分析,上述的所有形式都为正确的形式。

3.2 二维数组 和 二级指针传参

       了解了一级指针的传参以后,让我们继续学习有关于二级指针的传参。我们知道,形参其实就是建立一份变量的临时拷贝,那么传参时实参和形参的类型应该保持一致。

       让我们观察以下代码:

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?//错误的写法
{}
void test(int arr[][5])//ok?
{}

//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,我们可以不知道有多少行,但是必须知道一行多少元素。

void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}

void test(int (*arr)[5])//ok?
{}
//这四个中只有这个是正确的,
//二维数组的数组名等价于二维数组的首元素地址,
//也即第一个数组的数组地址,可以用数组指针来接收

void test(int **arr)//ok?
{}

int main()
{
 int arr[3][5] = {0};
 test(arr);
}

       通过对这些代码的观察学习,我们对数组和指针传参有了更好的理解,那么我们接下来学习一个新的知识——函数指针

4. 函数指针

4.1 函数指针的概念理解

       这里首先我们看一段代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

       其结果:

       我们之前遇到的指针都是指向某种数据类型,而今天我们学习的函数指针顾名思义是指向函数的指针,那么特殊的情况应该特殊对待,我们看到上述的代码中函数名 test 和&函数名 &test,它们的地址值一致,而相较于我们之前学过的那些指向数据类型的指针,不同的是这两者在函数指针中代表的意义是完全一致的。

        那么函数的地址该以何种形式来存储呢?换句话说,函数指针的创建和初始化该如何去完成呢?

       让我们一起观察以下代码:

void test()
{
 printf("hehe\n");
}

//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

🔎解读:

       与上面数组指针的分析方式一致,我们看名字与谁先结合,pfun1与 * 以括号括起来的形式先结合,这两个东西先结合我们便可以确认 pfun1 的身份确实是一个指针,然后我们把它拿掉后剩下的部分是 void (*)() 这部分便可以看作是这个指针的类型,它代表我们的 pfun1 指针指向的内容是一个没有形参和返回值的一个函数

       pfun2 则不相同,它与右边的 () 先结合,代表着它是一个函数,无参数,且返回值是一个无类型指针。

       这两者的差异我们可以从以上代码的高亮初识端倪(笑)。

       接下来让我们继续来看两段出自《C陷阱和缺陷》的有趣代码:

//代码1
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

🔎解读:

       代码1:

       我们先看第一段代码,我们似乎对它无法下手,它甚至没有一个名字,那我们可以向0是一个常量的方向去思考,那么它相邻的括号中有一对括号便是代表着 强制类型转换 ,当我们看向0转换的对应类型的时候,我们得到了 void(*)() 这样一个类型,它指的是一个像我们之前那个 pfun1 一样的函数指针,指向一个没有形参和返回值的一个函数。

       代码2:

       第二段代码我们可以先找到一个名字作为分析的开端,那我们从 signal 开始分析,经过了上面几次代码分析,相信大家已经慢慢开始轻车熟路,那我也加快步伐, signal 与()先结合,表明它是一个函数,函数的参数有两个,分别是一个 int 型的变量 和 一个类型为 void(*)(int) 的函数指针变量,返回值也是一个 void(*)(int) 类型指针。

       如果大家觉得代码2不够清晰,那么运用C语言中的关键字 typedef 它也可以这样写:

typedef   void ( * pfun_t )( int );
pfun_t   signal ( int ,   pfun_t );

4.2 函数指针数组及其用途

       数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组

       比如:
int * arr [ 10 ];
// 数组的每个元素是 int*
       如果我们把函数的地址存到一个数组中,这个数组就叫做函数指针数组,那函数指针的数组该如何定义呢?
       根据上面知识的学习,想必你也能慢慢地学会这种写法,我们先让数组名与 [ ] 相结合,再让它与存储的元素类型相结合,那我们来看以下的代码:
 
//哪种写法是正确的呢?
//参照我们在上面分析的代码,我相信你一定能得出正确答案!
int (*parr1[10])();

int *parr2[10]();

int (*)() parr3[10];
       答案是:parr1
       parr1 先和 [] 结合,说明 parr1 是数组,那数组的内容是什么呢? 是 int (*)() 类型的函数指针。至于 parr3 ,虽然逻辑上似乎讲得通,但是我们并没有这种写法。
       我们接下来继续看函数指针数组的作用:
       首先是一段计算器的简单实现:
#include <stdio.h>

int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a*b;
}
int div(int a, int b)
{
 return a / b;
}

int main()
{
 int x, y;
 int input = 1;
    int ret = 0;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
       {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
                printf("退出程序\n");
                breark;
        default:
              printf( "选择错误\n" );
              break;
       }
    } while (input);
    
    return 0;
}

       我们来思考一下,这段代码还有可以改进和优化的地方吗?或者我们是否能够完成对这段代码的简化呢?

       在我们学习了函数指针数组之后,我们便可以做到这一点:

#include <stdio.h>

int add(int a, int b)
{
           return a + b;
}
int sub(int a, int b)
{
           return a - b;
}
int mul(int a, int b)
{
           return a*b;
}
int div(int a, int b)
{
           return a / b;
}

int main()
{
     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
      scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
          printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0;
}

       应用函数指针数组,这个简易计算器的代码确实得到了简化。那么以后我们在遇到这种情况的时候也可以运用这种方式来进行简化和规范化。

4.3 指向函数指针数组的指针

       指向函数指针数组的指针是一个指针,这种指针指向一个 数组 ,数组的元素都是 函数指针 ,定义方式如下:
 
void test(const char* str)
{
 printf("%s\n", str);
}

int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}

5. 回调函数

       🔑 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

5.1 qsort 函数的使用

       qsort 函数是一个用到回调函数完成的排序函数,为帮助大家更好的理解回调函数,接下来我们一起来看看它是如何使用的吧:

#include <stdio.h>

//qosrt函数的使用者需要实现一个比较函数
//这个函数决定了qsort将会如何去排序使用者所给的数据
//这样表示将较大元素向后面排序
int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);
}

int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);

    qsort(arr, sz, sizeof(int), int_cmp);
    //qsort函数的调用:分别给定:
    //排序起始地址
    //排序的总元素个数
    //一个元素的大小
    //一个使用者自定义的比较函数

    for (i = 0; i< sz; i++)
   {
       printf( "%d ", arr[i]);
   }

    return 0;
}

💡5.2 用冒泡法模拟 qsort 函数

       🔑在实现 qsort 之前,我们先了解一下 void* 类型的指针该如何进行使用,这种指针在创建时只能得到一个地址值,因而无法仅仅通过它实现和权限以及访问相关的过程,所以我们常常将它与 char* 类型结合使用,也即将无类型指针强制类型转换成字符型指针, 因为字符型指针的访问权限是最小单位,只有一个字节,这样的话我们便可以通过多次访问来达到其他指针类型的访问效果。

       另外,不知道冒泡法的小伙伴可以支持一下我之前写的一篇关于冒泡排序的文章哦!

       🌐传送门(呼呼呼):【C语言】C语言成长之路之冒泡排序的实现_MO_lion的博客-CSDN博客https://blog.csdn.net/MO_lion/article/details/128032802

       现在我们开始以冒泡法对 qsort 函数的正式的实现:

主函数:

int main()
{
	int arr[10] = { 9,3,2,4,1,0,5,7,6,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_qsort(arr, sz, sizeof(arr[0]), cmp_int);

	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

cmp_int函数:

int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);
}

bubble_qsort函数的实现

void bubble_qsort(void* base, int sz, int width, int (*cmp) (const void* p1 ,const void* p2))
{
	int i = 0;
	int j = 0;
	int h = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((void*)((char*)base + j * width), (void*)((char*)base + (j + 1) * width)) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

Swap函数的实现:

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

       现在我们将它们组合在一起:

#include <stdio.h>
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_qsort(void* base, int sz, int width, int (*cmp) (const void* p1 ,const void* p2))
{
	int i = 0;
	int j = 0;
	int h = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int main()
{
	int arr[10] = { 9,3,2,4,1,0,5,7,6,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

       运行结果:

6. 一些指针和数组的题目

6.1 一维数组

大家一起来看看它们分别会表现出什么样的结果呢?
int a[] = {1,2,3,4};

printf("%d\n",sizeof(a));

printf("%d\n",sizeof(a+0));

printf("%d\n",sizeof(*a));

printf("%d\n",sizeof(a+1));

printf("%d\n",sizeof(a[1]));

printf("%d\n",sizeof(&a));

printf("%d\n",sizeof(*&a));

printf("%d\n",sizeof(&a+1));

printf("%d\n",sizeof(&a[0]));

printf("%d\n",sizeof(&a[0]+1));

6.2 字符数组

一、非字符串

char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(arr));

printf("%d\n", sizeof(arr+0));

printf("%d\n", sizeof(*arr));

printf("%d\n", sizeof(arr[1]));

printf("%d\n", sizeof(&arr));

printf("%d\n", sizeof(&arr+1));

printf("%d\n", sizeof(&arr[0]+1));

printf("%d\n", strlen(arr));

printf("%d\n", strlen(arr+0));

printf("%d\n", strlen(*arr));

printf("%d\n", strlen(arr[1]));

printf("%d\n", strlen(&arr));

printf("%d\n", strlen(&arr+1));

printf("%d\n", strlen(&arr[0]+1));

二、字符串

char arr[] = "abcdef";

printf("%d\n", sizeof(arr));

printf("%d\n", sizeof(arr+0));

printf("%d\n", sizeof(*arr));

printf("%d\n", sizeof(arr[1]));

printf("%d\n", sizeof(&arr));

printf("%d\n", sizeof(&arr+1));

printf("%d\n", sizeof(&arr[0]+1));

printf("%d\n", strlen(arr));

printf("%d\n", strlen(arr+0));

printf("%d\n", strlen(*arr));

printf("%d\n", strlen(arr[1]));

printf("%d\n", strlen(&arr));

printf("%d\n", strlen(&arr+1));

printf("%d\n", strlen(&arr[0]+1));

三、字符指针指向字符串

char *p = "abcdef";

printf("%d\n", sizeof(p));

printf("%d\n", sizeof(p+1));

printf("%d\n", sizeof(*p));

printf("%d\n", sizeof(p[0]));

printf("%d\n", sizeof(&p));

printf("%d\n", sizeof(&p+1));

printf("%d\n", sizeof(&p[0]+1));

printf("%d\n", strlen(p));

printf("%d\n", strlen(p+1));

printf("%d\n", strlen(*p));

printf("%d\n", strlen(p[0]));

printf("%d\n", strlen(&p));

printf("%d\n", strlen(&p+1));

printf("%d\n", strlen(&p[0]+1));

6.3 二维数组

int a[3][4] = {0};

printf("%d\n",sizeof(a));

printf("%d\n",sizeof(a[0][0]));

printf("%d\n",sizeof(a[0]));

printf("%d\n",sizeof(a[0]+1));

printf("%d\n",sizeof(*(a[0]+1)));

printf("%d\n",sizeof(a+1));

printf("%d\n",sizeof(*(a+1)));

printf("%d\n",sizeof(&a[0]+1));

printf("%d\n",sizeof(*(&a[0]+1)));

printf("%d\n",sizeof(*a));

printf("%d\n",sizeof(a[3]));

💡做一下小总结,数组名的意义:

1. sizeof( 数组名 ) ,这里的数组名表示整个数组,计算的是整个数组的大小。
2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

6.4 指针笔试习题

第一题:
int main ()
{
    int a [ 5 ] = { 1 , 2 , 3 , 4 , 5 };
    int * ptr = ( int * )( & a + 1 );
    printf ( "%d,%d" , * ( a + 1 ), * ( ptr - 1 ));
    return 0 ;
}
// 程序的结果是什么?

第二题:
struct Test
{
        int Num ;
        char * pcName ;
        short sDate ;
        char cha [ 2 ];
        short sBa [ 4 ];
} * p ;
// 假设 p 的值为 0x100000 。 如下表表达式的值分别为多少?
// 已知,结构体 Test 类型的变量大小是 20 个字节
int main ()
{
        printf ( "%p\n" , p + 0x1 );//该结构体指针加1则跨过20个字节大小
        printf ( "%p\n" , ( unsigned long ) p + 0x1 );//跨过一个字节大小
        printf ( "%p\n" , ( unsigned int* ) p + 0x1 );//跨过四个字节大小
        return 0 ;
}

第三题:

int main ()
{
    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 ;
}
//程序的结果是什么呢?
 
       🔑我们 可以将[ x ]等价为*(p+x)来考虑,例如以上的ptr1[-1]可以当作*(ptr1 - 1)来看,那么在知道了这一点之后大家是否能够更加好地理解 [ ] 操作符呢?

第四题:

#include <stdio.h>
int main ()
{
    int a [ 3 ][ 2 ] = { ( 0 , 1 ), ( 2 , 3 ), ( 4 , 5 ) };
    int * p ;
    p = a [ 0 ];//a[0]等效为第一个数组地数组名,也即元素 0 所对应的地址
    printf ( "%d" , p [ 0 ]);// p[0] == *(p + 0)
   return 0 ;
}
🔎解读:
       这道题最容易忽略地一点就是数组元素以(逗号表达式)的形式表示,那么我们在初始化的时候只放入了 1、3、5这三个元素,最后这个二维数组就是这样的 { {1,3},{5,0},{0,0} }。因此,最后的结果为1。

第五题:

int main ()
{
    int a [ 5 ][ 5 ];
    int ( * p )[ 4 ];
    p = a ;
    printf ( "%p,%d\n" , & p [ 4 ][ 2 ] - & a [ 4 ][ 2 ], & p [ 4 ][ 2 ] - & a [ 4 ][ 2 ]);
    return 0 ;
}
🔎解读:
       根据上面说过的对[ ]的理解,我们可以将p[4][2]当作*(*(p+4)+2),a也类似的情况。接下来我们以画图的方式来解读它:
  

       因此这两个指针之间相差了四个元素,我们知道指针相减则看它们之间的元素个数,显然是4个,那么%d格式打印的差值即为 -4。

       而%p的形式则不太相同,地址值是以无符号数十六进制数的形式进行打印,-4的值以十六进制打印补码:

       事实也正是如此:

🍀 结语

       那么C语言中关于指针的相关知识就暂时告一段落了,在以上的文章中,我们一起学习了C语言中关于 进阶指针 的知识,在阅读了上述内容后,希望大家能够学有所得,我相信大家肯定能用我们一起学习的新知识来完成一些新的代码💕💕💕!

       最后,希望大家一切安好,保护好自己和家人,在当下能够好好把握自己的生活学习节奏,每天都有不错的心情!(送给大家代表幸运的四叶草)ʕु•̫͡•ʔु 🍀

  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的yui~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值