C语言深度剖析--不定期更新的第二弹

news2024/9/21 12:28:55

在这里插入图片描述

好久不见,甚是想念。书接上回,继续前进!

关键字static-最名不副实的关键字

对extern声明的小小补充
当我要对一个函数进行声明的时候可不可以像如下情况:

extern int v_gal=100;

对这个变量进行了赋值,这是不可以的,因为声明并没有开辟空间,=100这是开辟空间或者初始化

所以我们得出结论

所有的变量声明的时候不能设置初始值

再来将讲一点:

我们这里是main.c这个源文件去调用test.c里的show()函数,倘若还有test1.c,test2.c,test3.c要去调用这个show()函数呢,所以:

单纯地使用源文件,组织项目结构的时候,项目越大越复杂的时候,维护成本会变得越来越高

这样也就诞生了头文件 .h

.h:头文件,组织项目结构的时候,减少大型项目的维护成本

接下来是对维护成本的解释:
还是拿刚刚的例子来举例,如果说我们要把上面的v_gal改成v_gal2,那么我们是不是要把所有的源文件声明的位置进行改动,如果说漏掉一个,,都会导致程序运行错误,这里还好只有四个源文件,但是如果是四十个,或者四百个呢,你试想一下。

综上,我们都在回答一个问题:为什么要有头文件

有关头文件的补充说明:

.h基本上都是要被多个源文件包含的,可能有一个问题,头文件被重复包含的问题,会导致运行效率降低

解决方案

在头文件开头写上#pragma once(**PS:**在VS2022中会默认加上#pragma once)
在这里插入图片描述
头文件包含以下内容:

  • C头文件

  • 所有的变量的声明

  • 所有的函数的声明

  • #define ,类型typedef, struct

在C语言中,包含头文件的两种形式

#include <stdio.h>这种是C语言库里面要包含的头文件

#include"test.h"是包含自定义的头文件

这个地方我们要特别注意的是函数的声明,因为在链接的过程中,test.c和main.c会进行匹配看是否有show()函数,即使说你已经定义了该函数,但是你没有声明的时候,编译器会忽视,所以要进行声明

书写如下:
在这里插入图片描述
不向里面添加函数体,道理和声明变量一个道理

注意

变量声明必须带上extern!!!

虽然在实际应用的时候编译器不会报错,但是你会无法区分是开辟空间还是声明变量

函数声明建议带上extern,为什么是建议呢。因为区别在于有没有函数体,编译器在识别的时候如果没有看到函数体会默认是函数的声明

几个小问题:

1.全局变量可以跨文件访问吗?可以

2.函数可以跨文件访问吗?可以

在具体的应用场景中,有没有可能我们不想让全局变量或者函数跨文件访问,指向本文件内部被访问

请出我们真正的主角-------static

看上面这幅图片,LNK表示的是链接的意思,这里是链接错误

static int g_val =100;//全局变量

结论1==:static修饰全局变量,该变量只能在本文件内被访问,不能被外部其他文件直接访问(可以被间接访问,如被函数调用)

​ static修饰函数,函数只能在本文件内被访问,不能在外部其他文件访问

为什么要这样做呢?

原因是static维护项目,提供安全保证

如果说把项目全部分装出去,那样别人就可以任意篡改,不具有安全性,static对其进行了很好的封装,相当于给对方私人定制。

第二个问题:static修饰全局变量会改变生命周期还是作用域?

答案是:作用域,只要它是全局变量,那么它就会随着程序的运行从开始到最后,但是被static修饰之后只能在本文件内被访问,所以说是改变了作用域

说完全局变量,那么再来谈谈局部变量

来看下面一段代码:

#include <stdio.h>
 static void fun()
{
	int i = 0;
	i++;
	printf("i=%d\n", i);
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		fun();
	}
	return 0;
}

运行结果:
在这里插入图片描述
如果是平常的情况来说的话,应该是依次递增的结果才是,局部变量被static修饰了之后发生了这样神奇的变化

原因如下:

1.局部变量,具有局域临时性

2.函数调用开辟空间并初始化,函数结束释放空间

在上面的代码基础上进行了些修改:

#include <stdio.h>
 static void fun()
{
	static int i = 0;//i的初始化动作,永远只会初始化一次,第一次!
     //初始化相当于定义,定义只有一次
     //定义的话,相当于表白心意,有且只有一次
	i++;
	printf("i=%d\n", i);
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		fun();
	}
	return 0;
}

这同样让我们大吃一惊,我们根据这样的既定事实,推导出一个结论:i在fun运行的过程中并没有被释放

