C语言指针就该这么学——C语言进阶【指针的进阶】

本期介绍:
本期主要讲解C语言指针如何轻松拿下,以各种面试真题来讲解C语言指针就该这么学

目录

  • 字符指针
    • ️基本介绍
    • ️面试题
  • 数组指针
    • ️基本介绍
    • ️&数组名和数组名
    • ️数组指针的使用
    • ️题目
  • 数组参数、指针参数
    • ️一维数组传参
      • 题目
    • ️二维数组传参
      • 题目
    • ️一维指针传参
    • ️二维指针传参
  • 函数指针
    • ️基本介绍
    • ️有趣的代码(来自《C陷阱和缺陷》)
  • 函数指针数组
    • ️基本认识和使用
  • 指向函数指针数组的指针
    • ️基本介绍和使用
  • 回调函数
    • ️基本介绍
    • ️qsort函数
      • qsort实现整型数据的排序
      • qsort实现对结构体数据的排序
      • 模拟实现qsort函数,实现一个冒泡排序的通用算法
  • ✨指针和数组笔试题解析(重点)
    • ️数组笔试题
      • 题一
      • 题二
      • 题三
      • 题四
      • 题五
      • 题六
    • ️指针笔试题
      • 题一
      • 题二
      • 题三
      • 题四
      • 题五
      • 题六
      • 题七
      • 题八

字符指针

️基本介绍

  • 字符指针用char*来定义

一般使用:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 's';
    return 0; 
}
  • ch的地址传到pc中,*pc(解引用pc)就为’w’(字符w),将其改变为’s’(字符s)

️面试题

#include 

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

输出为:
C语言指针就该这么学——C语言进阶【指针的进阶】_第1张图片
解释:

因为str1和str2都是数组,都开辟了一块空间来存储数组,并且表示的都是首元素的地址

str3和str4两个都是字符指针,定义的是同一个字符串的首元素地址,所以相等。并且是常量字符串。而且常量字符串是不能改变的,且只有一份

指针指向同一个字符串的时候,实际会指向同一块内存
而用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块

C语言指针就该这么学——C语言进阶【指针的进阶】_第2张图片

数组指针

️基本介绍

  • 一种指向数组指针本质是指针

辨别数组指针和指针数组:

int *p1[10];
int (*p2)[10];
P1和p2又表示什么呢?

由于[]的优先级要高于*号的,因此p1先和[10]结合,返回类型为int**类型,所以p1是一个指针数组

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

️&数组名和数组名

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

输出:
C语言指针就该这么学——C语言进阶【指针的进阶】_第3张图片

&数组名和数组名都表示首元素的地址,但是两者的本质不同

&arr表示整个数组的首地址,即首元素地址
arr表示数组首元素的地址

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

输出:
C语言指针就该这么学——C语言进阶【指针的进阶】_第4张图片

因为arr表示数组首元素的地址,因此arr+1就表示数组第二个元素的地址

而&arr表示的是整个数组的地址,也是首元素的地址,因此&arr+1表示跳出整个数组的首元素地址

C语言指针就该这么学——C语言进阶【指针的进阶】_第5张图片

️数组指针的使用

  • 数组指针指向的是数组,那数组指针中存放的应该是数组的地址
#include 
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    return 0; 
}
  • 但是我们很少这样子使用,一般对数组指针的使用都在传参之中
#include 

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;
    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,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收。

️题目

以下代码的含义是什么?
1. int arr[5];
2. int *parr1[10];
3. int (*parr2)[10];
4. int (*parr3[10])[5];

1. 整型数组
2. 整型指针的数组
3. 数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int类型
4. parr3是一个存储数组指针的数组,该数组能 够存放10个数值指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

数组参数、指针参数

️一维数组传参

题目

#include 
1. void test(int arr[])//ok?
{}
2. void test(int arr[10])//ok?
{}
3. void test(int *arr)//ok?
{}
4. void test2(int *arr[20])//ok?
{}
5. void test2(int **arr)//ok?
{}
int main()
{
 	int arr[10] = {0};
 	int *arr2[20] = {0};
 	test(arr);
 	test2(arr2);
}

