Bootstrap

指针、数组与函数

数组名的理解

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

这里我们使用&arr[0] 的方式拿到了数组第一个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址

#include<stdio.h>
int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	int* p1 = &arr[0];
	int* p2 = arr;
	printf("%p\n", p1);//输出 0137FE0C
	printf("%p", p2);//输出 0137FE0C
	return 0;
}

数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地址

数组名如果是数组首元素的地址,那下面的代码怎么理解呢?

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

输出的结果是:20。如果arr是数组首元素的地址,那输出应该的应该是4 或 8才对。

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名)sizeof中单独放数组名,这里的数组名表示整个数组计算的是整个数组的大小,单位是字节
  • &数组名,这里的数组名表示整个数组取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

整个数组的地址和数组首元素的地址的区别:

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

输出结果:

&arr[0] = 0099FEA0
arr     = 0099FEA0
&arr    = 0099FEA0

三个打印结果一模一样,那arr&arr有什么区别呢?

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

输出结果:

&arr[0]     = 00EFFB90
&arr[0] + 1 = 00EFFB94
arr         = 00EFFB90
arr + 1     = 00EFFB94
&arr        = 00EFFB90
&arr + 1    = 00EFFBA4

这里我们发现&arr[0]&arr[0]+1相差4个字节,arrarr+1 相差4个字节,是因为**&arr[0]**** 和 **arr** 都是首元素的地址,****+1**就是跳过一个元素

但是&arr&arr+1相差 20 个字节,这就是因为**&arr**是数组的地址,**+1**** 操作是跳过整个数组的**。

使用指针访问数组

使用指针而非数组名访问数组:

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

如果我们再分析一下,数组名arr是数组首元素的地址,可以赋值给p,其实数组名**arr****p**在这里是等价的。那我们可以使用arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

在第 14 行的地方,将*(p+i)换成p[i]也是能够正常打印的。

所以本质上p[i] 是等价于 *(p+i)

同理arr[i] 应该等价于 *(arr+i)

然而,*(p+i)*(i+p)是等价的,那么i[p]是否能够正常打印呢?

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", i[p]);
	}
	return 0;
}

结果得出:i[p]也和*(i+p)是等价的。

结论:

数组元素的访问在编译器处理 时,也是转换成首元素的地址+偏移量求出元素的地址然后解引用来访问的

因此arr[i] = *(arr+i) = p[i] = *(p+i) = i[p] = *(i+p)

一维数组传参的本质

首先从一个问题开始:

我们之前都是在函数外部计算数组的元素个数,再把数组元素的个数传给函数。那我们可以把函数传给一个函数后,在函数内部求数组的元素个数吗?

#include<stdio.h>
void test(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("%d", sz2);
}
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz1);
	test(arr);
	return 0;
}

输出结果:

x64环境:
10
2

x86环境:
10
1

我们发现在函数内部是没有正确获得数组的元素个数。

数组传参的本质:

数组名是数组首元素的地址,那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。

那么在函数内部我们写**sizeof(arr)**** 计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)**。所以在函数内部是没办法求的数组元素个数的。

#include<stdio.h>
void test1(int arr[])//参数写成数组形式,本质还是指针
{
	printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针形式
{
	printf("%d\n", sizeof(arr));
}
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	test1(arr);
	test2(arr);
	return 0;
}

输出结果:

x64环境:
8
8

x86环境:
4
4

总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

字符指针变量

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

#include<stdio.h>
int main()
{
	char ch = 'a';
	char* pc = &ch;//pc指向一个字符变量
	*pc = 'c';
	return 0;
}
#include<stdio.h>
int main()
{
	char arr[10] = "abcdef";
	char* pc = arr;//pc接受的是字符串中首字母的地址,但指向的是一个字符数组
	printf("%s\n", arr);//输出abcdef
	printf("%s\n", pc);//输出abcdef
	return 0;
}

字符数组与常量字符串

常量字符串和字符数组非常相似,也是在连续的空间中存放了多个字符,但是常量字符串中的内容不能修改

#include<stdio.h>
int main()
{
	char* pc = "abcdef";//将字符串的首字母a的地址赋给pc
	printf("%c", *pc);//只能打印一个字符
    *pc = 'w';//程序无法完成运行
	return 0;
}
#include<stdio.h>
int main()
{
    //常量字符串不允许修改,因此可以加上一个const
	const char* pc = "abcdef";
	printf("%c", *pc);
	return 0;
}
#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	printf("%s", *pc);//程序无法完成打印
	printf("%s", pc);//输出abcdef
	return 0;
}