由此基础上,再来得出一个结论:static修饰局部变量,更改的是局部变量的生命周期,改为全局的生命周期,作用域不同

回到前面,全局变量为什么可以跨文件访问?

因为有一定规模的项目,一定是多文件的,多个文件之间,后续一定要进行数据的“交互”(#include “test.h”,main.c),如果不能跨文件,“交互”成本会比较高

为什么临时变量具有临时性,全局变量具有全局性?

在这里插入图片描述
C语言地址空间是内存吗?

答案:不是

在任何有关C语言的书中是找不到相关知识的,这是操作系统的知识,有关进程地址空间

sizeof关键字-最冤枉的关键字

sizeof关键字(操作符),求特定类型对应开辟空间的大小

来看下面几行代码:

int a = 10;
	printf("%d\n", sizeof(a));//1
	printf("%d\n", sizeof(int));//2
	printf("%d\n", sizeof a);//3,基本事实,说明了什么呢?说明了sizeof不是函数,是关键字或者操作符
	printf("%d\n", sizeof int);//4

上面这四行里面,第四行为错的,sizeof是关键字,int 是变量类型,两个不能并列出现

C中为何要有类型:本质对内存合理化划分,按需索取

类型为什么在C中有这么多种:应用场景不同,解决应用场景对应的方法不同,需要的空间大小不同

本质:用最小的成本,解决多样化场景的问题

上面的两个问题也就回答了变量的定义,是什么,为什么,怎么办

需要注意的是:局部变量,不初始化,内容是随机值

对于命名的规范要求有以下几点:

1.见名知意

2.大小驼峰

3.数字字母下划线

整型的存储

联系之前,变量的创建是要在内存中开辟空间,空间的大小是根据不同类型决定的

那么,数据在所开辟的内存是如何存储的?

有符号数

首先要了解几个概念:原码,反码,补码

看下面几行代码:

int main()
{
    //计算机内存储的整型必须为补码
    int a=10;
    //任何数据在计算机中,必须要转化成二进制,为什么?计算机只认识二进制
    //符号位(0,1)+数据位
    //有符号数且为正数,原码=反码=补码
    //0000 0000 0000 0000 0000 0000 0000 1010
    int b=-20;
    //有符号数且为负数
    //1000 0000 0000 0000 0000 0000 0001 0100 原码
    //1111 1111 1111 1111 1111 1111 1110 1011 反码
    //1111 1111 1111 1111 1111 1111 1110 1100 补码
}

需要注意的是符号位需要参与计算

该怎样把补码转为原码呢?

有两种方法:

方法1:

补码-1,再按位取反变为原码

方法2:

将补码变为反码,反码+1变为原码

上面这两种方法,理解上推荐方法1,但是实际应用更喜欢方法二

方法二是通过计算机硬件完成的,可以用一条计算机硬件电路,完成转化

补充:整型存储的本质

unsigned int a=10;
unsigned int b=-120;//这并不会报错

数据保存到空间里,空间里只能存储二进制,那么也就意味着负数保存到空间里,先转化成二进制
在这里插入图片描述
可能就要问了,前面加上这个,什么时候起到效果?

是在读取的时候具有意义!类型决定了如何解释空间内部保存的二进制序列

再来看下面几行:

unsigned int b=-120;
printf("%u\n",b);
printf("%d\n",b);

在这里插入图片描述
这里需要了解两个概念:

变量存的过程:字面数据必须先转为补码,在放入空间当中。所以,所谓符号位,完全看变量是否携带±号,和变量是否有符号无关

变量取得的过程:取数据一定要看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转为十进制。如果需要,则需要转为原码,然后才能识别。(当然,最高符号位在哪,又要明确大小端)

看一个数据的时候:
1.先看自身类型决定

a.先看符号位

b.确定原反补

二进制快速转化口诀
1->20

10->21

100->22

1000->23

1后面跟n个比特位,就是2n

拿67举例,67=64+2+1

可以无脑写出32个比特位,然后拆26+21+20,从右往左数,对应的写1

0000 0000 0000 0000 0000 0000 0100 0011

大小端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那么什么叫做取值范围?

所谓的特定数据类型,能表示多少个数据类型,取决于,多个比特位对应的排列组合的顺序

从上面这张图来看,怎么表示数字0呢?

0,有且只有一种表示方案,0000 0000 还有一种是1000 0000,根据上面说的二进制转十进制的口诀,不难发现只有128和-128两种可能,根据第一位为符号位,很容易确定出是-128,所以上面的

接下来深入讨论一下为什么一定是-128(根据上面符号位来看,只是表象)

在这里插入图片描述
根据上面这幅图,变量的初始化会先开辟空间,然后十进制的数转成二进制,存储在空间中是补码,但是char是一个字节,也就是八个比特位,1 1000 0000 有九位,就会发生截断,截断后面八位,所以是-128
在这里插入图片描述
在原先的基础上又有些许变化,不能够根据1000 0000 来推断出其补码,原因是本身就是错的,被截断了,但是可以得到一种结论:

1000 0000->-128

所有的计算全部被转化成了加法,依靠的是补码

为什么要转化成加法呢?

因为这种是在CPU内进行的,全转化为加法,只要设计一套硬件电路就够了

一道练习题

#include <stdio.h>
#include <string.h>
#include <Windows.h>

int main()
{
    char a[1000];
    for(int i=0;i<1000;i++)
    {
        a[i]=-1-i;
}
	printf("%d\n",strlen(a));
}

在这里插入图片描述
再看一道

#include <stdio.h>
#include <Windows.h>
int main()
{
    int i=-20;
    unsigned int j=10;
    printf("%d\n",i+j);
    printf("%u\n",i+j);
    system("pause");
	return 0;
}

在这里插入图片描述
变形:

#include <stdio.h>
#include <Windows.h>
int main()
{
    unsigned int i;
    for(int i=9;i>=0;i--)
    {
        printf("%u\n",i);
        Sleep(1000);
	}
    return 0;
}

在这里插入图片描述

if-else组合

什么是语句?**

分号;隔开的就是一条语句

如:

printf("%d\n",a);

什么是表达式?**

用各种操作符把变量连起来,形成有意义的式子

第一个结论:if(0)可以用于注释代码,

但是不推荐,因为if(0)里面的代码也会进入到编译阶段,成本会大一点,如果有人把0改成1的话,运行效率会变低

第二个结论:C语言中,0为假,非0为真

第三个结论:if条件的执行流程,1.先执行()中的表达式,得到真假结果 2.条件 判定功能 3 .进行 分支功能

Bool类型

C语言有没有bool类型?

C99之前,主要是C90是没有的,目前大部分书,都是认为没有的。因为书籍一般会落后于行业。

但是C99引入_Bool类型(在新增头文件stdbool.h中,被重新用宏写成了bool,为了保证C/C++兼容性)

后面万一要用到bool,推荐C99,不要用微软的,因为微软的可移植性差,

总结

1.优先使用C90,就是我们之前以及后面一直用的方式

2.万一非得使用bool,推荐C99标准,不推荐MS定义

有以下三种书写方式:

//1
int flag=0;
if(flag==0)
{
    //条件相当于int x==0
    //不推荐
    printf("1\n");
}
//2
if(flag==false)
{
    //必须要在C99的环境下才行,也就是要包含头文件<stdbool.h>
}
//3
if(flag)
{
    //推荐
    //直观反映出flag=bool
    printf("3\n");
}

结论==:bool类型,直接判定,不用操作符和特定值进行比较

这里又产生了一个疑问:

在if中,结论所说的写法是否符合if本身语法?

符合的

浮点类型

浮点数在内存中存储,并不是像我们想的那样,是完整存储的,在十进制化为二进制,有可能存在精度损失

在这里插入图片描述
注意点:

1.浮点数在进行比较的时候,绝对不能使用==来进行比较

2.浮点数本身有精度损失,进而导致各种结果有细微的差别

解决方案:
在这里插入图片描述

浮点值的比较

在这里插入图片描述
浮点数和0比较**:

1.浮点数存储的时候,是有精度损失的

2.浮点数是不能进行==比较的

3.if(fabs(a-b)<DBL_EPSION){}

4.要不要<= ,细节 不要

注意点:0 \0 NULL都是一个意思,表示0

如何理解强制类型转换?

强制类型转换:不改变内存中的数据,只改变对应的类型

真实的转换:改变内存中的数据,只改变对应的类型

在这里插入图片描述

指针变量与“零”值进行比较

指针变量与“零”值进行比较,用if语句如何写?

有以下三种写法

int*p=NULL;

(1)if(p==0) if(p!=0)

(2)if§ if(!p)

(3)if(NULL==p) if(NULL!=p)

其中最推荐写法**(3)**

写法(1)如果去掉int*p=NULL,无法区分p是指针还是整型;写法(2)很容易理解成是一个BOOL类型,如果p=0,那么就不会执行了

写法(3)还有一个妙处,我们平常在实际应用中不怎么接触到==,如果把NULL提前,如果发现漏写=,会有提醒报错的

else到底与哪个if配对?

来看下面一段代码:

int main()
{
    int x=1;
    int y=1;
    if(10==x)
        if(11==y)
            printf("1234\n");
    else
        printf("5678\n");
    return 0;
}

在这里插入图片描述
结果如上

else会与最近的if配对,但是上面两个if里面的条件都是不对的,所以程序结束

在上面这几行代码的基础上做一点修改

int main()
{
    int x=10;
    int y=1;
    if(10==x)
        if(11==y)
            printf("1234\n");
    else
        printf("5678\n");
    return 0;
}

在这里插入图片描述
结果如上,因为if(10== x)满足,所以进入条件,但是if(11==y)不满足,所以进入else中

注意一个问题:

int main()
{
	int flag=0;
	if(flag);
	printf("Hello World");
}

在这里插入图片描述

非常的奇怪,明明if里面的条件为0,在C语言里面认为是假的,为什么还能够打印出来呢,原因就在;上,因为语句看到;就是结束了,然后if语句没有加上花括号,所以只会和最近的一个语句配对,正好是分号,对下面的一行代码没有影响

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

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

相关文章

相互作用先验下的 3D 分子生成扩散模型 - IPDiff 评测

IPDiff 是一个基于蛋白质-配体相互作用先验引导的扩散模型&#xff0c;首次把配体-靶标蛋白相互作用引入到扩散模型的扩散和采样过程中&#xff0c;用于蛋白质&#xff08;口袋&#xff09;特异性的三维分子生成。 本文将对 IPDiff 实际的分子生成能力进行评测。 一、背景介绍 …

动态规划的解题思想

1. 从斐波那契数列说起 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c; &#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0, F(2) 1 F&#xff08;n&#xff09; F&…

【C++】C++ STL探索:Vector使用与背后底层逻辑

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现 在string类文章中提及了STL容器间的接口是大差不差的&#xff0c;本篇将直…

虚幻5|不同骨骼受到不同伤害|小知识(2)

1.蓝图创建一个结构&#xff0c;B_BoneDamage 结构里添加一个浮点变量&#xff0c;表示伤害倍数 2.当我们创建了一个结构&#xff0c;就需要创建一个数据表格&#xff0c;数据表格可以选择对应的结构 不同骨骼不同倍数伤害&#xff0c;骨骼要对应骨骼网格体的名称 3.把我们br…

C#继承_里氏替换原则

知识点一&#xff1a;基本概念 知识点二&#xff1a;基本实现 #region 知识点二基本实现class GameObject{}class Player : GameObject{public void PlayerAtk(){Console.WriteLine("玩家攻击");}}class Monster : GameObject{public void PlayerAtk(){Console.Writ…

NLP从零开始------文本中阶处理之序列到序列模型(完整版)

1. 序列到序列模型简介 序列到序列( sequence to sequence, seq2seq) 是指输入和输出各为一个序列(如一句话) 的任务。本节将输入序列称作源序列&#xff0c;输出序列称作目标序列。序列到序列有非常多的重要应用&#xff0c; 其中最有名的是机器翻译( machine translation), 机…

消息中间件都有哪些

RabbitMQ&#xff1a;这可是一个开源的消息代理软件&#xff0c;也叫消息中间件。它支持多种消息传递协议&#xff0c;可以轻松地在分布式系统中进行可靠的消息传递。 Kafka&#xff1a;Apache Kafka是一个分布式流处理平台&#xff0c;它主要用于处理实时数据流。Kafka的设计初…

时下改变AI的6大NLP语言模型

本文将深入研究大语言模型领域的最新进展&#xff0c;改变AI的6大NLP语言模型&#xff0c;每个模型能够引入的增强功能、以及潜在功能应用与限制。 在快速发展的人工智能&#xff08;AI&#xff09;领域&#xff0c;自然语言处理&#xff08;Natural Language Processing&#…

6.1均值滤波

目录 实验原理 示例代码 运行结果 注意事项 总结 实验代码 运行结果 实验原理 https://zhuanlan.zhihu.com/p/76188487 在OpenCV中&#xff0c;cv::blur函数用于对图像进行均值滤波&#xff08;Mean Filter&#xff09;&#xff0c;这是一种线性滤波技术&#xff0c;主…

科技信贷业务怎么寻找客户?

在科技信贷业务领域&#xff0c;寻找客户的痛点主要集中在以下几个方面&#xff1a; 1.风险评估难题&#xff1a;科技型企业尤其是初创企业&#xff0c;往往缺乏足够的历史数据和抵押物&#xff0c;这使得金融机构在评估其信用风险时面临较大挑战。由于科技企业的研发周期长、…

视频监控系统布局策略:EasyCVR视频汇聚平台构建高效、全面的安全防线

随着科技的飞速发展&#xff0c;视频监控系统已成为现代社会安全防范的重要组成部分&#xff0c;广泛应用于公共场所、企业园区、住宅小区等各个领域。一个科学合理的视频监控系统布局与选型策略&#xff0c;不仅能够显著提升安全监控的效率和效果&#xff0c;还能在关键时刻提…

我用 GPT 学占星

最近对占星赶兴趣&#xff0c;但是看到星盘中好多名词&#xff0c;不懂是什么意思&#xff1f;所以直接问 gpt &#xff0c; 发现回答的真的很棒&#x1f389; &#xff01; 假如我想知道各个状态的具体是根据什么数据来显示的&#xff1f; 分分钟解决了我的问题&#xff1b; 我…

OpenFeign请求拦截器,注入配置属性类(@ConfigurationProperties),添加配置文件(yml)中的token到请求头

一、需求 OpenFeign请求拦截器&#xff0c;注入配置属性类&#xff08;ConfigurationProperties&#xff09;&#xff0c;添加配置文件&#xff08;yml&#xff09;中的token到请求头 在使用Spring Boot结合OpenFeign进行微服务间调用时&#xff0c;需要在发起HTTP请求时添加一…

【Linux高级命令】1_进程和端口相关命令

文章目录 一、重启和关机的命令【了解】二、进程2.1 查看系统进程&#xff1a;ps 【重点】2.1.1 grep结合管道符去查找特定的进程信息 2.2 实时查看系统的进程信息&#xff1a;top2.3 关闭进程&#xff1a;kill【重点】 三、端口3.1 查看系统监听端口&#xff1a;netstat【重点…

Notepad++ 下载安装教程

目录 1.下教程 2.安装教程 1.下教程 Downloads | Notepad (notepad-plus-plus.org) 进入下载地址后选择最新版点击连接 点击链接后&#xff0c;向下滑动&#xff0c;下载适合自己电脑版本的安装包 这里大家没有梯子可能打不开页面&#xff0c;可以直接从本文开头下载。 2.安…

【vscode】vscode paste image插件设置

本文首发于 ❄️慕雪的寒舍 vscode编辑md文件的时候&#xff0c;如果想插入图片&#xff0c;自带的粘贴只会粘贴到当前目录下&#xff0c;也没有文件重命名&#xff0c;很不友好。 在扩展商店里面有mushan的Paste Image插件&#xff0c;相比自带的&#xff0c;更加友好一点。但…

物联网之ESP32控制舵机、通过网页设置舵机角度、Web服务、舵机原理、接线、Arduino、WiFi、Http

MENU 前言原理硬件电路设计软件程序设计LEDC输出PWM信号使用第三方库控制舵机网页控制舵机 前言 舵机在电子产品中非常常见&#xff0c;比如四足机器人、固定翼航模等都有应用&#xff0c;因此学习舵机对电子制作非常有意义。本文章使用Arguino的PWM对SG90舵机旋转角度控制。 原…

什么是Hash冲突?如何解决Hash冲突?

目录 一丶什么是Hash冲突 二丶如何解决Hash冲突 简介&#xff1a;Hash叫做‘散列表’&#xff0c;就是把任意长度输入&#xff0c;通过散列算法&#xff0c;变成固定长度输出&#xff0c;该输出结果是散列值。其实这种转换是一种压缩映射&#xff0c;散列表的空间通常小于输入…

深入解析EF Core并发控制:乐观与悲观策略的全面对比与实战应用

1. 前言 在使用EF Core开发应用程序时&#xff0c;并发控制是确保数据一致性的重要机制。EF Core 提供了两种主要的并发控制策略&#xff1a;乐观并发控制和悲观并发控制。它们各自有不同的应用场景和实现方式。本文将详细介绍这两种并发控制的区别、常见的应用场景&#xff0…

fastadmin 文件上传七牛云

1-安装七牛云官方SDK composer require qiniu/php-sdk 2-七牛云配置 <?phpnamespace app\common\controller;use Qiniu\Storage\BucketManager; use think\Config; use Qiniu\Auth; use Qiniu\Storage\UploadManager; use think\Controller; use think\Db;/*** 七牛基类*…