【进阶】2. 指针的进阶

文章说明:该文章的知识点源于B站上比特鹏哥的C语言课程,结合鹏哥上课的讲义、课堂代码以及自己的理解整理形成。

1. 字符指针

常规用法:

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

另一种使用方式:

int main()
{
    const char* ps = "hello bit."; //常量字符串无法更改
    printf("%s\n", ps);
    return 0;
}

本质是把字符串首字符 h 的地址放到指针变量 ps 当中。

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

这里 str3str4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1str2 不同, str3str4 不同。

添加下面的代码会报错,因为常量字符串不允许更改!

//const char *str3 = "hello bit.";改为下行代码
char *str3 = "hello bit.";
*str3 = 'w';

2. 指针数组

本质是数组,数组中存放的是指针。

int* arr1[10]; //整形指针的数组
char* arr2[4]; //一级字符指针的数组
char** arr3[5];//二级字符指针的数组
int main()
{
	int a[5] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };

	int* arr[3] = {a,b,c}; //模拟了一个二维数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", *(arr[i] + j)); 
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

arr[i][j]*(arr[i] + j) 等价。

3. 数组指针

3.1 数组指针的定义

数组指针是指针。

数组指针和指针数组的辨析:

int *p1[10];	//p1是指针数组
int (*p2)[10];	//p2是数组指针

int (*p)[10]
p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
注:[] 的优先级要高于 * 号的,所以必须加上 () 来保证p先和 * 结合。

double *d[5];
double* (*pd)[5] = &d; //pd是一个数组指针

int arr[10] = {1,2,3,4,5};
int (*parr)[10] = &arr; //parr就是一个数组指针,其中存放的是数组的地址

3.2 &数组名 VS 数组名

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);	//00EFF920
    printf("%p\n", &arr);	//00EFF920 值一样,但是类型不一样
    return 0;
}
#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("arr = %p\n", arr);		//arr = 0133FBB0
    printf("&arr= %p\n", &arr);		//&arr = 0133FBB0
    printf("arr+1 = %p\n", arr+1);	//arr+1 = 0133FBB4
    printf("&arr+1= %p\n", &arr+1);	//&arr+1 = 0133FBD8
    //数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40.
    return 0;
}

数组名是数组首元素的地址,但是有2个例外:

  1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
  2. &数组名 - 数组名表示整个数组,取出的是整个数组的地址

3.3 数组指针的使用

一般很少这样写代码,很少用数组指针直接指向一维数组

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

	int (*pa)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*pa) + i)); //非常变扭
	}

	return 0;
}

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

void print(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };

	print(arr, 3, 5);//arr数组名,表示数组首元素的地址
	return 0;
}
int arr[5];			//整型数组
int *parr1[10];		//存放整型指针的数组
int (*parr2)[10];	//数组指针,指向一个有10个元素的数组,每个元素的类型是int
int (*parr3[10])[5];//存放数组指针的数组,存放10个数组指针,每个数组指针指向有5个元素的数组,每个元素的类型是int

4. 数组传参和指针传参

关键在于传递的是什么

4.1 一维数组传参

#include <stdio.h>
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);
}

4.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 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]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test(char* p)
{}

char ch = 'w';
char *pc = &ch;
test(&ch);
test(pc);

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

当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p)
{}

int main()
{
    char c = 'b';
    char*pc = &c;
    char**ppc = &pc;
    char* arr[10];
    test(&pc);
    test(ppc);
    test(arr);//Ok? - Yes
    return 0;
}

5. 函数指针

函数指针:存放函数地址的指针。

函数名 == &函数名

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n", test);	//输出结果:013211DB
    printf("%p\n", &test);	//输出结果:013211DB -> 取到的是函数的地址
    return 0;
}
int Add(int x, int y)
{
    return x+y;
}