占位符%s只要传入地址,就会连续打印至\0停止

笔试题:下面代码的输出结果为?

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

这里str1str2是不同的字符串数组的首字母的地址,str3str4指向的是同一个常量字符串。

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

指针数组

指针数组是存放指针的数组。

指针数组的每个元素都是用来存放地址(指针)的。

指针数组的每个元素都是地址,可以指向一块区域。

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[3] = { &a, &b, &c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *arr[i]);
	}
	return 0;
}

指针数组模拟二维数组

#include<stdio.h>
int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[] = { 2, 3, 4, 5, 5 };
	int arr3[] = { 3, 4, 5, 6, 7 };
	int* arr[] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
        //arr[i][j]等价于*(*(arr + j) + i)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
	return 0;
}

上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

数组指针变量

数组指针变量是什么?

数组指针变量是一种指针变量。

数组指针变量存放的应该是数组的地址,能够指向数组的指针变量。

//[]优先级高于*
int *p1[10];//指针数组
int (*p2)[10];//数组指针变量

解释:

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

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

数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,获得数组的地址需要用到&数组名

int arr[10] = {0};
&arr;//得到的就是数组的地址
int(*p)[10] = &arr;

&arrp的类型是完全一致的,为数组指针类型:int[10]int(*)[10]

int (*p) [10] = &arr;
 |    |   |
 |    |   |
 |    |   p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

二维数组传参的本质

之前我们在给一个二维数组传参给的时候,我们是这样写的:

#include<stdio.h>

void test(int arr[3][5], int r, int t)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < t; j++)
		{
			printf("%d ", arr[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 } };
	test(arr, 3, 5);
	return 0;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?

首先我们再次理解一下二维数组,二维数组可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。

根据上面的例子,第一行的一维数组的类型就是int [5] ,所以第一行的地址的类型就是数组指针类型int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址

那么形参也是可以写成指针形式的:

#include<stdio.h>

void test(int(*p)[5], int r, int t)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < t; 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 } };
	test(arr, 3, 5);
	return 0;
}

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

函数指针变量

函数指针变量的创建

函数指针变量应该是用来存放函数地址的,能够通过地址调用函数的。

#include<stdio.h>

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

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

确实打印出来了地址,所以函数是有地址的函数名就是函数的地址,当然也可以通过**&**函数名的方式获得函数的地址

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。

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

int Add(int x, int y)
{
	return x + y;
}

int(*p1)() = test;
int(*p2)() = &test;

int(*p3)(int x, int y) = Add;
int(*p4)(int, int) = &Add;//x和y写上或者省略都是可以的

函数指针类型解析

int (*pf3) (int x, int y)
 |    |     ------------
 |    |           |
 |    |           pf3指向函数的参数类型和个数的交代
 |   函数指针变量名
 pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

函数指针变量的使用

通过函数指针调用指针指向的函数。

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*p1)(int, int) = Add;
	printf("%d\n", (*p1)(3, 5));
	printf("%d", p1(3, 5));
	return 0;
}

注意:此处*p1必须加上括号。

回调函数

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

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

两段有趣的代码

代码1

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

突破口:0

由此猜测为强制类型转化。

首先将0转换为void (*)()的函数指针变量。

随后调用地址为0处的这个函数

代码2

void (*signal(int , void(*)(int)))(int);

intvoid后无变量,可知为函数的声明。

该函数的返回类型为void

函数名为signal

signal函数有两个参数,一个为int类型,另一个是函数指针,函数指针的类型为void(*)(int),有一个参数为int类型,返回值为void

signal函数的返回值类型为void(*)(int)的函数指针。这个函数指针指向的函数有一个int类型的参数,返回值类型为void

更加便于理解的写法:
void(*)(int) signal(int, void(*)(int);
但是这种写法不符合语法规定。

这种写法过于繁琐复杂,那么要简化代码2,可以这样写:

typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

函数指针数组

定义

函数的地址存到⼀个数组中,那这个数组就叫函数指针数组

那函数指针的数组如何定义呢?

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

答案是:parr1

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

练习:转移表

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

举例:计算器的一般实现

//基础版
#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;
}

int main()
{
	int input = 0;
	int x = 0, y = 0;
	int ret = 0;
	do
	{
		printf("*******0.退出*******\n");
		printf("*******1.加法*******\n");
		printf("*******2.减法*******\n");
		printf("*******3.乘法*******\n");
		printf("*******4.除法*******\n");
		printf("请选择\n");
		scanf("%d", &input);
		printf("输入操作数");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
		} 
	} while (input);
	return 0;
}
//进阶版
#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;
}

