C语言指针详解与指针进阶

目录

1.字符指针

2.指针数组

3.数组指针

3.1定义

3.2&数组名VS数组名

3.3用途

4.数组参数,指针参数

4.1一维数组传参

4.2二维数组传参

5.函数指针

5.1定义

5.2格式

5.3经典代码示例

5.3.1示例1

6.函数指针数组(又称为转移表:提供函数跳转功能)

6.1定义

6.2定义格式

6.3示例(仍以上文计算器为例)

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

7.1定义格式

8.回调函数

8.1定义

8.2经典案例(qsort库函数,在中,默认升序)

9.指针和数组笔试题解析(32位操作系统)

9.1一维数组

9.2字符数组

9.3二维数组

10.指针笔试题

1.字符指针

const char * p = "abcdef"//表示把字符串首字符的地址赋给了p,字符串实际内容为abcdef\0
//如果不加const可能会报警告,不安全,因为常量字符串无法修改
//但p的权限却变大了,会允许修改,但若修改,系统崩溃

经典代码:

#include <stdio.h>
int mian(){
	char * p1 = "abcde";
	char * p2 = "abcde";
	char str1[] = "abcde";
	char str2[]	= "abcde";
	if(p1 == p2)
		printf("p1 == p2\n");
	else
		printf("p1 != p2\n");
	if(str1 == str2)
		printf("str1 == str2\n");
	else
		printf("str1 != str2\n");
	return 0;
}

原因:1.常量字符串放在内存的只读数据区里面,因此相同的常量内存只会存一份;第二种创建数组需要自己的独立空间,两个数组的地址肯定不一样

2.指针数组

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

3.数组指针

3.1定义

指向数组的指针,用来存放数组的地址,与二级指针没一点关系

3.2&数组名VS数组名

&数组名得到的是数组的地址

数组名是数组首元素的地址

例如:

int arr[10];
int (*p)[10] = &arr; //定义数组指针时,数组元素个数不可省

3.3用途

一般可用于二维数组传参

例如:

void Print1(int (*p)[5],int r){
    int i = 0;
    int j = 0;
    for(i = 0;i < r;i++){
        for(j = 0;j < 5;j++){
            printf("%d ",p[i][j]);l        
        } 
        printf("\n");
    }                        
}
int main(){
    int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
    Print1(arr,3);        
    return 0;
}

4.数组参数,指针参数

4.1一维数组传参

格式:

//1.1void test(int arr[]){}
//1.2void test(int arr[10]){}
//1.3void test(int * arr){}
//2.1void test2(int * arr[10]){}
//2.2void test2(int ** arr){} //因为此时首元素的地址就是一个二级地址,首元素是一级指针
int main(){
    int arr[10] = {0};
    int * arr2[10] = {0};
    test(arr);
    test(arr2);
    retutn 0;
}

4.2二维数组传参

格式:

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

5.函数指针

5.1定义

指向函数的指针(函数没有首地址和起始地址的概念,就是函数的地址)

5.2格式

函数返回类型 (*指针变量名) (函数形参类型) = &函数名称(&加与不加无影响)

int ADD(int a,int b){
    return a+b;
}
int mian(){
    //ADD自身就是地址,&ADD也是他的地址,两种表达方式没有区别
    int (*p)(int,int) = &Add;//&加与不加都一样,形参变量名加与不加没影响
    int (*p)(int,int) = ADD;//二者相同
    int ret = p(3,5); //使用格式1
    int ret = (*p)(3,5);//使用格式2
    //这二者没有区别,*没有意义,只是表达的更明确,加几个都可以,但如果要加*一定要加括号
    return 0;
}

//函数指针重命名举例
typedef int(*p_tf)(int,int); //即将int(*)(int,int)这种函数指针类型重命名为p_tf

5.3经典代码示例

5.3.1示例1

(* ( void(*)() ) 0)();  //一次函数调用

解释:

  1. 在括号内加个p,变成void(*p)(),此时里面的p是函数指针,因此void(*)()相当于函数指针类型,类似于整型指针类型int *和字符指针类型char *,在这里起强制类型转换的意思,将0转换成函数指针,指向地址为0的函数
  2. 接着调用地址为0的无参无返回值的函数