int main()
{
    //int (*pf)(int, int) = Add; 效果同下
    int (*pf)(int, int) = &Add; //pf就是一个函数指针    Add 等价于 pf
    
    int ret1 = (*pf)(3, 5); //调用函数指针
    int ret2 = pf(3, 5); 	//调用函数指针
    int ret3 = Add(3, 5);	//调用函数
    
    return 0;
}

阅读两段有趣的代码:

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

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


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

//signal是一个函数的声明
//1. signal 和()先结合,说明signal是函数名
//2. signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
// 	 该函数指针,指向一个参数为int,返回类型是void的函数
//3. signal函数的返回类型也是一个函数指针
//   该函数指针,指向一个参数为int,返回类型是void的函数

代码2简化:

typedef void(*pfun_t)(int); //void(*)(int) -> pfun_t
pfun_t signal(int, pfun_t);

6. 函数指针数组

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

函数指针数组的用途:转移表,以计算器为例。

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 menu()
{
	printf("**************************\n");
	printf("**** 1. add    2. sub ****\n");
	printf("**** 3. mul    4. div ****\n");
	printf("****     0. exit      ****\n");
	printf("**************************\n");
}

int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除

	do {
		menu();

		//pfArr就是函数指针数组
		//转移表 - 《C和指针》

		int (*pfArr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);//2

		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = (pfArr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出程序\n");
			break;
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	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. 回调函数

回调函数就是一个通过函数指针调用的函数

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

将上述的计算器代码用回调函数实现。

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 menu()
{
	printf("**************************\n");
	printf("**** 1. add    2. sub ****\n");
	printf("**** 3. mul    4. div ****\n");
	printf("****     0. exit      ****\n");
	printf("**************************\n");
}

int Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作数>:");
	scanf("%d %d", &x, &y);
	return pf(x, y);
}

int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除

	do {
		menu();
	
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			ret = Calc(Add);
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);//
			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);//
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
		
	} while (input);
	return 0;
}

演示一下 qsort 函数的使用:

void qsort (void* base, //指向要排序的第一个对象
            size_t num, //要排序元素的个数
            size_t size, //要排序元素的大小
            int (*compar)(const void*,const void*)); //比较函数的指针

#include <stdio.h>
//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");
    return 0;
}

模拟实现 qsort 函数(采用冒泡的方式),为数组排序:

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