int main()
{
    //增加一个NULL,使下标与选项相对应
	int (*arr[5])(int, int) = { NULL, add, sub, mul, div };
	int input = 0;
	int x = 0, y = 0;
	int ret = 0;
	do
	{
		printf("*******0.退出*******\n");
		printf("*******1.加法*******\n");
		printf("*******2.减法*******\n");
		printf("*******3.乘法*******\n");
		printf("*******4.除法*******\n");
		printf("请选择\n");
		scanf("%d", &input);
		printf("输入操作数\n");
		scanf("%d %d", &x, &y);
		if (input >= 1 && input <= 4)
		{
			ret = arr[input](x, y);
			//ret = (*arr[input])(x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
			printf("退出程序");
		else
			printf("输入错误,请重新输入\n");
	} while (input);
	return 0;
}

数组和指针笔试题解析

一维数组

#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zd\n", sizeof(a));// 16 整个数组的大小
	printf("%zd\n", sizeof(a + 0));// 4 \ 8 首元素的地址
	printf("%zd\n", sizeof(*a));// 4 首元素的大小
	printf("%zd\n", sizeof(a[1]));// 4 第二个元素大小
	printf("%zd\n", sizeof(&a));// 4 \ 8 数组的地址
	printf("%zd\n", sizeof(*&a));// 16 数组的地址解引用得到的是整个数组
	return 0;
}

字符数组

练习一
#include<stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));// 6 整个数组的大小
	printf("%zd\n", sizeof(arr + 0));// 4 \ 8 首元素的地址
	printf("%zd\n", sizeof(*arr));// 1 首元素的大小
	printf("%zd\n", sizeof(arr[1]));// 1 第二个元素的大小
	printf("%zd\n", sizeof(&arr));// 4 \ 8 数组的地址
	printf("%zd\n", sizeof(&arr + 1));// 4 \ 8 跳过数组后的那个地址
	printf("%zd\n", sizeof(&arr[0] + 1));// 4 \ 8 第二个元素的地址
	return 0;
}
练习二
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", strlen(arr));// 首元素地址, 无 \0, 随机值
	printf("%zd\n", strlen(arr + 0));// 首元素地址, 无 \0, 随机值
	printf("%zd\n", strlen(*arr));// *arr-- 'a'-- 97, 将97作为地址使用--err
	printf("%zd\n", strlen(arr[1]));// 'b'-- 98, err
	printf("%zd\n", strlen(&arr));// 数组的地址, 随机值
	printf("%zd\n", strlen(&arr + 1));// 跳过数组后的地址, 随机值
	printf("%zd\n", strlen(&arr[0] + 1));// 第二个元素的地址, 随机值
	return 0;
}
练习三
#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", sizeof(arr));// 7 整个数组的大小
	printf("%zd\n", sizeof(arr + 0));// 4 \ 8 数组首元素的地址
	printf("%zd\n", sizeof(*arr));// 1 首元素的大小
	printf("%zd\n", sizeof(arr[1]));// 1 第二个元素的大小
	printf("%zd\n", sizeof(&arr));// 4 \ 8 数组的地址
	printf("%zd\n", sizeof(&arr + 1));// 4 \ 8 跳过数组后的地址
	printf("%zd\n", sizeof(&arr[0] + 1));// 4 \ 8 第二个元素的地址
}
练习四
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", strlen(arr));// 6 字符数
	printf("%zd\n", strlen(arr + 0));// 6 字符数
	printf("%zd\n", strlen(*arr));// err
	printf("%zd\n", strlen(arr[1]));// err
	printf("%zd\n", strlen(&arr));// 6 数组的地址--字符数
	printf("%zd\n", strlen(&arr + 1));// 随机值 跳过数组后的地址
	printf("%zd\n", strlen(&arr[0] + 1));// 5 除首字符外的字符数
}
练习五
#include<stdio.h>
int main()
{
	char* p = "abcdef";
	printf("%zd\n", sizeof(p));// 4 \ 8 首元素的地址
	printf("%zd\n", sizeof(p + 1));// 4 \ 8 第二个元素的地址
	printf("%zd\n", sizeof(*p));// 1 首元素的大小
	printf("%zd\n", sizeof(p[0]));// 1 p[0]-- *(p + 0)-- *p-- 首元素的大小
	printf("%zd\n", sizeof(&p));// 4 \ 8 p的地址
	printf("%zd\n", sizeof(&p + 1));// 4 \ 8 
	printf("%zd\n", sizeof(&p[0] + 1));// 4 \ 8 
	return 0;
}
练习六
#include<stdio.h>
#include<string.h>
int main()
{
	char* p = "abcdef";
	printf("%zd\n", strlen(p));// 6 字符数
	printf("%zd\n", strlen(p + 1));// 5 除首字符外的字符数
	printf("%zd\n", strlen(*p));// err
	printf("%zd\n", strlen(p[0]));// err
	printf("%zd\n", strlen(&p));// 随机值 p的地址
	printf("%zd\n", strlen(&p + 1));// 随机值
	printf("%zd\n", strlen(&p[0] + 1));// 5 除首字符外的字符数
	return 0;
}

