C语言进阶之指针的进阶

简介: 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

8c606566e06d4c67bacb697cb8e66e5e.png


指针的主题,我们在C语言初阶博客已经接触过了,我们知道了指针的概念:


1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2.指针的大小是固定的4/8个字节(32位平台/64位平台)。

3.指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

4.指针的运算。

下面,我们继续探讨指针的高级主题


1. 字符指针


在指针的类型中我们知道有一种指针类型为字符指针 char* ;

一般使用:


int main()
{
  char ch = 'a';
  char *pc = &ch;
  *pc = 'a';
  return 0;
}


还有一种使用方式如下:


int main()
{
  const char* pstr = "hello C.";
  printf("%s\n", pstr);
  return 0;
}


那么这里是把一个字符串放到pstr指针变量里了吗?

其实并不是哦,这里实际上是把字符串的首字符也就是h的地址存放到指针变量pstr中。


我们看看下面这道关于字符指针的面试题


#include <stdio.h>
int main()
{
  char str1[] = "hello bit.";
  char str2[] = "hello bit.";
  const char* str3 = "hello bit.";
  const char* str4 = "hello bit.";
  if (str1 == str2)
    printf("str1 and str2 are same\n");
  else
    printf("str1 and str2 are not same\n");
  if (str3 == str4)
    printf("str3 and str4 are same\n");
  else
    printf("str3 and str4 are not same\n");
  return 0;
}

93b637d5d8b346e9bfb57e30f95d42e2.png


首先数组名代表数组的首元素地址,所以这里的str1和str2分别存的是对应数组的首字符地址,他们并不相同,所以第一个输出结果是str1 and str2 are not same,这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。


2. 指针数组


前面在初阶C语言时我们就讲过指针数组

如下:


int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组


3. 数组指针


3.1 数组指针的定义


数组指针是指针?还是数组?

答案是:指针。

我们已经熟悉:

整形指针: int * p; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针。

那么下面代码哪个是数组指针?


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


答案是int (*p2)[10];


p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。


要注意的是:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合


3.2 &数组名VS数组名


对于数组

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?


我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

我们看一段代码:


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

d565dc9605f348cbab1fb4dbaef16144.png


我们可以看到数组名和&数组名打印的地址是一样的,难道两个是一样的吗?

再看下一段代码:


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

366f001a749f433aa657033b6b0bd53a.png


根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40


3.3 数组指针的使用


那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

看代码:


#include <stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
  int(*p)[10] = &arr;
  return 0;
}


把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写代码

如下:


#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
}
void print_arr2(int(*arr)[5], int row, int col)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++)
  {
    for (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);
  print_arr2(arr, 3, 5);
  return 0;
}


数组名arr,表示首元素的地址

但是二维数组的首元素是二维数组的第一行

所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址

可以数组指针来接收


我们再来看看下面代码分别代表什么意思


int arr[5];//数组
int *parr1[10];//指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//数组指针数组


4. 数组参数、指针参数


4.1 一维数组传参


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?
{}
int main()
{
  int arr[10] = { 0 };
  int* arr2[20] = { 0 };
  test(arr);
  test2(arr2);
}


这几种传递方式都是可以的


4.2 二维数组传参


void test(int arr[3][5])//ok
{}
void test(int arr[][])//不可以
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//不可以
{}
void test(int* arr[5])//ok
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//不可以
{}
int main()
{
  int arr[3][5] = { 0 };
  test(arr);
}


总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。

因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。


4.3 一级指针传参


#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]);
  print(p, sz);
  return 0;
}


4.4 二级指针传参


#include <stdio.h>
void test(int** ptr)
{
  printf("num = %d\n", **ptr);
}
int main()
{
  int n = 10;
  int* p = &n;
  int** pp = &p;
  test(pp);
  test(&p);
  return 0;
}


5. 函数指针


先看代码


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


bcb168e4256346fc924990195dc6be37.png


不难看出函数名即为函数地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?


#include <stdio.h>
void test()
{
  printf("hehe\n");
}
void (*pfun1)();


pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。


6. 函数指针数组


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


int *arr[10];
//数组的每个元素是int*


那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?


int (*parr1[10])();


parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的用途:转移表

示例:计算器常规写法


#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");
      break;
    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;
}


这样代码就不显得冗余了


7. 指向函数指针数组的指针


指向函数指针数组的指针是一个 指针,指针指向一个数组 ,数组的元素都是函数指针 ;

那么该如何定义呢,看下面代码:


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


8. 回调函数


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


我们以库函数种的qsort函数为例,我们先看此函数的定义


_ACRTIMP void __cdecl qsort(
    _Inout_updates_bytes_(_NumOfElements * _SizeOfElements) void*  _Base,
    _In_                                                    size_t _NumOfElements,
    _In_                                                    size_t _SizeOfElements,
    _In_                _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
    );


1b9112ff22484c4293604e967ab66666.png


第一个形参为void* 是因为其能够容纳任意类型的指针,

