C语言——9-c语言的组成

"对象" 就是一个东西, "名字" --> 标识符

变量,数组,函数,类型,....

1 作用域

什么是作用域 ?

一个东西(对象) 起作用的范围. "可见性"

一个C语言工程,可以由多个.c文件组成.

  • 整个工程的作用域

    在工程的所有文件中,都可以起作用

  • 本文件作用域

    只在本文件(.c/.h)起作用

  • 函数作用域

    只在某个函数中起作用.

  • 代码块作用域

    只在某个代码块中起作用.

根据作用域不同,把变量分为如下几类.

1.1 全局变量 : 定义在函数外部的变量

  • 加了static 修饰的全局变量 和 没有加static修饰的全局变量

    int a = 5;//没加static修饰的全局变量
    
    static int b = 6;//加了static修饰的全局变量
    
    void func()
    {
     
    }
    
  • a 普通的全局变量(没加static修饰的全局变量)

    作用域是在整个工程起作用.

    1.c					2.c
     
    //全局变量
    int a = 5;
    				extern int a;
    

    extern 用来声明一个对象,告诉编译器,后面的这个对象是在外部定义的.

    extern 声明的语法 :

    extern 外部对象的类型 外部对象名;
    
  • b 加了static修饰的全局变量 : " 本文件有效 "

    如果你定义了一个全局变量,只想在本文件中可用,其他文件不可用.

    这个时候,需要你在定义全局变量时,在最前面加一个"static" "本地的"

    static int c = 8;
    

1.2 局部变量 : 定义在 {}内的变量

局部变量的作用域 : 在 定义他的{ } 内有效

函数作用域

代码块的作用域

根据作用域的不同,把函数分为如下 :

1.3 全局函数 : 普通的,没有加staitc修的函数

全局函数的作用域是在整个工程有效

1.c						  2.c
int sum(int a,int b)
{
 return a + b;
}
						//extern告诉编译器,后面的那个对象是外部定义的
						extern int sum(int,int);	
						//告诉编译器, sum是一个函数
						//他可能是外部定义的,也有可能是本文件定义的.
						int sum(int,int);

1.4 加了static修饰的函数

static函数作用域是在本文件中有效

程序员自定义的类型(如 : 结构体,联合体) 作用域,只在他声明的文件 或 函数内部有效!!!

自定义的类型的作用域, 不可以跨文件.why

类型只在编译阶段有意义,并且编译都是一个一个文件编译的.

1.c									  2.c
struct test
{
 int a;
 int b;
}
//这个类型struct test只能在1.c中用.
									想在2.c中用struct test该怎么办?
                                  只能在2.c中重新定义struct test这个类型
                                     struct test
                                     {
                                         int a;
                                         int b;
                                     }

关于C语言中对象的名字, 能不能"重名"的问题.

一首诗 :

同名不要紧,
只要域不同."域" 作用域
具体是哪个,
往上就近找,"就近原则"

2 生存期

什么是生存期?

生存期是指一个对象从生到死的期间,''活着的期间''

  • 随文件系统持续性

    只要你的文件系统存在,那么这个对象就一直存在.

    如 : 你保存的硬盘上的 "电影" "代码"

  • 随进程持续性

    进程 : 一个正在运行的程序.

    随进程持续性,是指这个对象,进程启动的时候,他就存在,并且一直存在到进程死亡,

    进程启动时再执行第一条用户指针前,就会给那些"随进程持续性的" 对象,分配空间,

    并且这个空间一直到你进程死亡.

    如 :

    ​ 全局变量, 函数(代码) , static修饰的局部变量.

  • 随代码块持续性

    是指这个对象,在运行他所属的代码块时,才分配空间给他,

    并且当代码块执行完毕时,他的空间就会自动回收.

    如 :

    ​ 普通的局部变量(没有加staitc修饰的局部变量) ,生存期就是随代码块持续性.

  • 加了static修饰的局部变量

    加了static修饰的局部变量,该局部变量的生存期就变成 "随进程持续性"

  • image-20230718105524551

上述的代码中 :
	局部变量a的作用域  在整个func内有效 "代码块有效"
   局部变量 b 的作用域  在真个fun内有效 "代码块有效"
       
 局部变量 a 的生存期 "随代码块持续性"
 局部变量 b(加了static) 的生存期 "随进程持续性"
       进程启动时(在运行 fun 前),系统就会为 b 分配空间,不管你后面fun 有没有运行
       整个进程运行期间, b的空间一直不回收,直到进程结束.

