c语言指针的进阶

指针的进阶

文章目录

  • 指针的进阶
  • 前言
  • 一、字符指针
  • 二、指针数组
  • 三、数组指针
    • 1.定义及理解
    • 2.&数组名VS数组名
    • 3.数组指针的使用
  • 四、数组参数、指针参数
    • 1.一维数组传参
    • 2.二维数组传参
    • 3.一级指针传参
    • 4.二级指针传参
  • 五、函数指针
  • 六、函数指针数组
  • 七、指向函数指针数组的指针
  • 八、回调函数
    • qsort函数的使用:
    • 模拟实现 qsort函数:
  • 九、指针和数组面试题解析
    • **指针笔试题**
  • 总结


前言

区别于之前指针的初步了解,增添了更多内容


一、字符指针

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

一般使用:

int main()
{
     
  char ch='w';
  char *pc=&ch;
  *pc='w';
  return 0;
}
int main()
{
     
  char* pstr = "hello bit.";
  printf("%s\n",pstr);
  return 0;
}

代码 char* pstr = “hello bit.”;错误理解为字符串hello bit 存入字符指针pstr中
本质为字符串hello bit 首字符的地址放入pstr中

例如:下面代码的执行结果为( )

#include 
int main()
{
     
  char str1[] = "hello bit.";
  char str2[] = "hello bit.";
  char *str3 = "hello bit.";
  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; 
}

A.str1 and str2 are same str3 and str4 are same
B.str1 and str2 are same str3 and str4 are not same
C.str1 and str2 are not same str3 and str4 are same
D.str1 and str2 are not same str3 and str4 are not same

答案为(C)

分析:str3和str4指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域。当几个指针指向同一个字符串时,他们实际上会指向同一块内存。但是用相同的常量字符串去初始化不同的数组就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

注意:字符常量区是只读的,str3,str4指向相同的字符串只需要保存一份就够了。(减少成本)

二、指针数组

指针数组是一个存放指针的数组,归根结底是个数组。

int * char arr1[10]

分析:中括号优先级高,所以为一个数组。数组就一定有元素,所以数组内存储int*类型数据。称为整形指针数组

char * arr2[10]

同理,一个存储 char* 的数组,称为一级字符指针数组。
内容可以指向1.某种字符2.指向某个字符串

char ** arr3[10]

同理,一个存储char**的数组,称为二级字符指针数组。
内容可以指向在后面详解

三、数组指针

1.定义及理解

首先数组指针是指针。

我们知道:
整形指针:int * pint;能够指向整形数据的指针。
浮点型指针:float * pf;能够指向浮点型数据的指针。
那么,数组指针为能够指向数组的指针

1. p1,p2分别是什么?

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

分析:
int * p1[10];为整形指针数组,数组内保存指针
int ( * p2)[10];为数组指针。p2先和*结合,说明p2是一个指针变量,然后指针指向一个大小为10个整形的数组

2.

int main()
{
     
  int(*p1)[10]=NULL;
  int(*p2)[11]=NULL;
  p1=p2;
}

编译出现告警:
在这里插入图片描述
首先前两行一定正确属于变量定义,指针指向空。根据告警显示数组是p1=p2出错,因为p1和p2指向的数组不同,因此数组的下标也是数组的一部分。

3.
下面写法是否正确?

int main()
{
     
  int(*p1)[10]=NULL;
  int a[10];
  p1 = a;
}

答案:错误。
分析: p1 = a ; 其中 a 做右值不代表整个数组而是首元素地址,所以 a 实际上是int * 型,而 p1 为 int(*)[10]型。右侧是一个整形指针,左侧是一个是指向整形数组的指针,因此他们类型不同。正确做法应该改成,p1=&a.数组的地址就是数组指针类型。

2.&数组名VS数组名

通过下列代码完成理解
代码如下:

int main()
{
     
  int arr[10];
  printf("%p\n",arr);
  printf("%p\n",&arr);
  printf("%p\n",arr+1);
  printf("%p\n",&arr+1);
}

运行结果为:
c语言指针的进阶_第1张图片
可见数组名和&数组名打印地址一样,但实际意义不同。
因为对指针加一表示对其所指数据类型的大小加一,很显然arr+1相对于arr是4,&arr+1相对于&arr是40。