最后一个 _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction实际上就是一个回调函数,我们在使用qsort函数时,自己还需要再写一个_CompareFunction函数对不同类型排序。

示例:


#include<stdlib.h>
#include <stdio.h>
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;
  qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
  for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  return 0;
}


其中int_cmp函数即为qsort调用的_CoreCrtNonSecureSearchSortCompareFunction _CompareFunction


278e9727b5754870b011feb91d9c7f17.png


需要注意的是,这种默认的写法一般为升序,若想改为降序,可以将p1和p2互换,如下代码:


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


我们再看对结构体数组的排序


#include<stdlib.h>
#include <stdio.h>
#include <string.h>
struct Stu
{
  char name[20];
  int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
  return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test1()
{
  struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
  int sz = sizeof(arr) / sizeof(arr[0]); 
  printf("初始序列:> ");
  for (int i = 0; i < sz; i++)
  {
    printf("%s,%d     ", arr[i].name, arr[i].age);
  }
  printf("\n");
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
  printf("按年龄排序:> ");
  for (int i = 0; i < sz; i++)
  {
    printf("%s,%d     ", arr[i].name, arr[i].age);
  }
  printf("\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
  struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
  printf("按名字排序:> ");
  for (int i = 0; i < sz; i++)
  {
    printf("%s,%d     ", arr[i].name, arr[i].age);
  }
}
int main()
{
  test1();
  test2();
  return 0;
}


656ba593870e4f7998744626a63e2856.png


根据同样的原理,我们还可以写出适应多类型的冒泡排序回调函数

示例:


#include<stdio.h>
void Swap(void* p1, void* p2, int size)
{
  for (int i = 0; i < size; i++)
  {
    char tmp = *((char*)p1 + i);
    *((char*)p1 + i) = *((char*)p2 + i);
    *((char*)p2 + i) = tmp;
  }
}
void Bubble_Sort(void* base, int num, int size, int(*cmp)(void*,void*))
{
  int i = 0;
  int j = 0;
  for (i = 0; i < num - 1; i++)
  {
    for (j = 0; j < num - i - 1; j++)
    {
      if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
        Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
    }
  }
}
int Int_Sort(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
void Int_Print(int* arr,int sz)
{
  for (int i = 0; i < sz; i++)
    printf("%d ", arr[i]);
}
void Test1()
{
  int arr[] = { 7,6,5,4,8,9,3,1,2 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("初始序列:> ");
  Int_Print(arr, sz);
  printf("\n");
  Bubble_Sort(arr, sz, sizeof(arr[0]), Int_Sort);
  printf("排序后  :> ");
  Int_Print(arr,sz);
}
int main()
{
  Test1();
  return 0;
}


246d223770fc43c98c342afc07f7f6e4.png


9. 指针和数组笔试题解析


以下所有代码均为64平台编译


9.1 一维数组


int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//sizeof中数组名代表整个数组
printf("%d\n",sizeof(a+0));//数组名+0为第一个元素的地址,也就是指针,指针大小就是4/8个字节,当前为64位,所以是8
printf("%d\n",sizeof(*a));//对数组名进行解引用操作,获取指向数组第一个元素
printf("%d\n",sizeof(a+1));//a+1代表第二个元素的地址
printf("%d\n",sizeof(a[1]));//代表第二个元素
printf("%d\n",sizeof(&a));//对数组名取地址,代表的是指针
printf("%d\n",sizeof(*&a));//&再*解引用等于没做操作,还是整个数组
printf("%d\n",sizeof(&a+1));//对数组名取地址再+1代表的是该数组之后的地址
printf("%d\n",sizeof(&a[0]));//代表第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));//代表第二个元素的地址


d59c03bdbdc24f62a7faacd8bd9366ef.png


9.2 字符数组


char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr+0));//数组名+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));//代表第二个元素的地址

557eda7c40444df5b2bc01d54d4972a8.png


char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找,产生的结构就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素,就是'a'-97
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参
//strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//随机值-6,减去了上面6个元素的长度
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值

b94d0430936041a194b126992008ada0.png


char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr + 0));//arr + 0是首元素的地址
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过一个数组的地址,4/8
printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8

e21356e7e52e4f0ea4fd6be5e91c14b3.png


char arr[] = "abcdef";
printf("%d\n", strlen(arr));//整个字符串的长度
printf("%d\n", strlen(arr+0));//首元素的地址开始,所以结果同上
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//整个数组的地址,还是从首元素开始
printf("%d\n", strlen(&arr+1));//整个数组后开始计算,所以是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素地址开始算


012c6c751d6f4604b845f7d6a7152122.png


9.3 字符指针


也可以说是字符数组,数组本身也是指针


char *p = "abcdef";
printf("%d\n", sizeof(p));//p为指针变量,大小为4/8
printf("%d\n", sizeof(p+1));//p+1是'b'的地址
printf("%d\n", sizeof(*p));//*p 就是字符a
printf("%d\n", sizeof(p[0]));//同上
printf("%d\n", sizeof(&p));//*p的地址
printf("%d\n", sizeof(&p+1));//*p之后的地址
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址