//模仿qsort实现一个冒泡排序的通用算法
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++)
		{
			//两个元素比较
			//arr[j] arr[j+1]
			if (cmp( (char*)base+j*width, (char*)base+(j+1)*width )>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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

int main()
{
	test3();
	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 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++)
		{
			//两个元素比较
			//arr[j] arr[j+1]
			if (cmp( (char*)base+j*width, (char*)base+(j+1)*width )>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test4()
{
	//使用qsort函数排序结构体数据
	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()
{
	test4();
	return 0;
}

9. 指针和数组面试题的解析

//一维数组
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));

// 16 - sizeof(a)表示整个数组的大小
// 4/8 - a表示数组的首元素地址 -> 求的是数组第1个元素地址的大小
// 4 - a表示数组的首元素地址,*a表示数组的首元素 -> 求的是数组第1个元素的大小
// 4/8 - a表示数组的首元素地址,a+1示数组的第2个元素地址 -> 求的是数组第2个元素地址的大小
// 4 - a[1]表示数组的第2个元素 -> 求的是数组第2个元素的大小

// 4/8 - &a表示的是整个数组的地址 -> 求的是整个数组地址的大小
// 16 - &a表示的是整个数组的地址; *&a表示整个数组 -> 求的是整个数组地址的大小
// 4/8 - &a表示的是整个数组的地址; &a+1表示整个数组后面的地址 -> 求的是数组后面地址的大小
// 4/8 - a[0]是数组的第1个元素; &a[0]是取出第1个元素的地址 -> 求的是第1个元素地址的大小
// 4/8 - &a[0]是取出第1个元素的地址; &a[0]+1取出第2个元素的地址 -> 求的是第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));

// 6 - sizeof(arr)表示整个数组的大小
// 4/8 - arr表示数组首元素的地址; arr+0也还是数组首元素的地址 -> 求的是数组首元素地址的大小
// 1 - arr表示数组首元素的地址; *arr表示数组的首元素 -> 求的是数组首元素的大小
// 1 - arr[1]表示的是数组的第2个元素 -> 求的是数组第2个元素的大小
// 4/8 - &arr表示的是整个数组的地址 -> 求的是整个数组地址的大小
// 4/8 - &arr表示的是整个数组的地址; &arr+1表示的是整个数组后面的地址 -> 求的是数组后面地址的大小
// 4/8 - arr[0]表示的是数组的首元素; &arr[0]表示的是数组第1个元素的地址; 
//		 &arr[0]+1表示的是数组第2个元素的地址 -> 求的是第2个元素地址的大小

// 随机值 - arr表示数组首元素的地址 -> 一直找到'\0'字符为止
// 随机值 - arr表示数组首元素的地址;  arr+0也还是数组首元素的地址 -> 一直找到'\0'字符为止
// err - arr表示数组首元素的地址; *arr表示数组的首元素 -> 地址为a的ASCII码值 97 报错
// err - arr[1]表示数组的第2个元素 -> 地址为b的ASCII码值 98 报错
// 随机值 - &arr表示整个数组的地址; 隐含强制类型转换 strlen(char* str) -> 一直找到'\0'字符为止
// 随机值-6 - &arr表示的是整个数组的地址; &arr+1表示整个数组后面的地址 -> 一直找到'\0'字符为止
// 随机值-1 - arr[0]表示数组的首元素; &arr[0]表示数组第1个元素的地址; 
//			 &arr[0]+1表示数组第2个元素的地址 -> 一直找到'\0'字符为止
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));

// 7 - sizeof(arr)表示整个数组的大小
// 4/8 - arr表示的是数组首元素地址; arr+0表示的是数组首元素地址 -> 求的是数组首元素地址的大小
// 1 - arr表示的是数组首元素地址; *arr表示的是数组首元素 -> 求的是数组首元素的大小
// 1 - arr[1]表示的是数组第2个元素 -> 求的是数组第2个元素的大小
// 4/8 - &arr表示的是整个数组的地址 -> 求的是整个数组地址的大小
// 4/8 - &arr表示的是整个数组的地址; &arr+1表示整个数组后面的地址 -> 求的是整个数组后面地址的大小
// 4/8 - arr[0]表示的是数组首元素; &arr[0]表示的是数组首元素的地址; 
//		 &arr[0]+1表示的是数组第2个元素的地址 -> 求的是数组第2个元素地址的大小

// 6 - arr表示数组首元素的地址 -> 一直找到'\0'字符为止
// 6 - arr表示数组首元素的地址;  arr+0也还是数组首元素的地址 -> 一直找到'\0'字符为止
// err - arr表示数组首元素的地址; *arr表示数组的首元素 -> 地址为a的ASCII码值 97 报错
// err - arr[1]表示数组的第2个元素 -> 地址为b的ASCII码值 98 报错
// 6 - &arr表示整个数组的地址; 隐含强制类型转换 strlen(char* str) -> 一直找到'\0'字符为止
// 随机值 - &arr表示的是整个数组的地址; &arr+1表示整个数组后面的地址 -> 一直找到'\0'字符为止
// 5 - arr[0]表示数组的首元素; &arr[0]表示数组第1个元素的地址; 
//	   &arr[0]+1表示数组第2个元素的地址 -> 一直找到'\0'字符为止
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));

// 4/8  - p指向字符串的'a'字符; p存储的是字符'a'的地址 -> 求的是字符'a'地址的大小
// 4/8 - p存储的是字符'a'的地址; p+1指向字符串的'b'字符;-> 求的是字符'b'地址的大小
// 1 - p存储的是字符'a'的地址; *p表示的是字符'a' -> 求的是字符'a'的大小
// 1 - p[0]等价于*p -> 求的是字符'a'的大小
// 4/8 - &p表示的是p的地址 -> 求的是p地址的大小
// 4/8 - &p表示的是p的地址; &p+1表示的是p后面的地址 -> 求的是p后面地址的大小
// 4/8 - p[0]表示字符'a'; &p[0]表示字符'a'地址; &p[0]+1表示字符'b'地址 -> 求的是字符'b'地址的大小