由此得出:arr是数组名,数组名表示首元素地址(大部分情况)
&arr表示数组的地址,而不是数组首元素地址

补充:arr+1等价于&arr[1],*(arr+1)等价于arr[1],

3.数组指针的使用

数组指针指向的是数组,那么数组指针中存放的就是数组的地址

深入理解,如下所示并理解,

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

一、p[1]等价于*(p+1)也就是p指向下一个元素再进行解引用,因为p指向a,所以*(p+1)表示越过整个a数组再进行解引用,是一种越界行为。
二、p[0][1] 中的 p[0] 等价于 *(p+0) 整个数组相当于 数组名也就是首元素地址就是a,p[0][1] 等价于 *(*p+0)+1 也就是a[1]。

四、数组参数、指针参数

只要传参就要发生拷贝。数组是先降维再拷贝(降维成指针,指向其内部元素类型的指针。在数组传参时传的是数组名相当于是首元素地址),拷贝的仍是指针;指针传参会发生形参实例化,发生临时拷贝。

1.一维数组传参

#include 
#include 
void test(int arr[])//ok? yes
{
     }
void test(int arr[10])//ok?yes
{
     }
void test(int *arr)//ok?yes
{
     }
void test2(int *arr[20])//ok?yes
{
     }
void test2(int **arr)//ok?yes
{
     }
int main()
{
     
 int arr[10] = {
     0};
 int *arr2[20] = {
     0};
 test(arr);
 test2(arr2);
}

以上几种方式都是正确的

2.二维数组传参

void test(int arr[3][5])//ok?yes
{
     }
void test(int arr[][])//ok?no,二位数字下标不能省略,数组类型不明确
{
     }
void test(int arr[][5])//ok?yes
{
     }
void test(int *arr)//ok?no,传入的是数组指针
{
     }
void test(int* arr[5])//ok?no,是指针数组
{
     }
void test(int (*arr)[5])//ok?yes,将为成二级指针
{
     }
void test(int **arr)//ok?no,是等价于int *arr[5],是指针数组
{
     }
int main()
{
     
 int arr[3][5] = {
     0};
 test(arr);
}

3.一级指针传参

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

问题:第7行的p和第13行的p一样吗?
答案:不一样
分析:指针传参没有拷贝数组,但是会有指针临时变量,只是内容一样指向空间一样。

下列函数的参数为一级指针时能接收什么参数?

void test1(int *p)
{
     }
//test1函数能接收什么参数?
void test2(char* p)
{
     }
//test2函数能接收什么参数?

答案:test1可以是一维数组,也可以是整型变量传其地址
test2可以是单个字符、字符串以及字符数组

4.二级指针传参

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

test(pp);和test(&p);实际含以上是相等的; test(pp);是传址传参,test(&p);是传值传参(传值传参还是传址传参取决于传递的是数据还是地址)

问题:当函数的参数为二级指针的时候,可以接受什么参数?
以void test(char **p)为例
答案:可以传char * *,&char *,char *arr[]

五、函数指针

我们要知道函数也是有地址的,函数的地址通常是众多代码块中的起始地址

1.获得函数地址的方式可以是函数名和&函数

以main函数地址为例,可得到两个结果相同的值。(函数名只能做右值不能做左值)

{
     
 printf("main addr:%p\n",&main);
 printf("main addr:%p\n",main);
}

在这里插入图片描述
保存函数地址的变量称为函数指针变量,即函数指针

**2.**如何判断是否为函数指针,看优先级决定(首先判断是否为指针,然后考虑指针指向的是什么,是函数还是数组,如果是数组,就要考虑数组里放什么,以此类推思考)

int main()
{
     
  int *p();
  int (*p)();
  int (*p[3])();
}

int *p()为函数声明,p先和()优先结合所以一定是个函数然后返回值为int *
int (*p)();为函数指针变量,( *p)一定是个指针,然后指向一个函数,并且函数的返回值是int型。
int (*p[3])()为函数指针数组,p[3]数组存放的是指针类型数据,指针指向返回值为int的函数。
通过 [ ] , ( ) , *的优先级来理解

3.

int main()
{
     
  int (*p)()=main;
  printf("main addr:%p\n",&main);
  printf("main addr:%p\n",main);
  printf("main addr:%p\n",p);
}