5.3.2示例2

void (* signal( int, void(*)(int) ) )(int);  //三次函数调用

解释:

  1. void(*)(int)是无返回值函数指针类型,是作为signal函数指针的参数
  2. signal是第二层的函数指针,且返回值也为函数指针
  3. 最外层调用由signal函数指针返回的函数指针,且返回的函数指针只有一个整型参数

5.3.3示例3(用途举例)(实现计算器且避免数据冗余)

#include <stdio.h>
int Add(int x, int y) {
    return x+y;
}
int Sub(int x, int y) {
    return x - y;
}
int Mul(int x, int y) {
    return x * y;
}
int Div(int x, int y) {
    return x / y;
}
void Mule() {
    printf("***********************\n");
    printf("****1.加法   2.减法****\n");
    printf("****3.乘法   4.除法****\n");
    printf("****    0.退出    *****\n");
}
void calc(int (*pd)(int, int)) {
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入两个操作数->");
    scanf("%d %d", &x, &y);
    ret = pd(x, y);
    printf("%d\n", ret);
}
int main() {
    int input = 0;
    do {
        Mule();
        scanf("%d", &input);
        switch (input) {
        case 1:
            calc(Add);
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        case 0:
            printf("退出计算器\n");
            break;
        default:
            printf("输入错误\n");
        }
    } while (input);
    return 0;
}

6.函数指针数组(又称为转移表:提供函数跳转功能)

6.1定义

存放函数指针的数组

6.2定义格式

以之前四个函数为例:

int ( * arr[4])( int, int) = { Add, Sub, Mul, Div}; //arr就是函数指针数组

6.3示例(仍以上文计算器为例)

#include <stdio.h>
int Add(int x, int y) {
    return x + y;
}
int Sub(int x, int y) {
    return x - y;
}
int Mul(int x, int y) {
    return x * y;
}
int Div(int x, int y) {
    return x / y;
}
void Mule() {
    printf("***********************\n");
    printf("****1.加法   2.减法****\n");
    printf("****3.乘法   4.除法****\n");
    printf("****    0.退出    *****\n");
}
int main() {
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    int (*p[5])(int, int) = { 0, Add,Sub,Mul,Div }; //保证以后扩充代码时
    //可以不用switch语句进行扩充,方便修改,且使代码简洁
    do {
        Mule();
        scanf("%d", &input);
        if (input > 0 && input < 5) {
            printf("请输入两个操作数->");
            scanf("%d %d", &x, &y);
            ret = p[input](x, y);
            printf("%d\n", ret);
        }
        else if (input == 0) {
            printf("退出计算器\n");
        }
        else {
            printf("输入错误\n");
        }
    } while (input);
    return 0;
}

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

7.1定义格式

int ( * parr[4])( int, int) = { Add, Sub, Mul, Div}; //arr就是函数指针数组
int ( * (*pparr)[4])( int, int) = &parr;  //指向函数指针数组的指针

8.回调函数

8.1定义

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

8.2经典案例(qsort库函数,在中,默认升序)

void qsort (void* base,  //需要排序的数组的首地址,不确定元素类型 
size_t num,   //需要排序的数组元素个数
size_t size,  //每个元素的大小(字节数)
int (*compar)(const void*,const void*));  //自定义比较函数的指针,回调函数
//以上是qsort函数的声明及对每个形参的解释
//
int compar (const void* p1, const void* p2)  //自定义比较函数定义
{
     return (*(int *)p1 - *(int *)p2);   
}  //以整型元素为例
//void * 是无具体类型的指针,可以接受任意类型的地址
//但无法解引用和+-整数,因为这两个行为都需要知道字节数
//所以需要采用强制类型转换,把void *类型强制转换成其他类型 
//也就是需要用户自己定义的原因
//
//以结构体类型元素为例
typedef struct Stu {
	char name[20];
	int age;
}Stu;


int compar_name(const void* p1, const void* p2)  //自定义比较函数定义
{
	return strcmp(((Stu*)p1) -> name , ((Stu*)p2) -> name);  
        //注意操作符优先级顺序
	//强制类型转换优先级小于->
}  //比较名字