这里的5个全部ok,全都是正确的(易)

  1. arr是一个有10个元素的数组,10个元素都是int类型的,当传参传入(1)的时候,传过去的是首元素的地址,而int arr[]就是接受了这个数组的地址,这[]里面的数字可以不写
  2. 跟(1)差不多,只不过加了[]里面的数字,就是限制了元素个数
  3. *arr说明是一个指针,而指针就是地址,所以是对的
  4. arr2是一个由20个元素的数组,20个元素都是int*类型,当传参传入(4)的时候,传过去的也是首元素的地址,而arr[20]就接受了这个地址,且类型是int*类型
  5. *arr说明是一个指针,指针就是地址,且类型是int*类型

️二维数组传参

题目

1. void test(int arr[3][5])//ok?
{}
2. void test(int arr[][])//ok?
{}
3. void test(int arr[][5])//ok?
{}
4. void test(int *arr)//ok?
{}
5. void test(int* arr[5])//ok?
{}
6. void test(int (*arr)[5])//ok?
{}
7. void test(int **arr)//ok?
{}
int main()
{
 	int arr[3][5] = {0};
 	test(arr);
}

正确ok(1 3 6),错误no(2 4 5 7)

  1. 简单(不讲解)
  2. 二维数组传参,参数可以写成数组,但列不能省略
  3. 行可以不知道,但列要知道
  4. arr传过去的是第一行的地址,而且是有5个元素的一维数组,而*arr只表示的是第一行的地址。二维数组传参,传过来的是数组首地址 —— 一维数组,不能匹配
  5. arr[5]接收到的是第一行的地址,放不了,这里是存放指针的数组不能匹配
  6. *arr表示第一行的地址,加了[5]就是有5个元素的一维数组,即传过来的地址
  7. **arr其实表示的是首元素的地址,即第一行的首元素的地址,二级指针不能匹配一维数组的地址

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

️一维指针传参

#include 

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

️二维指针传参

#include 

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

函数指针

️基本介绍

  • 指向函数的指针,存放函数地址的指针

首先来看一串代码:

#include 

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

int main()
{
 	printf("%p\n", test);
 	printf("%p\n", &test);
 	return 0; 
}

输出:

C语言指针就该这么学——C语言进阶【指针的进阶】_第6张图片

  • 这里我们发现函数名和&函数名取出来的地址相同

那我们可以得出结论:&函数名 == 函数名,同时我们要知道数组名 != &数组名
因此在调用函数名的地址,可以省略&符号

️有趣的代码(来自《C陷阱和缺陷》)

(*(void (*)())0)();

首先观察代码,我们可以发现里面能突破的只有常数0,如果它代表常数0,那前面的(类型)就是强转类型,发现强转为函数指针类型

void(*)()
void(*)()0
将强转类型拿下后:

在这里插入图片描述

  • 发现就是调用函数

调用0地址处的函数,该函数无参,返回类型是void
1. void(*)() - 函数指针类型
2. (void(*)())0 - 对0进行强制类型转换 - 被解释为一个函数的地址
3. *(void(*)())0 - 对0地址进行解引用操作
4. (*(void(*)())0)() - 调用0地址处的函数

代码二:

void (*signal(int , void(*)(int)))(int);
  1. signal 和()先结合,说明signal是函数名
  2. signal函数的第一个参数的类型是int类型,第二个参数的类型是函数指针类型该函数指针,指向一个参数为int,返回类型是void的函数
  3. signal函数的返回类型也是一个函数指针,该函数指针指向一个参数为int,返回类型为void的函数 signal是一个函数的声明
    C语言指针就该这么学——C语言进阶【指针的进阶】_第7张图片

函数指针数组

️基本认识和使用

  • 存放函数指针的数组,并且只能存放同类型的函数指针
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	int(*pf1)(int, int) = Add;
	int(*pf2)(int, int) = Sub;
	int(*pfArr[2])(int, int) = {Add, Sub};
	//pfArr - 函数指针数组 - 存放同类型的函数指针
	return 0;
}

指向函数指针数组的指针

️基本介绍和使用

  • 是一个指针,指针指向一个数组,数组的元素都是函数指针
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; 
}
int(*p)(int, int);//函数指针
int(*p2[4])(int, int);//函数指针的数组
int(*(*p3)[4])(int, int) = &p2;//取出的是函数指针数组的地址
//p3就是一个指向函数指针数组的指针

回调函数

️基本介绍

  • 回调函数就是一个通过函数指针调用的函数,将函数的指针(地址)作为参数传递另一个函数,当整个指针被用来调用其所指向的函数时,这就是回调函数