int (*p)()=main此时函数指针变量p赋值mian地址,即p指向mian,因此三个得到的结果应该相同
c语言指针的进阶_第2张图片
4.

int main()
{
     
  int (*p)()=main;
  printf("main addr:%p\n",&main+1);//错误写法
  printf("main addr:%p\n",main+1);//错误写法
  printf("main addr:%p\n",p+1);//错误写法
}

由于函数大小(代码块)无法确定,所以无法通过指针指向下一个,不能对函数指针进行加操作
5.

int show()
{
     
  printf("hello show!\n");
  return 0;
}
int main()
{
     
  int (*p)()=show;
  (*p)();
  p();
}

两种表示方式都能调用函数,推荐使用第二种
c语言指针的进阶_第3张图片
6. 补充知识点
(1)函数地址是函数入口地址
(2)main函数也可以递归
(3)用指针解引用用的全都是指针的右值

  int a=10;
  int*p=&a;
  *p=100;//*p做左值,p做右值
  int b=*p;//*p做右值,p做右值
  p=200;//p做左值

(4)*p与 *(&a)是等价关系。
假设&a==0x123456,则 *(&a)可以写成 *(int *)0x123456,也就等于 *p。所以对于指针解引用来说p就是这个常数0x123456,即指针是地址。

7. 经典代码学习

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

void(*) ( )是函数指针,然后类比上面提到的 * ( int *)0x123456得到,代码1中0是地址,而前面的 *是解引用。
所以 ( void ( * )( ) ) 0相当于一个地址常数,剩下的类比成( * p ) ( )函数调用
因此 ( * (void ( * )( ) ) 0 ) ( ) ;相当于是一个从000…00地址开始向上的函数调用

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

signal(int , void(*)(int))相当于一个函数且第一个参数为int型,第二个参数为函数指针,返回值是void ( *)(int)函数指针。剩下一个void ( *)(int);参数为int返回值为void的函数指针。

六、函数指针数组

把函数的地址存在一个数组中,这个数组就叫函数指针数组

int (*parr1[10]])();//函数指针数组
int *parr2[10]();//什么也不是
//因为int *parr2[10]结合完成后,剩余()没法处理
int (*)() parr3[10];//什么也不是

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

例子:计算器
1.首先编写一个菜单,包含了加减乘除四种运算及退出计算器

void Menu()
{
     
	printf("##############################\n");
	printf("#1.Add                2.Sub  #\n");
	printf("#3.Mul                4.Div  #\n");
	printf("#                     0.Exit #\n");
	printf("##############################\n");
	printf("Please Select #");
}

2.写出对应MyExit, MyAdd, MySub, MyMul, MyDiv五个函数

int MyAdd(int x, int y)
{
     
	return x + y;
}
int MySub(int x, int y)
{
     
	return x - y;
}
int MyMul(int x, int y)
{
     
	return x * y;
}
int MyDiv(int x, int y)//考虑y为0
{
     
	if (0 == y){
     
		printf("Warning:div zero!\n ");//区别于数据结果为-1的提醒
		return -1;
	}
	return x /y;
}
int MyExit(int x, int y)
{
     
	printf("ByeBye!\n");
	system("pause");
	exit(0);//直接退出函数
}

3.整个函数的逻辑结构:首先通过函数指针数组来保存所需函数,将所需要输出的算式±*/号通过指针lable获得。进行多次计算用whlie死循环来实现,退出程序用0.Exit中的exit(0)函数实现。将用户选择的操作存入select中,如果输入选项为0~5合法则调用函数实现,否则提醒用户输入异常重新输入。

int main()
{
     
	int(*p[NUM])(int, int) = {
      MyExit, MyAdd, MySub, MyMul, MyDiv };
	const char *lable = "+-*/";
	while (1){
     
		Menu();
		int select = 0;
		scanf("%d", &select);
		if (select >= 0 && select < 5){
     
			if (select == 0){
     
				p[select](0, 0);//0仅仅是占位符
			}
			else{
     
				int x = 0;
				int y = 0;
				printf("Please Enter Your Data#");
				scanf("%d %d", &x, &y);
				int z = p[select](x, y);
				printf("%d %c %d =%d\n", x, lable[select - 1], y, z);
			}
		}
		else{
     
			printf("输入异常!请重新输入\n");
		}
	}
	return 0;
}

