进阶C语言

1.数据的存储

1.1 为什么数据在内存中存放的是补码

  • 因为CPU只有加法器,而使用补码,就可以将符号位数值域统一处理(即统一处理加法和减法)且不会需要额外的硬件电路

1.2 为什么会有大小端

  •  这是因为在计算机系统中,是以字节为单位的,比如: 每个地址单元都对应着一个字节
  • 位数大于8位的处理器,比如:16位,32位处理器,由于寄存器宽度大于一个字节,那么必然会存在如何将多个字节安排的问题,这就导致出现的大,小端存储

1.3. 验证机器大小端

#define _CRT_SECURE_NO_WARNINGS
#include
int check_sys()
{
	int a = 1;//0x00000001
	char* p = (char*)&a;//int*
	return *p;//返回1表示小端,返回0表示大端
}

int main()
{
	//写代码判断当前机器的字节序
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}

	return 0;
}

进阶C语言_第1张图片

  • char*类型的指针,解引用访问的是一个char的大小 
  • vs2022采用的是小端存储模式

 1.4 浮点型在内存中的存储

一个数的存入和它的取出是息息相关的 

1.4.1 案例展示 

#define _CRT_SECURE_NO_WARNINGS
#include 
int main()
{
	int n = 9;

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	return 0;
}

进阶C语言_第2张图片

1.4.2 浮点型在内存中的存储形式

进阶C语言_第3张图片

进阶C语言_第4张图片

  • 根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数都可以用上面的形式保存
  • (1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
  • M表示有效数字,大于等于1,小于2。
  • 2^E表示指数位

 1.4.3 对于32位的浮点数

进阶C语言_第5张图片

  •  最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M

1.4.4 对于64位的浮点数 

进阶C语言_第6张图片

  • 最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M 

1.4.5 对有效数字M和指数E的特别规定 

  •  有效数字M的取值范围是[1,2),即M可以写成1.XXXX的形式,其中XXXX表示为小数部分
  • IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的XXXX部分
  • 以32位浮点数为例,比如保存1.01的时候,
    • 将1舍去, 只保存01,M就会有24为有效位
    • 等到需要读取的时候,再把第1位的1加上去

1.4.6 指数E在内存中的存储 

E为一个无符号整数(unsigned int)

  • 如果E为8位,它的取值范围为0~255
    如果E为11位,它的取值范围为0~2047
  • 由于科学计数法中的E是可以出现负数的,
    所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,
  • 对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023
    比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

1.4.7 指数E从内存中取出

情况一:E不全为0或不全为1

  • 指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

情况二:E全为0

  • 说明存的时候E加上了127,但还是为0,说明这个2 ^ E特别小
  • 规定这时取的时候,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字

情况三:E全为1

  • 存的时候E加上127,居然全部都变成了1,说明这个2 ^ E特别大(正负取决于符号位s)

进阶C语言_第7张图片

  •  总结IEEE 754规定,得出浮点数的存储形式

 1.4.8 案例分析

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int main()
{
	int n = 9;

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	return 0;
}
  • 对于第一个printf,毫无疑问结果是9,不解释
  • 对于第二个printf,float* pFloat = (float*)&n;它将n的地址强制转化成float*,并赋给了pFloat,
    • 此时pFloat就认为这段二进制: 是float类型存入内存的二进制
    • pFloat指向9并解引用,最后又是以%f打印的,所以结果为0.000000
      进阶C语言_第8张图片
  • 对于第三个printf,*pFloat = 9.0;把9的值赋给了n,且pFloat是一个float* 的指针变量,最后又是以%d的形式打印,所以结果为1091567616

        进阶C语言_第9张图片

  • 对于第四个printf,和第三个printf同理,不同之处是
    第三个printf以浮点数存入,以%d的形式打印,
    第四个printf中也是以浮点数存入,但是却是以%f
    所以结果应该为9.000000

2. 指针

2.1 字符指针 

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int main()
{
	char ch = 'q';
	char * pc = &ch;

	char* ps = "hello bit";
	char arr[] = "hello bit";
	*ps = 'w';//err

	arr[0] = 'w';
	printf("%c\n", *ps);//h
	printf("%s\n", ps);//hello bit
	printf("%s\n", arr);//wello bit

	return 0;
}
  • char* ps = "hello bit";不是把字符串 hello bit放到字符指针 pstr 里了,而是把"hello bit"这个字符串的首字符的地址存储在了ps中

  • "hello bit"是一个常量字符串,常量字符串是不能被修改,则*ps = 'w';这个语句就是错的

2.1.1 经典题

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";
    //*str3 = 'w';

    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语言_第10张图片

str1和str2不同,str3和str4相同。

  • 这其实也很好理解"hello bit.",这是一个常量字符串,不能被修改,又因为str1和str2都是指向同一个常量字符串,自然也就不需要再开辟一段空间放相同的常量字符串
  • srt1和str2虽然数组的内容一样,但是str1和str2中的"hello bit."是可以被修改,所以开辟了2个不同数组存放"hello bit."

 2.2 二维数组传参

 传入的参数是二维数组的首地址

  • 个test错误,接收时int arr[][],可以用二维数组接收,但不能省略列数
  • 个test错误,不能用一级指针接收,指针接收,只能用数组指针(一级)
  • 个test错误,不能用一级指针数组接收,数组接收,只能用二维数组,
  • 个test错误,不能用二级指针接收,指针接收,只能用数组指针(一级)

2.3 函数指针

 2.3.1 函数传参

  • 函数名 == &函数名,即函数传参的时候,&可以不写

2.3.2 函数指针解引用

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//int (*pf)(int, int) = &Add;//OK
	int (*pf)(int, int) = Add;//Add === pf
	int ret = 0;

	ret = (*pf)(3, 5);//1
	printf("%d\n", ret);

    ret = pf(3, 5);//2
	printf("%d\n", ret);

	ret = Add(3, 5);//3
	printf("%d\n", ret);
	//int ret = * pf(3, 5);//err
	return 0;
}

 

  •  对于一个函数指针的解引用,*可以不用写