可以这么理解:有一个A函数,这里不是直接去调用A函数,而是先将A函数的地址传给B函数(这里的B函数的参数就是一个函数指针),然后通过B函数去调用A函数时,这样子就被称为回调函数
C语言指针就该这么学——C语言进阶【指针的进阶】_第8张图片

️qsort函数

  • qsort函数就是快速排序,什么类型都能排(整型数据、字符串 数据、结构体数据)

对于这种不熟悉的函数,不知道如何使用的话可以上查找即https://cplusplus.com/reference/
在上面可以查看到qsort的使用方法

C语言指针就该这么学——C语言进阶【指针的进阶】_第9张图片

void qsort(void* base, //base里面放的是待排序的第一个元素(对象)的地址.void* 无类型的指针,什么都可以放进去
			size_t num, //排序数据元素的个数
			size_t size,//数据中一个元素的大小,单位是字节
			int (*compar)(const void*, const void*));//是用来比较待排序数据中的2个元素的函数

qsort实现整型数据的排序

#include 
#include 

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

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void test1()
{
	//整型数据的排序
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//排序
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

int main()
{
	test1();
	return 0;
}

qsort实现对结构体数据的排序

struct Stu
{
	char name[20];
	int age;
};

int sort_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

int sort_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test2()
{
	//使用qsort函数排序结构体数据
	struct Stu s[] = { {"zhangsan", 30}, {"lisi", 34}, {"wangwu", 20} };
	//按照年龄来排序
	int sz = sizeof(s) / sizeof(s[0]);
	//qsort(s, sz, sizeof(s[0]), sort_by_age);
	//按照名字比较
	qsort(s, sz, sizeof(s[0]), sort_by_name);
}
int main()
{
	test2();
	return 0;
}

模拟实现qsort函数,实现一个冒泡排序的通用算法

  • 首先先来了解下冒泡排序
//假设写一个冒泡排序函数,让你排序字符串
//bubble_sort_str();
//
void bubble_sort(int arr[], int sz)
{
	升序
	int i = 0;
	冒泡排序的趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序
		int j = 0;
		for (j = 0; j < sz - i -1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				交换
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

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

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

冒泡排序就是两两比较,我这里是升序,所以小的在前,每次都相邻比较,每次都将最小的放在最后C语言指针就该这么学——C语言进阶【指针的进阶】_第10张图片

  • ✨模拟实现qsort函数
//模仿qsort函数,实现一个冒泡排序的通用算法(可以排各种数据)

struct Stu
{
	char name[20];
	int age;
};

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_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟的排序
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两个元素的比较
			//char*是加几就跳过几个字节
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

int sort_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		printf("%d ", arr[i]);
	}
}

void test3()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

void test4()
{
	struct Stu s[3] = { {"zhangsan", 30}, {"lisi", 34}, {"wangwu", 20} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), sort_by_age);
	//bubble_sort(s, sz, sizeof(s[0]), sort_by_name);
}

int main()
{
	test3();
	test4();
	return 0;
}

✨指针和数组笔试题解析(重点)

数组名的意义:

  1. sizeof(数组名),这里要数组名单独存放在sizeof内部,这里的数组名才表示整个数组,不然表示的是首元素的地址,这里计算的是整个数组的大小
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
  3. 除此之外所有的数组名都表示首元素的地址
  4. sizeof里面计算的,只要是地址,那么计算出来就是4或8,和电脑的位数有关

️数组笔试题

题一

一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16=4*4
printf("%d\n", sizeof(a + 0));//4或8,a+0是第一个地址,因为a+0了,a并不是单独放在sizeof内部,那么a就表示首元素的地址,+0那么还是首元素的地址,那就是4或8,看电脑位数
printf("%d\n", sizeof(*a));//4, a也是表示首元素的地址,解引用后就是1
printf("%d\n", sizeof(a + 1));//4或8,a+1就是第二个元素的地址
printf("%d\n", sizeof(a[1]));//4

printf("%d\n", sizeof(&a));//4或8,&a虽然是整个数组的地址,但是也是地址,因此sizeof计算的就是一个地址的大小
printf("%d\n", sizeof(*&a));//16,&a是整个数组的地址,解引用后找到的就是整个数组,那么就是16
&a -- int(*p)[4] -- &a
printf("%d\n", sizeof(&a + 1));//4或8,&a表示整个数组的地址,+1跳过一个数组,下一块空间的地址,还是地址
printf("%d\n", sizeof(&a[0]));//4或8,第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4或8,第二个元素的地址