int compar_age(const void* p1, const void* p2)  //自定义比较函数定义
{
	return (((Stu*)p1)->age - ((Stu*)p2)->age);  //注意操作符优先级顺序
	//强制类型转换优先级小于->
}  //比较年龄 

int main() {
	Stu stu[] = { {"张三",20},{"李四",21},{"王五",19} };
	int i = 0;
	qsort(stu, 3, sizeof(stu[0]), compar_name);
	for (i = 0; i < 3; i++) {
		printf("%s %d\n", stu[i].name,stu[i].age);
	}
	printf("\n");
	qsort(stu, 3, sizeof(stu[0]), compar_age);
	for (i = 0; i < 3; i++) {
		printf("%s %d\n", stu[i].name, stu[i].age);
	}
	return 0;
}

//模拟qsort实现通用冒泡排序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>  //qsort库函数所在位置

//得到需要交换的两个元素的起始地址和元素大小,一个字节一个字节进行交换
void Swap(void * buf1,void * buf2,int size) {
	char temp = 0;
	int i = 0;
	for (i = 0; i < size; i++) {
		temp = *((char*)buf1 + i);
		*((char*)buf1 + i) = *((char*)buf2 + i);
		*((char*)buf2 + i) = temp;
	}
}

//冒泡升序排序
void Bubble_sort(const void* base, const int count, const int size, int(*compar)(const void*, const void*)) {
	int i = 0;
	int j = 0;
	int flag = 1;
	//趟数
	for (i = 0; i < count - 1; i++) {
		//一趟比较的次数
		for (j = 0; j < count - 1 - i; j++) {
			//强制类型转换为指向一个字符的指针,操作数据
			if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0) {
				Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
				flag = 0;
			}
		}
		if (flag == 1)
			break;
		flag = 1;
	}
}

int compar_int(const void* p1, const void* p2)  //自定义比较函数定义
{
	return (*(int*)p1 - *(int*)p2);
}  //以整型元素为例
//void * 是无具体类型的指针,可以接受任意类型的地址
//但无法解引用和+-整数,因为这两个行为都需要知道字节数
//所以需要采用强制类型转换,把void *类型强制转换成其他类型
//也就是需要用户自己定义的原因

int compar_str(const void* p1, const void* p2)  //自定义比较函数定义
{
	return strcmp((char*)p1, (char*)p2);
}

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


int compar_name(const void* p1, const void* p2)  //自定义比较函数定义
{
	return strcmp(((Stu*)p1)->name, ((Stu*)p2)->name);  //注意操作符优先级顺序
	//强制类型转换优先级小于->
}  //以结构体类型元素为例

int compar_age(const void* p1, const void* p2)  //自定义比较函数定义
{
	return (((Stu*)p1)->age - ((Stu*)p2)->age);  //注意操作符优先级顺序
	//强制类型转换优先级小于->
}  //以结构体类型元素为例

void test1() {
	int i = 0;
	int num[10] = { 9,8,7,6,5,4,3,2,1,0 };
	Bubble_sort(num, 10, 4, compar_int);
	for (i = 0; i < 10; i++) {
		printf("%d ", num[i]);
	}
	printf("\n");
}

void test2() {
	int i = 0;
	char str[3][10] = { "zhangsan","lisi","wangwu" };
	Bubble_sort(str, sizeof(str) / sizeof(str[0]), sizeof(str[0]), compar_str);
	for (i = 0; i < 3; i++) {
		printf("%s ", str[i]);
	}
	printf("\n");
}

void test3() {
	Stu stu[] = { {"张三",20},{"李四",21},{"王五",19} };
	int i = 0;
	Bubble_sort(stu, 3, sizeof(stu[0]), compar_name);
	for (i = 0; i < 3; i++) {
		printf("%s %d\n", stu[i].name, stu[i].age);
	}
	printf("\n");
	Bubble_sort(stu, 3, sizeof(stu[0]), compar_age);
	for (i = 0; i < 3; i++) {
		printf("%s %d\n", stu[i].name, stu[i].age);
	}
	printf("\n");
}

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