思考 :

1. 初始化语句 和 赋值语句的区别?
初始化语句 :
	定义一个对象并给对象赋值的语句, 初始化语句.
    初始化语句,只会在给对象分配空间的时, 执行一次!!!
赋值语句:
	每次运行他,就会执行.
        int a = 5;//初始化语句

		a = 1024;//赋值语句


3 C文件组成

一般来说, .c文件有如下部分组成 :

3.1 头文件包含部分

include : 包含命令

include 作用是把后面的那个文件的内容在此处展开

include <stdio.h>

include <stdlib.h>

include "led.h"

include "lcd.h"

include "bmp.h"

<> " " 有什么区别?

include 后面只指定了文件名, 并没有指定该文件的绝对路径.

include 他到哪里去找这个文件呢?

<> 和 " " 区别是在于 : <> 和 " " 搜索路径不一样

include <stdio.h> 会到编译器或系统指定的标准路径下去找,如 : /usr/include

include "lcd.h" 先到工程当前的路径下去找,找不到,再去标准路径下去找.

3.2 宏定义 和 声明部分

(1) 宏

什么是宏?

宏就是一个可以替换其他东西的东西.''替换''

宏定义的语法 :

#define 宏名 要替换的内容(表达式或语句)
//宏定义不能跨行,遇到换行符(\n)就认为宏定义结束了

宏有两种 :

不带参数的宏 :

如 :

#define N 10
#define PI 3.14

带参数的宏 :

写法有点类似函数

如 :

#define MAX(a,b)  ((a > b) ? (a) : (b))  //后面用括号括起来,避免宏的副作用
//宏里面的参数猎列表,不需要类型
int m = m + MAX(3,5 + 9);
==>
 m = m +  ((3) > (5 + 9) ? (3) : (5 + 9))
//"宏仅替换" ,不会做其他处理 

练习 :

1.分析如下程序的结果 :

#define MAX(a,b)  ((a > b) ? (a) : (b)) 

int main()
{
 int i = 6;
 int j = 5;
 int m;
 m = MAX(i++,j);//==> m = ( (i++) > j ? (i++) : (j) )
 
 printf("m = %d\n",m);//7
 printf("i = %d\n",i);//8
 printf("j = %d\n",j);//5
}

//宏的副作用 :宏展开后, 表达式可能会做多次
如何修改 ?  用函数

为什么要用宏? 宏的作用

(1) 替换

(2) 宏名 "字面意思" <<<<<

我们用宏能实现的功能可以用函数来实现的.

用函数能实现的功能,有时候也可以用宏来实现.

宏和函数有什么区别?

宏仅替换,在编译的时候展开的; 函数是运行时候展开的,占运行时间.

宏有时候会有副作用.

(2) 声明

什么是声明?

C语言的声明就是把一个符号(名字,标识符) 与一个对象或类型关联起来.

让编译器知道, 这个符号(这个名字) 他是什么东西.

为什么要声明?

在编译C源文件时,是一个文件一个文件去编译的,并且每个文件是

从第一行到最后一行,一行一行的去编译.

当编译器.碰到一个不认识的或没有定义的符号时,

他就会认为是一个 undefine/undeclared 的东西.

编译就结束了(不成功).

如何声明呢?

  • <1> 外部变量(数组)的声明
extern 外部变量的类型  外部变量名;

  • <2> 外部函数的声明
{extern} 函数返回值类型 函数名(函数参数的类型列表);
{ }表示可选
  • <3> 类型的声明 或定义

自定义的类型 作用域最大就是本文件有效. ctrl + c ,ctrl + v 拷贝过去

  • <4> 全局变量 或 函数的定义
int a = 5;

void print_a(void)
{
   
}

C源程序文件是一个.c文件,这个.c文件由函数组成,C语言的指令(如 : if/switch/while/for/...) 必须写在函数的内部 , 可以由多个函数组成一个.c文件,一个工程可以由多个.c文件组成.

但是一个工程只能并且必须有一个mian函数,这个main函数就是你程序的入口.

同时main函数结束,程序就执行完毕.

4 .h文件组成

一个工程里面可不可以不要.h文件? 当然可以

.h 文件到底有什么作用?

为什么需要用到呢?

.h文件如何写呢?

liubei.c		  zhangfei.c			    guanyu.c (zhugeliang)
 
