进阶C语言

news2024/11/9 5:19:42

1.数据的存储

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

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

1.2 为什么会有大小端

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

1.3. 验证机器大小端

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

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

 1.4 浮点型在内存中的存储

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

1.4.1 案例展示 

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

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

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

 1.4.3 对于32位的浮点数

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

1.4.4 对于64位的浮点数 

  • 最高的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)

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

 1.4.8 案例分析

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
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
  • 对于第三个printf,*pFloat = 9.0;把9的值赋给了n,且pFloat是一个float* 的指针变量,最后又是以%d的形式打印,所以结果为1091567616

        

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

2. 指针

2.1 字符指针 

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

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 <stdio.h>
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);// 请问该代码什么意思 

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

2.4 函数指针数组的用途

#define _CRT_SECURE_NO_WARNINGS 1
#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 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 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<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 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 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 题一 

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

  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 题二  

  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 题三

 

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

2.5.4 题四

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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<stdio.h>
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双指针算法初阶

前言&#xff1a;首先&#xff0c;这是不是你所了解的双指针算法&#xff1f; for (int i 0; i < n; i) {for (int j 0; j < n; j){...} } 那你就要继续往下看了&#xff0c;双指针算法可不是简单的两层的for循环暴力&#xff0c;这并不能起到时间优化的作用。 那话…

基于html+css的图片展示9

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

educoder实训——数值类型

第1关:三角形周长及面积 任务描述 输入的三角形的三条边a、b、c 的长度,计算并依次输出三角形的周长和面积,结果严格保留2位小数。测试用例的数据保证三角形三边数据可以构成三角形。 三角形面积计算公式: 其中s=(a+b+c)/2。 输入格式 分三行输入 3 个浮点数,表示三…

银行数字化转型导师坚鹏:金融数字化转型助力乡村振兴及案例

金融数字化转型助力乡村振兴及案例课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚如何借助数字化转型助力乡村振兴&#xff1f; 不知道普惠金融模式和产品如何有效创新&#xff1f; 不知道数字化转型助力乡村振兴的成功案例&#xff1f; 课程特色&#xff1…

【用AI写周报,“卷死”同事】打造一款自动生成周报的微信小程序

文章目录前言步骤1&#xff1a;创建一个ChatGPT账号步骤2&#xff1a;创建一个微信小程序并配置API。步骤3&#xff1a;在微信开发者工具中创建一个新的微信小程序项目步骤4&#xff1a;创建ChatGPT API云函数步骤5&#xff1a;创建UI界面步骤6&#xff1a;创建发送邮件的云函数…

Kubernetes 1.27 正式发布

Kubernetes 1.27 正式发布&#xff0c;这是 2023 年的第一个版本。这个版本包括 60 项增强功能。其中 18 项增强功能进入 Alpha、29 项进入 Beta&#xff0c;还有 13 项升级为 Stable 稳定版。 主题和标识 Kubernetes v1.27 的主题是 Chill Vibes 新内容 冻结 k8s.gcr.io镜像…

replugin宿主与插件通信小结

近来replugin开发中遇到宿主和插件间需要通信的情形&#xff0c;思来只有进程间通信(IPC)才是比较好的宿主与插件的通信方式。而Android进程间通信主要有2种方式&#xff1a;Messenger和AIDL。 AIDL&#xff08;Android Interface Definition Language&#xff09;是Android接…

矩阵和线性代数的应用

矩阵和线性代数是数学中重要的概念&#xff0c;它们被广泛应用于物理、工程、计算机科学、经济学等众多领域。本文将讨论矩阵和线性代数的一些基本概念以及它们在实际应用中的重要性和影响。 一、矩阵和线性代数的基本概念 矩阵是由数字组成的矩形数组。它可以表示线性方程组…

线程池并发服务器

线程池技术 线程池技术是一种典型的生产者-消费者模型。 线程池技术是指能够保证所创建的任一线程都处于繁忙状态&#xff0c;而不需要频繁地为了某一任务而创建和销毁线程&#xff0c;因为系统在创建和销毁线程时所耗费的cpu资源很大。如果任务很多&#xff0c;频率很高&am…