二维数组

#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));// 48 数组名单独放在sizeof中,计算的是整个数组的大小
	printf("%zd\n", sizeof(a[0][0]));// 4 第一行第一个元素的大小
	printf("%zd\n", sizeof(a[0]));// 16 
    //a[0]为第一行的数组名,计算的是第一个一维数组的大小
	printf("%zd\n", sizeof(a[0] + 1));// 4 \ 8 
    //a[0]并非单独放在sizeof中, a[0]为首元素地址,a[0]--> &a[0][0], a[0] + 1 --> &a[0][1]
	printf("%zd\n", sizeof(*(a[0] + 1)));// 4
	printf("%zd\n", sizeof(a + 1));// 4 \ 8
    //a并非单独放在sizeof中, a为首个一维数组的地址,a--> &a[0], a + 1 --> &a[1]
	printf("%zd\n", sizeof(*(a + 1)));// 16
    //*(a + 1) --> *(&a[1]) --> a[1], 数组的地址解引用得到的是整个数组
	printf("%zd\n", sizeof(&a[0] + 1));// 4 \ 8
    //&a[0]为第一个一维数组的地址, &a[0] + 1为第二个一维数组的地址
	printf("%zd\n", sizeof(*(&a[0] + 1)));// 16
	printf("%zd\n", sizeof(*a));// 16
    //a并非单独放在sizeof中, a为首个一维数组的地址,计算的是第一个一维数组的大小
	printf("%zd\n", sizeof(a[3]));// 16
    //sizeof后()中的运算不会真的进行,只是由类型进行推断得出结果
	return 0;
}

指针运算笔试题解析

练习一

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

画板

练习二

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结构是啥?

#include<stdio.h>

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
	printf("%p\n", p + 0x1);//输出 00100014
    //p为结构体指针变量,p + 1跳过20个字节,且原数据为十六进制,p + 1 = 0x100014
    //%p打印时,忽略0x,因为为x86环境,指针变量为4个字节,在前面补上2个0
	printf("%p\n", (unsigned long)p + 0x1);//输出 00100001
    //p为无符号长整型,p + 1 = 0x100001
    //%p打印时,忽略0x,因为为x86环境,指针变量为4个字节,在前面补上2个0
	printf("%p\n", (unsigned int*)p + 0x1);//输出 00100004
    //p为无符号整型指针变量,p + 1跳过4个字节,p + 1 = 0x100004
    //%p打印时,忽略0x,因为为x86环境,指针变量为4个字节,在前面补上2个0
	return 0;
} 

练习三

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //注意:(0, 1)用的小括号而非大括号,即a[0][0]所赋值的为(0, 1)
    //(0, 1)为逗号表达式
    //逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
    //即a[0][0] = 1
	int* p;
	p = a[0];
    //p赋值首个一维数组的首元素的地址
	printf("%d", p[0]);//输出 1
	return 0;
}

练习四

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
	int a[5][5];
	int(*p)[4];
    //p是一个数组指针,这个指针指向的数组指向的数组有4个int类型的元素
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//输出 FFFFFFFC,-4
    //指针 - 指针得到的值的绝对值,是两个指针间的元素的个数
    //&p[4][2] - &a[4][2] = -4
    //将-4当做地址打印:
    //-4的原码:1000000 00000000 00000000 00000100
    //-4的补码:1111111 11111111 11111111 11111100
    //转换为十六进制:0xFFFFFFFC
	return 0;
}

画板

练习五

#include <stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
    //&aa:整个数组的地址
	int* ptr2 = (int*)(*(aa + 1));
    //aa:第一个一维数组的地址
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//输出 10,5
	return 0;
}

画板

练习六

#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
    //将a中首元素(work的地址)的地址赋值给pa
	pa++;
	printf("%s\n", *pa);//输出 at
	return 0;
}

画板

练习七

#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
    //注意:
    //++和--会真实改变cpp的值
	//而cpp[-1]这类运算不会改变cpp的值
    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;
}

画板

;