4.因此,完整的计算机程序为

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define NUM 5
void Menu()
{
     
	printf("##############################\n");
	printf("#1.Add                2.Sub  #\n");
	printf("#3.Mul                4.Div  #\n");
	printf("#                     0.Exit #\n");
	printf("##############################\n");
	printf("Please Select #");
}

int MyAdd(int x, int y)
{
     
	return x + y;
}
int MySub(int x, int y)
{
     
	return x - y;
}
int MyMul(int x, int y)
{
     
	return x * y;
}
int MyDiv(int x, int y)//考虑y为0
{
     
	if (0 == y){
     
		printf("Warning:div zero!\n ");//区别于数据结果为-1的提醒
		return -1;
	}
	return x /y;
}
int MyExit(int x, int y)
{
     
	printf("ByeBye!\n");
	system("pause");
	exit(0);//直接退出函数
}
int main()
{
     
	int(*p[NUM])(int, int) = {
      MyExit, MyAdd, MySub, MyMul, MyDiv };
	const char *lable = "+-*/";
	while (1){
     
		Menu();
		int select = 0;
		scanf("%d", &select);
		if (select >= 0 && select < 5){
     
			if (select == 0){
     
				p[select](0, 0);//0仅仅是占位符
			}
			else{
     
				int x = 0;
				int y = 0;
				printf("Please Enter Your Data#");
				scanf("%d %d", &x, &y);
				int z = p[select](x, y);
				printf("%d %c %d =%d\n", x, lable[select - 1], y, z);
			}
		}
		else{
     
			printf("输入异常!请重新输入\n");
		}
	}
	return 0;
}

结果为:
c语言指针的进阶_第4张图片

七、指向函数指针数组的指针

void test (const char* str)//返回值是void 参数是const char*
{
     
  printf("&s/n",str);
}
int main()
{
     
 //函数指针pfun
 void (*pfun)(const char*) = test;//函数指针指向test
 
 //函数指针的数组pfunArr
  void (*pfunArr[5])(const char* str);//函数指针数组
  pfunArr[0] = test;
 
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[10])(const char*) = &pfunArr;//函数指针数组指针
 return 0;
}

八、回调函数

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

qsort函数的使用:

我们首先了解一个qsort函数,为了无类型排序(void*)
void qsort(void* base, size_ num,size_ size,int(compar)(const void,const void*))
void* base 表示数组首元素地址
size_ num 所排元素个数
size_ size 每个元素的大小(在这个函数中与数据类型无关)
int(compar)(const void,const void*) 用户编译比较方法

#include 
//qosrt函数的使用者得实现一个比较函数
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");

编写实现对Int型,Double型数据的排序
c语言指针的进阶_第5张图片
编写实现字符串排序
c语言指针的进阶_第6张图片

模拟实现 qsort函数:

c语言指针的进阶_第7张图片

九、指针和数组面试题解析

//一维数组
int a[] = {
     1,2,3,4};
printf("%d\n",sizeof(a));//16,整个数组大小
printf("%d\n",sizeof(a+0));//4,a+0是表达式因此a为首元素地址+0还是首元素地址
printf("%d\n",sizeof(*a));//4,整个数组解引用,由于首元素地址和整个数组地址相同,因此解引用为首元素
printf("%d\n",sizeof(a+1));//4,首元素地址+1,指向第二个元素地址
printf("%d\n",sizeof(a[1]));//4,第二个元素大小
printf("%d\n",sizeof(&a));//4,整个数组的地址但仍为地址
printf("%d\n",sizeof(*&a));//16,整个数组取地址再解引用,因此为整个数组的大小
printf("%d\n",sizeof(&a+1));//4,整个数组的下一个地址但仍是地址
printf("%d\n",sizeof(&a[0]));//4,等价于sizeof(a+0)
printf("%d\n",sizeof(&a[0]+1));//4,第二个元素的地址

c语言指针的进阶_第8张图片
sizeof只用确定其最终指向类型,而strlen属于函数需要考虑形参问题
size_t strlen (const char* str)