Android中级——系统信息与安全机制

系统信息与安全机制系统信息获取/system/build.prop/procandroid.os.buildSystemPropertyPackageManagerActivityManagerpackages.xmlpermissions标签package标签perms标签安全机制Apk反编译apktooldex2jarjd-guiApk加密系统信息获取 /system/build.prop 存放一些配置信息&am…

Seaborn 变量分布分析

文章目录一、数据集1.1 下载数据集1.2 字段含义说明1.3 导入数据集二、初步分析2.1 缺失值分布查看2.2 异常值分布查看2.3 查看变量分布三、数值变量分析3.1 replot()&#xff1a;多个变量之间的关联关系3.2 lmplot()/regplot&#xff1a;分析两个变量的线性关系3.3 displot()&…

从前序与中序遍历序列构造二叉树——力扣105

题目描述 法一&#xff09;递归 复杂度分析 代码如下 class Solution { private:unordered_map<int, int> index;public:TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_ri…

Qt Quick - StackView

StackView 使用总结一、概述二、在应用中使用StackView三、基本的导航1. push Item2. pop Item3. replace Item四、深度链接五、寻找Item六、转换六、Item的所有权七、大小一、概述 StackView可以与一组相互链接的信息页面一起使用。例如&#xff0c;电子邮件应用程序具有单独…

HTML5 <img> 标签

HTML5 <img> 标签 实例 HTML5 <img>标签用于向网页中添加相关图片。 如何插入图像&#xff1a; <img src"smiley-2.gif" alt"Smiley face" width"42" height"42">尝试一下 &#xff08;更多实例见页面底部&…

基于营销类系统运营活动增长带来的数据库设计演进

一、前言 为了支持业务数据的不断增长&#xff0c;在数据库层面的性能提升主要体现在几个维度&#xff1a;1&#xff09;数据降级&#xff1b;2&#xff09;数据主题分而治之&#xff1b;3&#xff09;实时交易转异步&#xff1b;4&#xff09;硬件扩容&#xff0c;当然网上一…

question

4、Mysql高可用有几种方案&#xff0c;分别有什么特点? 特点优点缺点mysql group replication(MGR)组复制组内一半节点同意即可提交更改操作、最多支持 9 个节点、基于MRG插件、多节点写入支持、故障自动检测、引擎必须为 innodb、必须有主键、binlog 为 row强一致、paxos协议…

Arcgis Engine之打开MXD文档

Arcgis Engine之打开MXD文档概述方法一&#xff1a;方法二&#xff1a;概述 图层加载功能将用到MapControl 控件提供的LoadMxFile 方法。 该方法通过指定的*. Mxd文档路径直接获取 该方法第一个参数是文件路径&#xff0c; 第二个参数是MExd文档中地图的名称或索引&#xff0…

1.初识Earth Engine

Earth Engine平台是一个集科学分析和地理信息可视化的综合性平台&#xff0c;该平台提供丰富的API&#xff0c;以及工具帮助方便查看、计算、处理、分析大范围的各种影像等GIS数据。 基础数据 目前Earth Engine上已由几十PB的影像栅格数据及矢量数据数据地址。数据主要分为以…

Prometheus+Grafana从0到1搭建jvm监控

目 录1. 准备工作2. 添加配置2.1 添加maven依赖2.2 application.properties增加配置2.3 新增配置类2.4 配置Prometheus2.5 配置Grafana3. 小结在上一篇博客《 PrometheusMysqld_exporterGrafana从0到1搭建MySQL的可视化监控》&#xff0c;我们完成了对数据库的可视化监控搭建&a…

都说程序员就是吃青春饭,35岁就会被淘汰,我用自己的经历来告诉你事实

上个假期我回家了&#xff0c;遇到三姑六婆总会问我读研没读、工作怎么样、薪资多少等等问题&#xff0c;相信大家也都遇到过。我一般会用“在做程序员&#xff0c;写代码的这种话”来敷衍他们&#xff0c;但没想到他们懂得还挺多的&#xff0c;又搬出了一套关于程序员的理论&a…