9fa100f7664e4af2a0b067fcaacfb477.png


char *p = "abcdef";
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));//从第二个字符开始算


c08f09751acc47cd9ca1c8511c047a30.png


9.4 二维数组


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));
//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&
//a[0]表示数组首元素的地址,也就是a[0][0]的地址
//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节


f1bb51fabd7947928c955d0fa6e27347.png


int a[3][4] = { 0 };
printf("%d\n", sizeof(*(a[0] + 1)));//计算的是就是第一行第2个元素的大小
printf("%d\n", sizeof(a + 1));//a是数组首元素的地址,是第一行的地址 int(*)[4],a+1 就是第二行的地址
printf("%d\n", sizeof(*(a + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址 int(*)[4],&a[0]+1 是第二行的地址 int(*)[4]
printf("%d\n", sizeof(*(&a[0] + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(*a));//计算的是第一行的大小
printf("%d\n", sizeof(a[3]));//计算的是一行的大小,并不存在越界,因为实际并没有访问内存

3f3eec84ec9b4f928f63faa4200712c1.png


总结:

数组名的意义:


1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3.除此之外所有的数组名都表示首元素的地址。


10. 指针笔试题


10.1 笔试题1


int main()
{
  int a[5] = { 1, 2, 3, 4, 5 };
  int* ptr = (int*)(&a + 1);
  printf("%d,%d", *(a + 1), *(ptr - 1));
  return 0;
}


程序的结果是什么?


0365f074a52b4601b437ef02d5d60656.png


*(a + 1)访问的第二个元素,*ptr是跳过整个数组后的地址


10.2 笔试题2


struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p= (struct Test*)0x10000000;
int main()
{
  printf("%p\n", p + 0x1);
  printf("%p\n", (unsigned long)p + 0x1);
  printf("%p\n", (unsigned int*)p + 0x1);
  return 0;
}


假设p 的值为0x10000000。 如下表达式的值分别为多少?

已知,结构体Test类型的变量大小是20个字节


8142f027115b4449ad798777607fdb86.png


第一个结果是跳过整个结构体,所以直接加20,地址是以十六进制打印,所以是10000014

第二个结果是先将结构体地址强转为长整形,而整形计算则是直接加1

第三个结果是先将结构体地址强制转换为指针,加1则跳过一个指针的大小,即4/8个字节


10.3 笔试题3


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

02ac2c242c1a45e98965c20a5c0535ae.png

8bb2c148614b4f4aa889f0e976c58eed.png



10.4 笔试题4


int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) };
  int* p;
  p = a[0];
  printf("%d", p[0]);
  return 0;
}

f0fb6bd5d4c04da883b5efff80828885.png


这里需要注意的是,在初始化时,我们只初始化了前3个值,因为这里面放的是()而不是{},所以编译时只将逗号表达式中的数字,即1,3,5,而后都应是0;所以打印指向第一行的值,就只打印a[0][0],即1。


10.5 笔试题5


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

9a43c7a7a678401fb915a3557fb6896e.png

2592dfe47a7644c6a31efeb1e12830e7.png


10.6 笔试题6


int main()
{
  int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  int* ptr1 = (int*)(&aa + 1);
  int* ptr2 = (int*)(*(aa + 1));
  printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
}


8b36da0dd67a4e59803f4ebb00e3d717.png


&aa+1跳过的是整个数组,所以再-1,打印的整数值即为aa数组最后一个元素

aa+1是跳过二维数组第一行,即6的起始地址,,所以再-1,打印的整数值即为aa数组第一行最后一个元素


10.7 笔试题7


int main()
{
  char* a[] = { "work","at","alibaba" };
  char** pa = a;
  pa++;
  printf("%s\n", *pa);
  return 0;
}

0715446a574a4c7086fbdd967cc196c3.png


首先char* a[]存储的就是三个首字母的地址,而char** pa存储的是指针数组中首元素w的地址,所以pa++,pa指向的就是第二个元素a的地址,解引用后打印即为at。


10.8 笔试题8


int main()
{
  char* c[] = { "ENTER","NEW","POINT","FIRST" };
  char** cp[] = { c + 3,c + 2,c + 1,c };
  char*** cpp = cp;
  printf("%s\n", **++cpp);
  printf("%s\n", *-- * ++cpp + 3);
  printf("%s\n", *cpp[-2] + 3);
  printf("%s\n", cpp[-1][-1] + 1);
  return 0;
}

a3308937afd44747817e867e0c89154f.png


cf6659ee07a541dcb39c8960030aaf79.png


结语


有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!

制作不易,如有不正之处敬请指出

感谢大家的来访,UU们的观看是我坚持下去的动力

在时间的催化剂下,让我们彼此都成为更优秀的人吧!!


0306564fcedb4d87bbfe644801c70b2e.png

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
40 1
|
1月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
49 0
|
1月前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
18 2
|
1月前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
1月前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
14 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
15 1
下一篇
无影云桌面