//字符数组
char arr[] = {
     'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6,整个数组
printf("%d\n", sizeof(arr+0));//4,首元素地址
printf("%d\n", sizeof(*arr));//1,首元素地址解引用为首元素
printf("%d\n", sizeof(arr[1]));//1,首元素
printf("%d\n", sizeof(&arr));//4,数组的地址
printf("%d\n", sizeof(&arr+1));//4,下一个数组的地址
printf("%d\n", sizeof(&arr[0]+1));//4,第二个元素的地址,等价于arr+1和&arr[1]
printf("%d\n", strlen(arr));//随机值,且大于等于6
printf("%d\n", strlen(arr+0));//随机值,且大于等于6
printf("%d\n", strlen(*arr));//报错,arr是char*,*arr是char
printf("%d\n", strlen(arr[1]));//报错,arr[1]是char
printf("%d\n", strlen(&arr));//随机值,且大于等于6会有warning
printf("%d\n", strlen(&arr+1));//随机值-6,且有warnning,整个数组的下一个地址
printf("%d\n", strlen(&arr[0]+1));//随机值-1

c语言指针的进阶_第9张图片
&arr和&arr+1的警告:
可以理解是因为取地址arr代表的是整个数组的地址,而我们只需要首元素的地址
虽然都是char*,但是含义不一样
c语言指针的进阶_第10张图片

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7,这个数组大小加上'\0'
printf("%d\n", sizeof(arr+0));//4,第一个元素地址
printf("%d\n", sizeof(*arr));//1,首元素大小
printf("%d\n", sizeof(arr[1]));//1,第一个元素
printf("%d\n", sizeof(&arr));//4,数组的地址
printf("%d\n", sizeof(&arr+1));//4,整个数组的下一个地址
printf("%d\n", sizeof(&arr[0]+1));//4,第二个元素地址
printf("%d\n", strlen(arr));//6,整个数字长度
printf("%d\n", strlen(arr+0));//6,从 第一个元素开始
printf("%d\n", strlen(*arr));//报错
printf("%d\n", strlen(arr[1]));//报错
printf("%d\n", strlen(&arr));//6,有warning
printf("%d\n", strlen(&arr+1));//随机数,整个数组的下一个地址开始,有warning
printf("%d\n", strlen(&arr[0]+1));//5,从第二个元素开始

c语言指针的进阶_第11张图片

char *p = "abcdef";
printf("%d\n", sizeof(p));//4,p是个指针
printf("%d\n", sizeof(p+1));//4,是指针
printf("%d\n", sizeof(*p));//1,指针解引用,指向的目标是a,为一个字节
printf("%d\n", sizeof(p[0]));//1,等价于*(p+0)=*p
printf("%d\n", sizeof(&p));//4,是个地址,指针变量p的地址,char**
printf("%d\n", sizeof(&p+1));//4,栈上的另一个变量仍是地址
printf("%d\n", sizeof(&p[0]+1));//4,等价于*(p+0)+1=*p+1,是b的地址
printf("%d\n", strlen(p));//6,形参实例化
printf("%d\n", strlen(p+1));//5,指向b
printf("%d\n", strlen(*p));//报错
printf("%d\n", strlen(p[0]));//报错
printf("%d\n", strlen(&p));//随机值+warning,传入二级指针p的地址,但与字符串地址都为地址所以持续向下寻找
printf("%d\n", strlen(&p+1));//随机值+warning,同理,越过p变量开始访问
printf("%d\n", strlen(&p[0]+1));//5,从b开始

c语言指针的进阶_第12张图片

//二维数组
int a[3][4] = {
     0};
printf("%d\n",sizeof(a));//48,整个数组
printf("%d\n",sizeof(a[0][0]));//4,第一个元素
printf("%d\n",sizeof(a[0]));//16,a[0]中包含4个元素大小都是4字节
printf("%d\n",sizeof(a[0]+1));//4,等价于&a[0][1],取地址
printf("%d\n",sizeof(*(a[0]+1)));//4,等价于a[0][1],为整形
printf("%d\n",sizeof(a+1));//4,首元素地址加一为第二个数组地址
printf("%d\n",sizeof(*(a+1)));//16,等价于a[1]
printf("%d\n",sizeof(&a[0]+1));//4,等价于a+1,为第二个数组地址
printf("%d\n",sizeof(*(&a[0]+1)));//16,等价于a[1]
printf("%d\n",sizeof(*a));//16,数组名解引用,首元素地址为首元素等价于a[0]
printf("%d\n",sizeof(a[3]));//16,但要注意越界不可被写入

c语言指针的进阶_第13张图片

总结: 数组名的意义:

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

指针笔试题

笔试题1:

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;
}
//程序的结果是什么?