题二

字符数组
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));//错误error,首元素的地址,解引用就是字符a,传过去就是97,不是个合法的地址
printf("%d\n", strlen(arr[1]));//错误error,b为98,同上
printf("%d\n", strlen(&arr));//随机值,整个数组的地址
printf("%d\n", strlen(&arr + 1));//随机值,跳过一个数组的地址
printf("%d\n", strlen(&arr[0] + 1));//随机值,b的地址

error的情况:在模拟strlen函数的时候,我们可以知道 int my_strlen(const char* str)它的参数是一个指针,应该接收地址,而这里传的是一个字符,因此error。
调试可以发现错误
C语言指针就该这么学——C语言进阶【指针的进阶】_第11张图片

题三

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

printf("%d\n", sizeof(arr));//6,
printf("%d\n", sizeof(arr + 0));//4或8,arr表示首元素地址,+0还是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,因arr是首元素的地址,*arr解引用就是a,
printf("%d\n", sizeof(arr[1]));//1,arr[1]就是b
printf("%d\n", sizeof(&arr));//4或8,取出这个地址
printf("%d\n", sizeof(&arr + 1));//4或8,&arr是整个数组的地址,+1跳到了f后面的地址,但还是地址
printf("%d\n", sizeof(&arr[0] + 1));//4或8,&arr[0]就是a的地址,+1就是b的地址,还是地址

题四

char arr[] = "abcdef";
[a,b,c,d,e,f,\0]
printf("%d\n", strlen(arr));//6,首元素地址
printf("%d\n", strlen(arr + 0));//6,首元素地址
printf("%d\n", strlen(*arr));//error,将a传过去了,97作为地址有问题
printf("%d\n", strlen(arr[1]));//error,同上,只不过传的是98的地址
printf("%d\n", strlen(&arr));//6,起始元素的地址,还是从首元素开始
printf("%d\n", strlen(&arr + 1));//随机值,跳出整个数组的地址,\0也跳过去了,所以就是随机值
printf("%d\n", strlen(&arr[0] + 1));//5,b的地址,从b开始数

printf("%d\n", sizeof(arr));//7,总大小,算\0
printf("%d\n", sizeof(arr + 0));//4或8,首元素地址+0还是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,解引用就是字符a
printf("%d\n", sizeof(arr[1]));//1,解引用就是字符b
printf("%d\n", sizeof(&arr));//4或8,整个数组的地址,还是地址
printf("%d\n", sizeof(&arr + 1));//4或8,跳过整个数组的地址,还是地址
printf("%d\n", sizeof(&arr[0] + 1));//4或8,b的地址,还是地址

题五

char* p = "abcdef";
a b c d e f \0,p存的是a的地址

printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//error,*p找到的是a,传进去是97,会出错
printf("%d\n", strlen(p[0]));//error,同上理
printf("%d\n", strlen(&p));//随机值,找的是p的首地址,p里面存的只是a的地址,因此没有\0,所以随机
printf("%d\n", strlen(&p + 1));//随机值,同上理
printf("%d\n", strlen(&p[0] + 1));//5

printf("%d\n", sizeof(p));//4或8,p是地址
printf("%d\n", sizeof(p + 1));//4或8,p+1就是b的地址,还是地址
printf("%d\n", sizeof(*p));//1,拿出来a,大小就是1个字节
printf("%d\n", sizeof(p[0]));//1,将字符串当作数组来访问,p[0] = *(p+0),所以还是找到a
printf("%d\n", sizeof(&p));//4或8,取得还是地址
printf("%d\n", sizeof(&p + 1));//4或8,取的是跳出整个p的地址,但还是地址
printf("%d\n", sizeof(&p[0] + 1));//4或8,取出的是第二个元素b的地址

&p就是取指针变量p的地址
&p就是取指针变量p的首地址,而p里面存的只有a的地址,没有\0,所以随机值

题六

二维数组
int a[3][4] = { 0 };

printf("%d\n", sizeof(a));//3*4*4 = 48....3*4*sizeof(int)
printf("%d\n", sizeof(a[0][0]));//4,第一行第一个元素
printf("%d\n", sizeof(a[0]));//16
printf("%d\n", sizeof(a[0] + 1));//4或8,解释:a[0]作为数组名,并没有单独放在sizeof内部,
								 //也没有取地址&,而是与1结合,
								 //所以a[0]就代表第一行第一个的地址,+1就是第二个元素的地址