9.指针和数组笔试题解析(32位操作系统)

9.1一维数组

int a[] = {1,2,3,4};
printf("%d\n" ,sizeof(a));  
 //打印16,整个数组的byte大小
printf("%d\n" ,sizeof(a+0));  
 //打印4,此时a既没有单独放在sizeof中,也没有&,就表示首地址,+0还是首地址,4byte
printf("%d\n" ,sizeof(*a));
 //打印4,此时a仍没有单独放sizeof中,表示首地址,解引用后计算首元素的大小,int型4byte
printf("%d\n" ,sizeof(a+1));
 //打印4,理由同第四行代码
printf("%d\n",sizeof(a[1]));
 //打印4,得到第二个元素
printf("%d\n" ,sizeof(&a));
 //打印4,取整个数组地址,指针大小,4byte
printf("%d\n" ,sizeof(*&a));
 //打印16,整个数组大小
printf("%d\n" ,sizeof(&a+1));
 //打印4,取地址+1仍是地址,4byte
printf("%d\n" ,sizeof(&a[0]));
 //打印4,首元素地址,4byte
printf("%d\n",sizeof(&a[0]+1));
 //打印4,第二个元素地址,4byte

9.2字符数组

char arr[] = { 'a' ,'b' ,'c' , 'd' , 'e','f’}; 
//单个写字符且数组未标明大小,不自动补\0
printf("%d\n", sizeof(arr));
 //打印6,字符串长度为6byte
printf("%d\n", sizeof(arr + O));
 //打印4,此时arr既没有单独放在sizeof中,也没有&,就表示首地址
 //+0还是首地址,4byte
printf("%d\n", sizeof(*arr));
 //打印1,此时arr仍没有单独放sizeof中,表示首地址
 //解引用后计算首元素的大小,char型1byte
printf("%d\n", sizeof(arr[1]));
 //打印1,表示第二个元素,char型1byte
printf("%d\n", sizeof(&arr));
 //打印4,取整个数组的地址,地址4byte
printf("%d\n", sizeof(&arr + 1));
 //打印4,指向arr数组之后的相同数组大小地址位置,同样4byte
printf("%d\n", sizeof(&arr[0] +1));
 //打印4,表示arr数组第二个元素的地址
printf("%d\n",sizeof(arr[0] + 1));
 //打印4,char型+int型发生整型提升,提升为int型,4byte
printf("%d\n", strlen(arr));
 //随机数,数组内无\0,越界访问
printf("%d\n", strlen(arr + 0));
 //随机数,与第18行结果相同,数组内无\0,越界访问
printf("%d\n", strlen(*arr));
 //error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(arr[1]));
 //error,strlen库函数参数为指针,98属于野指针,读取98的地址发生访问冲突
printf("%d\n", strlen(&arr));
 //随机数,但与第18行代码结果相同,strlen访问时一个byte一个byte访问
printf("%d\n", strlen(&arr + 1));
 //比第18行代码随机数少6,&arr+1直接指向了arr数组之后的位置,跳过了arr数组
printf("%d\n", strlen(&arr[0] + 1));
 //比第18行代码随机数少1,&arr[0]+1指向了arr数组第二个元素的位置
 
char arr[] = "abcdef"; //直接赋值字符串,末尾补\0,7个字符长度
printf("%d\n",sizeof(arr));
 //打印7,字符串长度为7byte
printf("%d\n", sizeof(arr + O));
 //打印4,此时arr既没有单独放在sizeof中,也没有&,就表示首地址
 //+0还是首地址,4byte
printf("%d\n", sizeof(*arr));
 //打印1,此时arr仍没有单独放sizeof中,表示首地址
 //解引用后计算首元素的大小,char型1byte
printf("%d\n", sizeof(arr[1]));
 //打印1,表示第二个元素,char型1byte
printf("%d\n", sizeof(&arr));
 //打印4,表示数组的地址,4byte
printf("%d\n", sizeof(&arr + 1));
 //打印4,表示arr数组之后的地址
printf("%d\n", sizeof(&arr[0] +1));
 //打印4,表示数组第二个元素地址
printf("%d\n", strlen(arr));
 //打印6,传递字符串首元素的地址,字符串\0前的字符有6个
printf("%d\n", strlen(arr + 0));
 //打印6,传递的还是字符串首元素地址
printf("%d\n", strlen(*arr));
 //error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(arr[1]));
 //error,strlen库函数参数为指针,98属于野指针,读取98的地址发生访问冲突
printf("%d\n", strlen(&arr));
 //打印6,实参是数组的地址,但strlen函数计算时时一个byte一个byte计算
 //相当于还是首元素的地址
printf("%d\n", strlen(&arr + 1));
 //随机数,传递的是arr数组之后位置的地址,不确定之后的内容 
printf("%d\n", strlen(&arr[0] +1));
 //打印5,传递的是arr数组第二个元素的地址
 
char* p = "abcdef" ;  //p作为字符指针,只保存了a的地址,放在栈区
 //"abcdef\0"是常量字符串,放在常量区(只读数据区)
printf("%d\n",sizeof(p));
 //打印4,p是指针,4byte(区别于字符数组)
printf("%d\n", sizeof(p + 1));
 //打印4,p+1仍是指针,指向字符串第二个字符位置
printf("%d\n", sizeof(*p));
 //打印1,表示字符'a'
printf("%d\n", sizeof(p[0]));
 //打印1,表示字符'a'
printf("%d\n", sizeof(&p));
 //打印4,表示p的地址,地址4byte
printf("%d\n", sizeof(&p + 1));
 //打印4,&p是二级指针,&p+1会跳过栈区的四个字节
printf("%d\n", sizeof(&p[0] + 1));
 //打印4,&p[0]表示'a'的地址,+1表示'b'的地址
printf("%d\n", strlen(p));
 //打印6,实参是字符串首地址
printf("%d\n", strlen(p + 1));
 //打印5,p+1表示'b'的地址
printf("%d\n", strlen(*p));
 //error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(p[0]));
 //error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(&p));
 //随机数,&p是栈区内p的地址,而栈区内的数据我们无法得知
printf("%d\n", strlen(&p + 1));
 //随机数,&p+1是栈区内p之后四个byte的地址,而栈区内的数据我们无法得知
 //且&p和&p+1之间有四个byte,无从得知这四个字节中是否存在\0
 //如果存在在什么位置,所以这两个随机数的关系无法确认
printf("%d\n", strlen(&p[0] +1));
 //打印5,实参是字符串第二个字符地址

9.3二维数组

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,计算首行的大小
printf("%d\n", sizeof(a[0] +1));
 //打印4,由于a[0]不是单独的,有了+1后a[0]就表示第一行第一个元素的地址
 //a[0]+1表示第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));
 //打印4,表示第一行第二个元素
printf("%d\n", sizeof(a + 1));
 重点//打印4,a没有单独存放,所以表示第一行的地址
 //+1后就表示第二行的地址,是地址,不是第二行
printf("%d\n", sizeof(*(a + 1)));
 重点//打印16,a+1表示第二行的数组地址,解引用后表示第二行
printf("%d\n", sizeof(&a[0] +1));
 重点//打印4,a[0]表示第一行的数组名,&a[0]就表示首元素的地址
 //+1后表示第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));
 重点//打印16,与上一行代码相比,解引用后表示第二行
printf("%d\n", sizeof(*a));
 //打印16,a不是单独出现,所以a表示首元素的地址,即第一行数组的地址
 //解引用后表示第一行
printf("%d\n", sizeof(a[3]));
 重点//打印16,sizeof不访问内容,它只拿到传递的实参的类型
 //而a[3]的类型是int [4]

10.指针笔试题

笔试题1:

int main()
{
  int a[5] = {5, 4, 3, 2, 1};
  int *ptr = (int *)(&a + 1); //&a+1跳过整个数组,指向1的后面,强制类型转换后-1只移动四个byte
  printf( "%d,%d", *(a + 1), *(ptr - 1)); //一个指向第二个元素地址,一个指向最后一个元素地址
  return 0;
}

笔试题2:

struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p = (struct Test*)0×100000;    //结构体*表示指向该结构体变量的指针变量类型
//假设p 的值为日x100000。如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//x86
//
int main()
{
    printf("%p\n", p + 0x1);  //0x100000->0x100014
    //0x1表示十六进制的1,0x100000+1,跳过一个结构体类型的大小,20byte
    printf("%p\n",(unsigned long)p+0x1);  //0x100000->0x100001
    //强制类型转换为unsigned long类型,+1后跳过一个byte
    printf("%p\n",(unsigned int*)p + 0x1);  //0x100000->0x100004
    //强制类型转换为unsigned int*类型,+1后跳过4个byte
    return 0;
}

笔试题3:

int main()
{
    int a[4] = { 1,2,3,4 };  //栈区内数组地址由低到高
    //0x00000001,0x00000002,小端存储0x02000000
    int* ptr1 = (int*)(&a + 1); //ptr1指向了4之后的地址
    int* ptr2 = (int*)((int)a + 1); //(int)a+1是把首地址强制转换为整型再+1
    //再强制转换为int *,相当于向后移动一个字节后的地址
    printf("%×,%x", ptr1[-1],*ptr2);
    //ptr1[-1]就表示4(0x00000004),*ptr2就表示0x02000000
    return 0;
}

笔试题4:

int main(){
    int a[3][2]= { (0,1),(2,3),(4,5)}; //小括号括起来里面表示逗号表达式
    //元素为1,3,5,0,0,0
    int* p;
    p = a[0]; //a[0]没有取地址,也没有放在sizeof内部,就表示首元素的地址
    //即把第一行首元素的地址赋值给p
    printf("%d", p[0]);  //打印第一行首元素的值
    return 0;
}

笔试题5:

int main(){
    int a[5][5];
    int(*p)[4];
    p = a; //强制类型转换后p[1][0] == a[0][4]
    printf("%p,%d\n",&p[4][2]-&a[4]2],&p[4][2]-&a[4][2]);
    //p[4][2] == 4*4+3 == 19 第19个元素
    //p[4][2] == 4*5+3 == 23 第23个元素
    //二者相减 == -4个元素
    //%p == 11111111111111111111111111111100 == FF FF FF FC
    //%d == -4
    return 0;
}