在这里插入图片描述
笔试题2:

这里告知结构体的大小是20个字节
struct Test
{
     
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
     
 printf("%p\n", p + 0x1);
 //指针加一=加上其所指向类型的大小(是20个字节再转为16进制14) 
 printf("%p\n", (unsigned long)p + 0x1);//无符号整数加1就是加1
 printf("%p\n", (unsigned int*)p + 0x1);//同理unsigned int* 加4
 return 0;
}

c语言指针的进阶_第14张图片

笔试题3:

int main()
{
     
    int a[4] = {
      1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);//整个数组的地址加一然后强转,为下一个数组的首地址
    int *ptr2 = (int *)((int)a + 1);//将a的地址强转成整数再加一再强转成int*型,等价于地址向后移动一个字节,需要考虑内存字节存储情况,需要考虑大小端问题,如下所示
    printf( "%x,%x", ptr1[-1], *ptr2);
    //ptr1[-1]等价于*(ptr1-1),因此等于a[]的最后一个元素的地址解引用为元素4.
    return 0;
}

c语言指针的进阶_第15张图片
笔试题4:

#include 
int main()
{
     
    int a[3][2] = {
      (0, 1), (2, 3), (4, 5) };//(,)相当于逗号表达式
    //因此等价于 int a[3][2]={1,3,5};
    int *p;
    p = a[0];
    printf( "%d", p[0]);//等价于*(p+0)等价于*p,即输出第一个元素1
 return 0;
}

在这里插入图片描述

笔试题5:
重点在于画图

int main()
{
     
    int a[5][5];
    int(*p)[4];//指针p指向一个数组大小是4元素类型是整形
    p = a;//将二位数组的第一行的地址赋给p
    //p的类型为int(*)[4],a的类型是int(*)[5],所以会有警告
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //&p[4][2]等价于*(*(p+4)+2)
    //首先是*(p+4)
    //p每次跳过4个元素,跳了4次
    //*( +2),即向后移动两个元素地址即最终指向地址
    //注意:地址从左向右依次增大
    return 0;
}

c语言指针的进阶_第16张图片

笔试题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));
    //数组名等于首元素地址,对于二维数组加一表示从第一行转到第二行地址
    //*(aa+1)等价于aa[1],即第二行元素首地址,元素6的地址
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    //*(ptr1 - 1)地址减一解引用为元素10
    //*(ptr2 - 1)地址减一解引用为元素5
    return 0;
}

在这里插入图片描述

笔试题7:

#include 
int main()
{
     
 char *a[] = {
     "work","at","alibaba"};//指针数组,存了三个字符串的首地址
 char**pa = a;//二级指针,存了a的地址
 pa++;
 printf("%s\n", *pa);//指针解引用
 return 0;
}

c语言指针的进阶_第17张图片
笔试题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);
 //++cpp改变原指向地址,指向cp中第二个元素地址然后解引用得到c+2,c+2为“POINT”的首地址。打印内容为POINT
 printf("%s\n", *--*++cpp+3);
 //++cpp改变原指向地址,指向cp中第三个元素地址然后解引用,内容为c+1,然后又--使得指向c,然后再解引用指向“ENTER”首地址,再+3指向E。打印内容为ER。
 printf("%s\n", *cpp[-2]+3);//等价于*(*(cpp-2))+3
 //cpp-2不改变原指向,*(*(cpp-2))指向c的“FIRST”的首地址,再+3指向S。打印内容为ST
 printf("%s\n", cpp[-1][-1]+1);//等价于*(*(cpp-1)-1)+1
 //cpp指向cp数组中内容为c+1的地址,然后*(cpp-1)拿到内容为c+2指向“POINT”但是又-1再解引用因此指向“NEW”,再+1指向E。打印内容为NW。
 return 0;
}

++,–实际是一次赋值,会改变原来指向地址
c语言指针的进阶_第18张图片


总结

以上为个人学习指针的进阶总结内容,有问题欢迎与大家讨论交流~

你可能感兴趣的:(c语言,指针)