《c语言修炼内功之第二种境界(看代码就是内存)之关键字系列二》

  • 前言:这个系列内容我会深入讲解一下c语言中的重点内容,会把每一个知识点讲的更加底层些,会增强大家的c语言内功,从内存维度看代码你会有不同的理解。
  • 这一节我主要讲一下多文件的知识,static修饰全局变量和局部变量,函数的作用及一些结论,函数命名的规则以及sizeof的用法。(在这里我们所用测试的编译器为vs2019)

多文件


  • 多文件的介绍

什么是多文件呢?我们在进入编译器(我们这里说vs)中都会有解决资源管理器。具体看图

 我们这里有头文件,资源文件还有源文件,这里资源文件暂且不说。

我们在创建某一个工程(我们在写c语言代码时候)一定在添加源文件时候后面一定是.c文件(我们的源文件是用来定义的,也就是代码的具体实现)。

 我们在创建头文件时候后面一定是.h文件(头文件用来声明的)。


在我们进行写代码的时候,我们会写函数和变量,那我们可不可以在外部的源文件实现函数,而在我们本文件进行调用呢?(我们的本文件为main.c,测试文件为test.c,头文件为test。h)我们接下来观察一下

 我们声明变量以后的情况

现象总结:

  1.  我们在既没有给变量又没给函数声明的时候,我们结果都会报错
  2. 我们给变量声明后,函数和变量的值都可以打印出来

结论:

我们可以通过多文件的形式在其他外部文件进行定义,在自己的主源文件进行调用,但是我们必须给变量和函数进行声明。虽然有时候(比如在有些编译器)没有给函数声明的时候不会出现报错的情况,但是为了确保警告,我们在声明外部变量和函数的时候我们要进行外部声明加上外部声明符号extern.


我们再来思考一个问题,我们可以在声明的时候进行定义变量么?

 我们再这样写代码的时候编译器就会自动给你报错,解释不允许对外部变量的局部声明使用初始化设定项。

那为什么不可以在进行外部声明的时候进行定义变量呢?

我们来回忆一下什么是变量,变量就是在内存的某个位置开辟的空间,既然需要开辟空间,我们在进行声明的时候只是声明没有进行定义(也就是初始化和赋值),所以我们没有在内存上开辟空间。

总结:

我们在进行声明变量的时候,不能设置初始化和赋值也就是内存并没有给开辟空间,所以我们在变量声明的时候必须加上extern(变量和函数都要加上extern)。

头文件


  • 我们为啥会有头文件呢?

首先,我们未来肯定在某一个公司上班(这里祝咱们都进大厂),我们在未来公司写代码,我们又两种方式,第一种方式是把所有的代码都写在main函数里,第二种方式就是分多个文件(也就是我们常说的模块化)来写代码。我想每个人都知道是分多个文件写代码不仅清晰而且便于后面别人看你的代码(可读性高)和修改代码更好一些。而相反你要把代码都写在main函数里,你要修改一处代码,可能都要改多处(甚至都有可能找不到),我们单纯的使用源文件在进行组织项目结构时,项目越大越复杂时,维护成本就越高,而当我们利用头文件,在组织项目时,就会减少大型项目的维护成本的问题。

  • 我们头文件都会包含哪些内容呢?

1.进行头文件的包含(c的库文件)

2.所有变量的声明

3.所有函数的声明

4.#define(宏),类型,typedef,struct,等等。

我们可以创建一个头文件,把这些内容都放在头文件里,而在源文件里要包含这个你创建的头文件     我们一般将c语言库的头文件用<>来包含,我们自己定义的头文件用" "(双引号)包含。

  • 头文件重复包含问题

头文件一定会被多个源文件包含,这个时候就会出现重复包含的问题

这里我们的解决方案就是加上#pragma once

  • 两个结论

  1. 全局变量可以跨文件访问
  2. 全局函数可以跨文件访问

这里又有个问题为啥可以跨文件访问呢?

在一定规模的项目一定是多文件的,在多文件之间后语一定要进行数据交互(比如我们的 main.c ,test.c ,test.h的内容要进行交互),如果我们没有跨文件,进行交互的成本就会变得很高。

关键字——static


  • static修饰全局变量

 

 所以static修饰局部变量时只能在本文件内部访问,不能被外部其他文件直接访问。

 既然不可以直接访问那可不可以间接访问呢?

 

 虽然使用static不能访问外部文件的变量内容,但我们可以使用函数嵌套调用的方式来进行间接访问。

总结:

static修饰局部变量时只能在本文件内部访问,不能被外部其他文件直接访问。由于调用变量具有临时性,函数调用的时候开辟空间并初始化,函数结束的时候,变量被释放。而这里static改变了全局变量的作用域,生命周期没有改变。

  • static修饰局部变量

没有static修饰的时候

void fun1() 
{ 
	int i = 0; 
	i++;
	printf("no static: i=%d\n", i);
}

int main() 
{
	for (int i = 0; i < 10; i++)
	{
		fun1();
		
	}
	 return 0; 
}

 

我们来观察一下没有static修饰的时候输出的全是1,那这是为什么呢?大家应该还记得上节课我们讲的变量只在代码块内部有效,出了代码块,变量会被销毁,所以每调用一次就会加一,然后出了代码块又变成0.