2.3.2 经典题

代码1 : (*(void (*)())0)();// 请问该代码什么意思

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

代码2 :void (*signal(int , void(*)(int)))(int);// 请问该代码什么意思 

进阶C语言_第11张图片

  • signal 和()先结合,说明signal是函数名
  • signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
  • signal函数的返回类型也是一个函数指针\
  • signal是一个函数的声明

2.4 函数指针数组的用途

#define _CRT_SECURE_NO_WARNINGS 1
#include
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;
	//计算器-计算整型变量的加、减、乘、除
	//a&b a^b a|b a>>b a<b

	do {
		menu();
		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);//只有输入0才退出
	return 0;
}
  •  函数指针数组更像是一个跳板的作用,可以减少代码冗余

 2.4.回调函数

 将一个函数A的地址传给另一个函数B(用函数指针接收),该函数B又通过解引用调用其他函数

#define _CRT_SECURE_NO_WARNINGS 1
#include
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;
	//计算器-计算整型变量的加、减、乘、除
	//a&b a^b a|b a>>b a<b

	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;
}
  •  Clac这一个函数就能调用多个函数,减少了代码的冗余,Clac就像一个集成器

2.5 指针经典题

2.5.1 题一 

进阶C语言_第12张图片

考查的是:指针类型决定了指针的运算 

  1.  p+0x1中p为结构体指针变量,这个结构体的大小为20,0x1实际上就是1,p+1会跳过一个结构体的大小,指向的是数组后面空间的地址0x100000+20=0x100014,结果为0x100014
  2. (unsigned long)p + 0x1中将p强制类型转换为unsigned long,它加1就是加1,0x100000+1=0x100001
  3. (unsigned int*)p+0x1中将p强制类型转换为unsigned long*,p变成了无符号整形指针,它加一就是加一个int,0x100000+4=0x100004

2.5.2 题二  

进阶C语言_第13张图片

  1.   ptr1是一个整形指针,指向的是数组后面空间的地址,&a取出的是数组的地址
  2. ptr2是一个整形指针,(int)a + 1中a表示首元素的地址,再将其强制类型转换问int,它加一就是加一(地址加1)相当于向后偏移了一个字节,在内存中一个字节给一个地址,如:0x0012ff44-->int+1-->0x0012ff45
    在小端机器下,
  3. *ptr2表示对ptr2进行解引用,找到并访问4个字节,ptr1[-1]等价于*(ptr1-1),结果为4,2000000

2.5.3 题三

进阶C语言_第14张图片 

  •  (0,1) 叫做逗号表达式,结果是最右边的值
  • a[0]表示这个二维数组的首元素的地址,p为整形指针变量,p[0]等价于*(p+0),结果为1

2.5.4 题四

#define _CRT_SECURE_NO_WARNINGS
#include
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]);//FFFFFFFC,-4
	return 0;
}

 

  • -4以%d的形式打印还是-4
  • -4的原码10000000000000000000000000000100
  • -4的反码111111111111111111111111111111111011
  • -4的补码111111111111111111111111111111111100
  • -4在内存中以补码的形式存储,%p的形式打印,会直接将-4的补码当作原码打印出来所以结果为FFFFFFFC 

2.5.5 题五

#define _CRT_SECURE_NO_WARNINGS
#include
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;
}
  1.    char*c[],char**cp[],char***cpp这三者之间的指向关系如下:
  2. **++cpp表示先cpp+1,再解引用,指向c+2的地址,再解引用,指向P的地址,结果为POINT
  3. *-- * ++cpp + 3表示先cpp+1,由于上面的运算cpp变成了cpp+1,所以这里的cpp变成了cpp+2,再解引用,指向c+1的地址,再减1,指向c的地址,再解引用,指向E的地址,再加3,指向第四个E的地址,结果为ER
  4. *cpp[-2] + 3等价于*(*(cpp-2))+3表示为cpp-2,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp,再解引用,指向c+3地址,再解引用,指向F的地址,再加3,指向S的地址,结果为ST
  5. cpp[-1][-1] + 1等价于*(*(cpp-1)-1)+1,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp+1,再解引用,指向c+2的地址,再减1,指向c+1的地址,再解引用,指向N的地址,再加一指向E的地址,结果为EW

你可能感兴趣的:(c语言,开发语言)