printf("%d\n", sizeof(*(a[0] + 1)));//4,解释:对(a[0]+1)第一行第二个元素的地址解引用就是第二个元素,是int类型

printf("%d\n", sizeof(a + 1));//4或8,解释:a并没有单独放在sizeof内部,因此就作为二维数组首元素的地址,
							  //二维数组首元素就是第一行,+1就是第二行的地址

printf("%d\n", sizeof(*(a + 1)));//16,解释:a+1是第二行的地址,所以解引用是第二行,*(a+1)<-->a[1]
			    				 //所以计算的就是第二行的大小

printf("%d\n", sizeof(&a[0] + 1));//4或8,解释:a[0]是第一行的数组名,取地址&a[0],取出的就是第一行的地址
								  //+1就是第二行的地址

printf("%d\n", sizeof(*(&a[0] + 1)));//16,解引用拿到的就是第二行

printf("%d\n", sizeof(*a));//16,a没有单独放在sizeof中,所以a表示首元素的地址,即第一行的地址,解引用就是第一行

printf("%d\n", sizeof(a[3]));//16,a[3]在数组里不存在,a[3]其实是第四行数组名(如果有的话)
							 //所以其实不存在,也能通过类型计算大小的

注意:
1. sizeof内部的表达式时不计算的!
2. sizeof不会导致越界
C语言指针就该这么学——C语言进阶【指针的进阶】_第12张图片


补充:sizeof的用法

int main()
{
	short s = 5;
	int a = 4;
	printf("%d\n", sizeof(s = a + 6));//2
	printf("%d\n", s);//5
	return 0;
}

由于sizeof内部不进行计算,因此只看最后输出为什么类型即可,最后输出为short类型,即2字节大小
因为sizeof内部不进行计算,所以s没有变化,所以s还是为5,打印即为5

️指针笔试题

题一

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

C语言指针就该这么学——C语言进阶【指针的进阶】_第13张图片
&a表示整个数组的首地址,因此+1就是跳过整个数组
而a只表示数组的首元素地址,+1就是表示第二个元素的地址

题二

由于还没学习结构体,这里告知结构体的大小是20个字节
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);//0x100014
	printf("%p\n", (unsigned long)p + 0x1);//0x100001
	printf("%p\n", (unsigned int*)p + 0x1);//0x100004
	return 0;
}

C语言指针就该这么学——C语言进阶【指针的进阶】_第14张图片

题三

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);//4 2000000
				//*(ptr+(-1)) --> *(ptr-1)
	return 0;
}

C语言指针就该这么学——C语言进阶【指针的进阶】_第15张图片
C语言指针就该这么学——C语言进阶【指针的进阶】_第16张图片

题四

/int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//用了小括号括起来的,就是逗号表达式,只取最后
	//					1     3       5
	int* p;
	p = a[0];
	printf("%d", p[0]);//1
	return 0;
}

数组情况C语言指针就该这么学——C语言进阶【指针的进阶】_第17张图片

题五

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;//int(*)[5]
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//FFFFFFFC  -4
	return 0;
}

C语言指针就该这么学——C语言进阶【指针的进阶】_第18张图片

题六

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));//10 5
	return 0;
}

C语言指针就该这么学——C语言进阶【指针的进阶】_第19张图片
注意:二维数组的首地址就是第一行的地址

题七

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

C语言指针就该这么学——C语言进阶【指针的进阶】_第20张图片

题八

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

-------------------------------------------开始状态-------------------------------------------
C语言指针就该这么学——C语言进阶【指针的进阶】_第21张图片
-------------------------------------------**++cpp-------------------------------------------
C语言指针就该这么学——C语言进阶【指针的进阶】_第22张图片
-------------------------------------------*- -*++cpp+3-------------------------------------------
C语言指针就该这么学——C语言进阶【指针的进阶】_第23张图片
-------------------------------------------*cpp[-2] + 3-------------------------------------------
C语言指针就该这么学——C语言进阶【指针的进阶】_第24张图片
-------------------------------------------cpp[-1][-1] + 1-------------------------------------------
C语言指针就该这么学——C语言进阶【指针的进阶】_第25张图片

若是本文有出处,请各位小伙伴们留言哦~,看到会及时回复,另外制作不易,一键三连!!!

你可能感兴趣的:(C语言,c语言,算法,c++,经验分享)