// 6 - p指向字符串的'a'字符 -> 一直找到'\0'字符为止
// 5 - p指向字符串的'a'字符; p+1指向字符串的'b'字符 -> 一直找到'\0'字符为止
// err - p指向字符串的'a'字符; *p表示的是字符'a' -> 地址为a的ASCII码值 97 报错
// err - p[0]等价于*p -> 地址为a的ASCII码值 97 报错
// 随机值 - &p表示的是p的地址; 从p的地址开始找, 一直找到'\0'字符为止
// 随机值 - &p表示的是p的地址; &p+1表示的是p后面的地址;从p+1的地址开始找, 一直找到'\0'字符为止
// 5 - p[0]表示字符'a'; &p[0]表示字符'a'地址; &p[0]+1表示字符'b'地址-> 一直找到'\0'字符为止
//二维数组 -> 关键a[0] a[1] a[2] 可以看作是单独的数组名; 每个数组有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));
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]));

// 3*4*4=48 - sizeof(a)表示整个数组 -> 求的是整个数组的大小
// 4 - a[0][0]表示数组第1行第1列的元素 -> 求的是元素a[0][0]的大小
// 4*4=16 - a[0]单独在sizeof内部表示第1行的数组名 -> 求的是数组第1行的大小
// 4/8 - a[0]表示第1行第1列元素的地址; a[0]+1表示第1行第2列元素的地址 -> 求的是a[0][1]地址的大小
// 4 - a[0]+1表示第1行第2列元素的地址; *(a[0]+1))表示a[0][1]的值 -> 求的是元素a[0][1]的大小
// 4/8 - a表示数组第1行的地址; a+1表示数组第2行的地址 -> 求的是数组第2行地址的大小

// 4*4=16 - a+1表示数组第2行的地址; *(a+1)表示数组的第2行 -> 求的是数组第2行的大小
// 4/8 - &a[0]表示第1行的地址; &a[0]+1表示第2行的地址 -> 求的是数组第2行地址的大小
// 4*4=16 - &a[0]+1表示第2行的地址; *(&a[0]+1)表示第2行 -> 求的是数组第2行的大小
// 4*4=16 - a表示数组第1行的地址; *a表示数组的第1行 -> 求的是数组第1行的大小
// 4*4=16 - a[3]表示数组第4行地址, 实际不存在 -> sizeof(a[3])可以根据类型推算出大小
int main()
{
	short s = 5;
	int a = 4;
	printf("%d\n", sizeof(s = a + 6));//2 -> s=a+6并没有进行计算
	printf("%d\n", s);//5
	return 0;
}

【总结】数组名的意义

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

//程序的结果:
//2,5

10.2 笔试题2

//指针类型决定了指针的运算
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);	
    printf("%p\n", (unsigned long)p + 0x1);
    printf("%p\n", (unsigned int*)p + 0x1);
    return 0;
}

//程序的结果:
//0x100014
//0x100001 - 整数加1
//0x100004

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

//程序的结果:
//4 2000000  -> ((int)a + 1) 是指向a[0]中的第2个字节,配合小端存储

10.4 笔试题4

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) }; 
    int *p;
    p = a[0];
    printf( "%d", p[0]); 
    return 0;
}

//程序的结果:
//1		->	a[3][2] = {1,3,5,0,0,0}
//把a[0]单独看出一个数组名; a[0]表示数组首元素的地址, 即a[0][0]的地址

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

//程序的结果:
//0xFFFFFFFC -4

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

//程序的结果:
//10 5

10.7 笔试题7

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa); 
    return 0;
}

//程序的结果:
//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;
}

//程序的结果:
//POINT
//ER
//ST
//EW
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值