笔试题6:

int main(){
    int aa[2][5] ={ 1,2,3,4,5,6,7,8,9,10 };
    int* ptr1 = (int*)(&aa + 1); 
    //&aa+1直接跳过了整个数组,ptr1指向了数组后的地址
    int* ptr2 = (int*)(*(aa + 1));
    //aa+1表示首元素的地址+1,即表示第二行的地址,解引用之后表示第二行
    //因为没有放在sizeof里面,也没有&,ptr2就表示第二行第一个元素的地址
    printf("%d,%d",*(ptr1 - 1),*(ptr2 - 1));
    //打印10,5
    return 0;
}

笔试题7:

int main( )
{
    char* a[] = { "work" ,"at" , "alibaba" };
    char** pa = a;
    //无sizeof,无&,表示首元素地址
    pa++;
    //at的指针的地址
    printf("%s\n", *pa);  
    //打印"at"
    return 0;
}

笔试题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表示c+2的地址,第一次*得到c+2,即POINT的地址,第二次*得到POINT
    //++cpp对cpp产生影响,现在cpp指向了cp+1的位置
    printf("%s\n",*-- * ++cpp + 3);
    //++cpp导致cpp指向cp+2的位置
    //解引用后得到c+1,再--表示c+1变成c,指向了c的位置
    //再解引用后+3指向了E的位置,打印ER
    printf("%s\n", *cpp[-2] + 3);
    //先[-2],得到C+3,解引用得到FIRST,+3后打印ST
    printf("%s\n", cpp[-1][-1] + 1);
    //cpp[-1]得到c+2,c+2[-1]表示*(c+2-1) == c[1],+1后打印EW
    return 0;
}

做题时画图更易理解!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

求索1024

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

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

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

打赏作者

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

抵扣说明:

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

余额充值