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

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

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

对extern声明的小小补充

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

c 复制代码
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表示的是链接的意思,这里是链接错误

c 复制代码
static int g_val =100;//全局变量

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

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

为什么要这样做呢?

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

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

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

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

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

来看下面一段代码:

c 复制代码
#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.函数调用开辟空间并初始化,函数结束释放空间

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

c 复制代码
#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关键字(操作符),求特定类型对应开辟空间的大小

来看下面几行代码:

c 复制代码
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.数字字母下划线

整型的存储

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

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

有符号数

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

看下面几行代码:

c 复制代码
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,但是实际应用更喜欢方法二

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

补充:整型存储的本质

c 复制代码
unsigned int a=10;
unsigned int b=-120;//这并不会报错

数据保存到空间里,空间里只能存储二进制,那么也就意味着负数保存到空间里,先转化成二进制

可能就要问了,前面加上这个,什么时候起到效果?

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

再来看下面几行:

c 复制代码
unsigned int b=-120;
printf("%u\n",b);
printf("%d\n",b);

这里需要了解两个概念:

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

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

看一个数据的时候:

1.先看自身类型决定

a.先看符号位

b.确定原反补

二进制快速转化口诀:

1->2^0^

10->2^1^

100->2^2^

1000->2^3^

...

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

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

可以无脑写出32个比特位,然后拆2^6^+2^1^+2^0^,从右往左数,对应的写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内进行的,全转化为加法,只要设计一套硬件电路就够了

一道练习题

c 复制代码
#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));
}


再看一道

c 复制代码
#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;
}

变形:

c 复制代码
#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组合

什么是语句?**

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

如:

c 复制代码
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定义

有以下三种书写方式:

c 复制代码
//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配对?

来看下面一段代码:

c 复制代码
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里面的条件都是不对的,所以程序结束

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

c 复制代码
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中

注意一个问题:

c 复制代码
int main()
{
	int flag=0;
	if(flag);
	printf("Hello World");
}

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

相关推荐
远望清一色几秒前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself10 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041515 分钟前
J2EE平台
java·java-ee
小黄人软件19 分钟前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
XiaoLeisj22 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man25 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*26 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家27 分钟前
go语言中package详解
开发语言·golang·xcode
llllinuuu28 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s28 分钟前
Golang--协程和管道
开发语言·后端·golang