有static修饰局部变量的时候

void fun2()
{ 
	static int i = 0;
	i++; 
	printf("has static: i=%d\n", i); 
}
int main() 
{
	for (int i = 0; i < 10; i++)
	{
		fun2();
	}
	 return 0; 
}

 

我们再来观察一下没有static的情况,随着循环依次增加,那这又是为啥呢?这肯定就是static的作用了,与上次没有static的对比可以知道我们这里加1之后没有被销毁,而是直接保存上次计算的结果,依次累加。

总结:static修饰局部变量时变量只会初始化一次,在执行函数时变量没有被释放。这里static修饰局部变量的时候生命周期改变,而作用域没有被改变。

  这里我们说static修饰局部变量的时候,将临时变量变成了全局的生命周期,但作用域没有被改变。

函数地址空间图

 从这张图可以说明我们的局部变量时存放在栈区的,而当我们用static修饰局部变量的时候,我们将局部变量的位置由栈区变成了全局数据区,而全局数据区在整个“进程”运行生命周期都是有效的,所以static修饰局部变量只初始化一次当调用函数时不被释放具有了全局的生命周期,但并不是说它就是全局变量。全局变量和全局数据区是两回事。在全局数据区只能说明它的生命周期是全局的。

  • static修饰函数

static修饰函数与全局变量一样。

 static修饰局部变量时只能在本文件内部访问,不能被外部其他文件直接访问。

深入理解数据类型

  • 数据类型

 我们这里讲一下c语言的内置数据类型

  •  为啥要有数据类型呢?

我们都知道这些数据类型,但为啥要存在它们呢?我们有内存,一整块内存拿去用不可以么?比如我们来统计班级内的各学科成绩,最高也就不超过1000分,就这么小的数据我们用一个字节就可以存下去,如果把整个内存拿去用的话是不就会大量的浪费内存了。

好我们总结一下:

c语言中为啥要出现数据类型呢? 本质上讲对内存进行合理的划分,合理地进行划分,按需所求那类型为啥要分为那么多种呢?分这么多的类型是因为应用场景不同,为了解决应用场景对应得计算方式不同,需求空间的大小也是不同的。所以本质上就是用最小的成本解决各种多样化的场景问题。

  • 变量命名规则

按照c语言深度解剖这本书规则我们选择最优的规则来进行书写,但这些规则对于我们新手也不用时刻记在心里,因为每个公司都有这自己的命名规范到时候在按照公司的命名规范学习也不迟,我们这里推荐一些,便于后期复习用


      1 .     命名应当直观且可以拼读,可望文知意,便于记忆和阅读。
标识符最好采用英文单词或其组合不允许使用拼音。程序中的英文单词一般不要太复
杂,用词应当准确
      2 .     命名的长度应当符合“min-length && max-information”原则。
C 是一种简洁的语言 , 命名也应该是简洁的。例如变量名 MaxVal 就比
MaxValueUntilOverflow 好用。标识符的长度一般不要过长,较长的单词可通过去掉 元音 ” 形成缩写
另外,英文词尽量不缩写,特别是非常用专业名词,如果有缩写,在同一系统中对同一
单词必须使用相同的表示法,并且注明其意思。
      3 . 当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写。比如:
int CurrentVal
这样的名字看起来比较清晰,远比一长串字符好得多。
尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的确需要编
号。比如驱动开发时为管脚命名,非编号名字反而不好。
        4 .   对在多个文件之间共同使用的全局变量或函数要加范围限定符 ( 建议使用模块名
( 缩写 ) 作为范围限定符 ) (GUI_ etc)
程序中不得出现仅靠大小写区分的相似的标识符。
例如: int x, X; 变量 x X 容易混淆
void foo(int x); 函数 foo FOO 容易混淆
void FOO(float x);
这里还有一个要特别注意的就是 1 (数字 1 )和 l (小写字母 l )之间, 0 (数字 0 )和 o
(小写字母 o )之间的区别。这两对真是很难区分的,我曾经的一个同事就被这个问题折腾
了一次
       5 .   一个函数名禁止被用于其它之处
       6 .  所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
      7 .  考虑到习惯性问题,局部变量中可采用通用的命名方式,仅限于 n i j 等作
为循环变量使用。
一般来说习惯上用 n,m,i,j,k 等表示 int 类型的变量; c ch 等表示字符类型变量; a 等表
示数组; p 等表示指针。当然这仅仅是一般习惯,除了 i,j,k 等可以用来表示循环变量外,别
的字符变量名尽量不要使用。
       8 .  定义变量的同时千万千万别忘了初始化。定义变量时编译器并不一定清空了
这块内存,它的值可能是无效的数据
  • 关键字——sizeof

sizeof:是求变量所占空间大小单位是字节

  •  sizeof注意事项

虽然sizeof()用括号但sizeof不是函数,sizeof是运算符

int main()
{
	int a;
	int* pa = NULL;
	printf("%d\n", sizeof(int));
	printf("%d\n", sizeof(a));
	printf("%d", sizeof int);//其中这个是错误的sizeof和int都是关键字,关键字不能计算关键字的大小
	printf("%d\n", sizeof a );
	return 0;
}

好啦,今天就讲到这里再见!!bye bye

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值