int cm = 7;									int sum(int a,int b)
				  extern int cm;			{
 											return a + b
											}
				  
				  //需要用到guanyu.c的sum
				  extern int sum(int,int);
				  void func()
               {
                   sum(3,4);
               }
....

每次用到liubei.c中变量或类型或guanyu.c中的函数,都需要问一下 guanyu(如何使用,参数是什么等问题),

并且需要他们各自的源文件中, 包含 函数或类型或变量的声明 .

...

能不能把一些函数的说明(使用说明),类型的说明,变量的说明等等,

写成一个"告示/使用说明书"

接口文件?

这个接口文件("使用说明书") 就是头文件.

guanyu.h

/*
	sum : 求两个整数的和
	@a : 第一个加数
	@b :第二个加数
	返回值 :
		返回 a + b 的和
*/
int sum(int a,int b);

所有需要用到guanyu.c里面的函数,类型,变量的其他文件,就只需要包含guanyu.h

include "guangyu.h"

这个就是头文件的作用!!!

头文件就是一个接口说明文件,包含一些函数的声明(说明),类型的声明(说明),全局变量的声明...

所有的对外接口(可以给别人用的 函数, 类型,变量)

其他文件用,就直接包含就可以了.

.h文件(接口描述文件)包含哪些内容呢?

包含一些声明 : 函数的声明 , 外部变量的声明, 类型的声明

头文件一般不包含具体代码的实现,.h就是一个接口说明文件.

头文件包含的原则 : 用到谁包含谁

.h文件如何来写呢?

guanyu.h
 struct test
 {
     int a;
     int b;
 };

zhangfei.c
 #include "guanyu.h"
 #include "zhangfei.h"
  //间接 或直接多次包含了guanyu.h
 
zhangfei.h
 //也用到了 struct test
 #include "guanyu.h"
 
 //也定义了一些其他的类型,如 :
 struct abc
 {
     char c;
 };
 

image-20230718153605356

image-20230718151336342

同一个头文件,如果被其他文件重复多次包含,就会造成 类型的 "重定义 redefinition"的错误

==> 头文件不能被重复包含!!!!

如何防止头文件被重复包含呢?

为了防止头文件内容被重复包含 , 约定 : 头文件的写法(格式) 如下 :

guanyu.h

#ifndef __文件名大写_H__ //如果 "__文件名大写_H__"没有被定义
#define __文件名大写_H__ //定义 "__文件名大写_H__"

//...头文件的内容
struct test
{
 int a;
 int b;
};

#endif  //结束前面的 #ifndef
#ifndef __GUANYU_H__
#define __GUANYU_H__

/*
	sum : 求两个整数的和
	@a : 第一个加数
	@b :第二个加数
	返回值 :
		返回 a + b 的和
*/
int sum(int a,int b);

struct test
{
 int a;
 int b;
};

#endif

5 关于头文件

xxx.c 具体功能代码的实现
xxx.h 接口文件的说明
  • (1) 头文件只是一个接口文件

    一般只包含声明部分或类型定义部分

    不能包含具体代码的实现!!!

  • (2) "模块化思想"

    array.c/array.h
    lcd.c/lcd.h
    ...
    .c 是具体的代码和功能的实现
    .h 是声明和接口描述
    
  • (3) 头文件的写法(格式) 如下:

    #ifndef __文件名大写_H__ 
    #define __文件名大写_H__ 
    
    //...头文件的内容
    
    #endif  //结束前面的 
    
  • (4) 头文件包含/引用的原则

    谁用谁包含

    头文件也可以包含其他头文件

练习 1 :

写一个sum.c ,sum.h,main.c 三个文件,main.c中调用sum.c中求和函数.

注意sum.h的写法.

sum.c

#include "sum.h"
/*
	sum : 求两个整数的和
	@a : 第一个加数
	@b : 第二个加数
	返回值 :
		返回 a + b 的和
*/
int sum(int a,int b)
{
 return a + b;
}

sum.h

#ifndef __SUM_H__
#define __SUM_H__

/*
	sum : 求两个整数的和
	@a : 第一个加数
	@b : 第二个加数
	返回值 :
		返回 a + b 的和
*/
int sum(int a,int b);

#endif

main.c

#include <stdio.h>
#include "sum.h"

int main()
{
 int s = sum(3,8);
 printf("sum = %d\n",s);
 
 return 0;
}
posted @ 2023-07-22 15:33  风恬月淡时  阅读(113)  评论(0)    收藏  举报