--- title: C语言-1 date: 2022-11-01 15:40:14 tags: [C语言, 笔记] categories: 笔记 --- ## C 简介 C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的。C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现。 在 1978 年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)制作了 C 的第一个公开可用的描述,现在被称为 K&R 标准。 UNIX 操作系统,C编译器,和几乎所有的 UNIX 应用程序都是用 C 语言编写的。由于各种原因,C 语言现在已经成为一种广泛使用的专业语言。 其主要有一下几个特点: > * 易于学习. > * 结构化语言. > * 可以生产高效率的程序. > * 可以处理底层的活动. > * 可以在多种计算机平台上编译. ## 关于C - C 语言是为了UNIX操作系统而被发明的. - C 语言是以B语言为基础的, B语言在大约1970年被引进的. - C 语言的标准是与1988年由美国国家标准协会(ANSI, 全称American National Standard Institute)制定的. - 截至1973年,UNIX操作系统是完全使用C语言编写的. - 目前,C 语言是最广泛使用的系统程序设计语言. - 大多数先进的软件都是使用C语言实现的. - 当今流行的Linux操作系统和RDBMS(Relational Database Management System:关系数据库管理系统) MySQL都是使用C语言编写的. ## 为什么要用C C 语言最初是用于系统开发工作,特别是组成操作系统的程序。由于 C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样,所以采用 C 语言作为系统开发语言。 几个使用 C 的实例: - 操作系统 - 语言编译器 - 汇编器 - 文本编辑器 - 打印机 - 网络驱动器 - 现代程序 - 数据库 - 语言解释器 - 实体工具 C 程序 一个 C 语言程序,可以是 3 行,也可以是数百万行,它可以写在一个或多个扩展名为 “.c” 的文本文件中,例如,hello.c。可以使用 VScode/Dev-C++/Vim或者其他编辑器来编写 C 语言程序。 ## C11 C11(也被称为C1X)指ISO标准ISO/IEC 9899:2011。在它之前的C语言标准为C99。 ## 新特性 - 对齐处理(Alignment)的标准化(包括_Alignas标志符,alignof运算符,aligned_alloc函数以及头文件)。 - _Noreturn 函数标记,类似于 gcc **attribute**((noreturn))。 - _Generic 关键字。 - 多线程(Multithreading)支持,包括: - _Thread_local存储类型标识符,头文件,里面包含了线程的创建和管理函数。 - _Atomic类型修饰符和头文件。 - 增强的Unicode的支持。基于C Unicode技术报告ISO/IEC TR 19769:2004,增强了对Unicode的支持。包括为UTF-16/UTF-32编码增加了char16_t和char32_t数据类型,提供了包含unicode字符串转换函数的头文件。 - 删除了 gets() 函数,使用一个新的更安全的函数gets_s()替代。 - 增加了边界检查函数接口,定义了新的安全的函数,例如 fopen_s(),strcat_s() 等等。 - 增加了更多浮点处理宏(宏)。 - 匿名结构体/联合体支持。这个在gcc早已存在,C11将其引入标准。 - 静态断言(Static assertions),_Static_assert(),在解释 #if 和 #error 之后被处理。 - 新的 fopen() 模式,(“…x”)。类似 POSIX 中的 O_CREAT|O_EXCL,在文件锁中比较常用。 - 新增 quick_exit() 函数作为第三种终止程序的方式。当 exit()失败时可以做最少的清理工作。 ## 环境配置 最简单的环境配置只需要配置C的编译环境,C语言对应的工具主要有以下三种: > * GCC/G++ (Window可以用[MinGW]([mingw-w64](https://www.mingw-w64.org/))) > * MSVC (可以通过[Visual Studio installer](https://visualstudio.microsoft.com/)下载C++的桌面开发环境下载) > * [CLANG (LLVM) ](https://clang.com) 下载对应的编译工具并添加进环境即可 ## 程序结构 ### C “hello world” 实例 C 程序主要包括以下几个部分: - 预处理指令 - 函数 - 变量 - 语句 & 表达式 - 注释 hello world!实例: ```c #include int main() { /*我的第一个 C 程序*/ printf("Hello World! \n"); return 0; } ``` | 代码 | 解释 | | :---------------: | :----------------------------------------------------------: | | #include | 预处理指令,告诉 C 编译器在实际编译之前要包含 stdio.h 文件。 | | int main() | 程序的主函数,程序从这里开始运行 | | /*…*/ | “…”里的内容会被编译器忽略,这里放置程序的注释内容。它们被称为程序的注释 | | printf(…) | C语言 中一个可用的函数,会在屏幕上显示消息”…(括号内的内容)” | | return 0; | 终止main()函数,并返回值0. | ### 编译 & 执行 C 程序 不同编译器操作方式不同,此处只提供其中一种[关于vscode如何运行C代码 ](https://watchdogs-x.github.io/2022/12/22/vscode_install/) ## C 基本语法 ### C 的令牌(Token) C 程序由各种令牌组成,令牌可以是关键字、标识符、常量、字符串值,或者是一个符号。例如,下面的 hello world 语句就包括五个令牌: ```c printf("hello world! \n"); ``` 这五个令牌分别为: - printf - ( - “Hello, World! \n” - ) - ; #### 分号 ‘;’ 在 C 程序中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。 ```c printf("Hello, World! \n"); //这是第一条语句 return 0; //这是第二条语句 ``` #### 注释 C 语言有两种注释方式: - // 单行注释 以 // 开始的单行注释,这种注释可以单独占一行。 - /*…*/ 这种格式的注释可以单行或多行。您不能在注释内嵌套注释,注释也不能出现在字符串或字符值中 ```c // 单行注释 /* 单行注释 */ /* 多行注释 多行注释 多行注释 多行注释 */ ``` #### 标识符 - C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。 - C 标识符内不允许出现标点字符,比如 @、$ 和 %。C 是区分大小写的编程语言。因此,在 C 中,Manpower 和 manpower 是两个不同的标识符。 几个有效的标识符: note simple mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal endnote #### 关键词 hideToggle 点我查看 | 关键词 | 说明 | | ------------------------ | ------------------------------------------------------------ | | auto | 声明自动变量 | | break | 跳出当前循环 | | case | 开关语句分支 | | char | 声明字符型变量或函数返回值类型 | | const | 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变 | | continue | 结束当前循环,开始下一轮循环 | | default | 开关语句中的”其它”分支 | | do 循环语句的循环体 | | | double | 声明双精度浮点型变量或函数返回值类型 | | else | 条件语句否定分支(与 if 连用) | | enum | 声明枚举类型 | | extern | 声明变量或函数是在其它文件或本文件的其他位置定义 | | float | 声明浮点型变量或函数返回值类型 | | for | 一种循环语句 | | goto | 无条件跳转语句 | | if | 条件语句 | | int | 声明整型变量或函数 | | long | 声明长整型变量或函数返回值类型 | | register | 声明寄存器变量 | | return | 子程序返回语句(可以带参数,也可不带参数) | | short | 声明短整型变量或函数 | | signed | 声明有符号类型变量或函数 | | sizeof | 计算数据类型或变量长度(即所占字节数) | | static | 声明静态变量 | | struct | 声明结构体类型 | | switch | 用于开关语句 | | unsigned | 声明无符号类型变量或函数 | | union | 声明共用体类型 | | void | 声明函数无返回值或无参数,声明无类型指针 | | volatile | 说明变量在程序执行中可被隐含地改变 | | while 循环语句的循环条件 | | **C99新增关键词** | _Bool | _Complex | _Imaginary | inline | restrict | | ----- | -------- | ---------- | ------ | -------- | | | | | | | **C11 新增关键字** | _Alignas | _Alignof | _Atomic | _Generic | _Noreturn | | ------------------ | ----------------- | ------- | -------- | --------- | | **_Static_assert** | **_Thread_local** | | | | endhideToggle #### C 中的空格 只包含空格的行,被称为空白行,可能带有注释,C 编译器会完全忽略它。 在 C 中,空格用于描述空白符、制表符、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中: ``` int age; ``` 在这里,int 和 age 之间必须至少有一个空格字符(通常是一个空白符),这样编译器才能够区分它们。另一方面,在下面的语句中: ``` fruit = apples + oranges; // 获取水果的总数 ``` fruit 和 =,或者 = 和 apples 之间的空格字符不是必需的,但是为了增强可读性,您可以根据需要适当增加一些空格。 ## C 数据类型 在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。 C 中的类型可分为以下几种: | 序号 | 类型与描述 | | --------------- | ------------------------------------------------------------ | | **基本类型:** | 它们是算术类型,包括两种类型:整数类型和浮点类型。 | | **枚举类型:** | 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。 | | **void 类型:** | 类型说明符 *void* 表明没有可用的值。 | | **派生类型:** | 它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。 | ### 整数类型 下表列出了关于标准整数类型的存储大小和值范围的细节: | 类型 | 存储大小 | 值范围 | | -------------- | ----------- | ---------------------------------------------------- | | char | 1 字节 | -128 到 127 或 0 到 255 | | unsigned char | 1 字节 | 0 到 255 | | signed char | 1 字节 | -128 到 127 | | int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 | | unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 | | short | 2 字节 | -32,768 到 32,767 | | unsigned short | 2 字节 | 0 到 65,535 | | long | 4 字节 | -2,147,483,648 到 2,147,483,647 | | unsigned long | 4 字节 | 0 到 4,294,967,295 | note modern 为了得到某个类型或某个变量在特定平台上的准确大小,您可以使用 **sizeof** 运算符。表达式 *sizeof(type)* 得到对象或类型的存储字节大小。下面的实例演示了获取 int 类型的大小: ```c #include #include int main() { printf("int 存储大小 : %lu \n", sizeof(int)); return 0; } ``` **%lu** 为 32 位无符号整数,详细说明查看 [C 库函数 - printf() ](https://www.runoob.com/cprogramming/c-function-printf.html)。 endnote ### 浮点类型 下表列出了关于标准浮点类型的存储大小、值范围和精度的细节: | 类型 | 存储大小 | 值范围 | 精度 | | ----------- | -------- | ---------------------- | ----------- | | float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 | | double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 | | long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 | 头文件 float.h 定义了宏,在程序中可以使用这些值和其他有关实数二进制表示的细节。下面的实例将输出浮点类型占用的存储空间以及它的范围值: ### 实例 ```c #include #include int main() { printf("float 存储最大字节数 : %lu \n", sizeof(float)); printf("float 最小值: %E\n", FLT_MIN ); printf("float 最大值: %E\n", FLT_MAX ); printf("精度值: %d\n", FLT_DIG ); return 0; } ``` **%E** 为以指数形式输出单、双精度实数,详细说明查看 [C 库函数 - printf() ](https://www.runoob.com/cprogramming/c-function-printf.html)。 当您在 Linux 上编译并执行上面的程序时,它会产生下列结果: ``` float 存储最大字节数 : 4 float 最小值: 1.175494E-38 float 最大值: 3.402823E+38 精度值: 6 ``` ### void 类型 void 类型指定没有可用的值。它通常用于以下三种情况下: | 类型 | 描述 | | ----------------- | ------------------------------------------------------------ | | **函数返回为空** | C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 **void exit (int status);** | | **函数参数为空** | C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 **int rand(void);** | | **指针指向 void** | 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 **void \*malloc( size_t size );** 返回指向 void 的指针,可以转换为任何数据类型。 | ## C 变量 变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。 变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为 C 是大小写敏感的。基于前一章讲解的基本类型,有以下几种基本的变量类型: | 类型 | 描述 | | ------ | ------------------------------------------------------------ | | char | 通常是一个字节(八位), 这是一个整数类型。 | | int | 整型,4 个字节,取值范围 -2147483648 到 2147483647。 | | float | 单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。![img](https://www.runoob.com/wp-content/uploads/2014/09/v2-749cc641eb4d5dafd085e8c23f8826aa_hd.png) | | double | 双精度浮点值。双精度是1位符号,11位指数,52位小数。![img](https://www.runoob.com/wp-content/uploads/2014/09/v2-48240f0e1e0dd33ec89100cbe2d30707_hd.png) | | void | 表示类型的缺失。 | C 语言也允许定义各种其他类型的变量,比如枚举、指针、数组、结构、共用体等等. #### C 中的变量定义 变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表,如下所示: ``` type variable_list; ``` 在这里,**type** 必须是一个有效的 C 数据类型,可以是 char、w_char、int、float、double 或任何用户自定义的对象,**variable_list** 可以由一个或多个标识符名称组成,多个标识符之间用逗号分隔。下面列出几个有效的声明: ```c int i, j, k; char c, ch; float f, salary; double d; ``` 行 **int i, j, k;** 声明并定义了变量 i、j 和 k,这指示编译器创建类型为 int 的名为 i、j、k 的变量。 变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号,后跟一个常量表达式组成,如下所示: ``` type variable_name = value; ``` 下面列举几个实例: ```c extern int d = 3, f = 5; // d 和 f 的声明与初始化 int d = 3, f = 5; // 定义并初始化 d 和 f byte z = 22; // 定义并初始化 z char x = 'x'; // 变量 x 的值为 'x' ``` 不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的。 ### C 中的变量声明 变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。 变量的声明有两种情况: - 1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。 - 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。 - 除非有extern关键字,否则都是变量的定义。 ```c extern int i; //声明,不是定义 int i; //声明,也是定义 #include // 函数外定义变量 x 和 y int x; int y; int addtwonum() { // 函数内声明变量 x 和 y 为外部变量 extern int x; extern int y; // 给外部变量(全局变量)x 和 y 赋值 x = 1; y = 2; return x+y; } int main() { int result; // 调用函数 addtwonum result = addtwonum(); printf("result 为: %d",result); return 0; } ``` 当上面的代码被编译和执行时,它会产生下列结果: ``` result 为: 3 ``` 如果需要在一个源文件中引用另外一个源文件中定义的变量,我们只需在引用的文件中将变量加上 extern 关键字的声明即可。 ## addtwonum.c 文件代码: ```c #include /*外部变量声明*/ extern int x ; extern int y ; int addtwonum() { return x+y; } ``` ## test.c 文件代码: ```c #include /*定义两个全局变量*/ int x=1; int y=2; int addtwonum(); int main(void) { int result; result = addtwonum(); printf("result 为: %d\n",result); return 0; } ``` 当上面的代码被编译和执行时,它会产生下列结果: ``` result 为: 3 ``` ### C 中的左值(Lvalues)和右值(Rvalues) C 中有两种类型的表达式: 1. **左值(lvalue):**指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。 2. **右值(rvalue):**术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。 变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。下面是一个有效的语句: ```c int g = 20; ``` 但是下面这个就不是一个有效的语句,会生成编译时错误: ``` 10 = 20; ``` ## C 常量 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做**字面量**。 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。 **常量**就像是常规的变量,只不过常量的值在定义后不能进行修改。 ### 整数常量 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。 整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。 下面列举几个整数常量的实例: ``` 212 /* 合法的 */ 215u /* 合法的 */ 0xFeeL /* 合法的 */ 078 /* 非法的:8 不是八进制的数字 */ 032UU /* 非法的:不能重复后缀 */ ``` 以下是各种类型的整数常量的实例: ``` 85 /* 十进制 */ 0213 /* 八进制 */ 0x4b /* 十六进制 */ 30 /* 整数 */ 30u /* 无符号整数 */ 30l /* 长整数 */ 30ul /* 无符号长整数 */ ``` ### 浮点常量 - 浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。 - 当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。 下面列举几个浮点常量的实例: ``` 3.14159 /* 合法的 */ 314159E-5L /* 合法的 */ 510E /* 非法的:不完整的指数 */ 210f /* 非法的:没有小数或指数 */ .e55 /* 非法的:缺少整数或分数 */ ``` ### 字符常量 - 字符常量是括在单引号中,例如,’x’ 可以存储在 **char** 类型的简单变量中。 - 字符常量可以是一个普通的字符(例如 ‘x’)、一个转义序列(例如 ‘\t’),或一个通用的字符(例如 ‘\u02C0’)。 - 在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。 更多的转义序列码 | 转义序列 | 含义 | | ---------- | -------------------------- | | \ | \ 字符 | | ' | ‘ 字符 | | " | “ 字符 | | ? | ? 字符 | | \a | 警报铃声 | | \b | 退格键 | | \f | 换页符 | | \n | 换行符 | | \r | 回车 | | \t | 水平制表符 | | \v | 垂直制表符 | | \ooo | 一到三位的八进制数 | | \xhh . . . | 一个或多个数字的十六进制数 | **例:** ```c \#include int main() { printf("Hello\tWorld\n\n"); return 0; } ``` 当上面的代码被编译和执行时,它会产生下列结果: ``` Hello World ``` ### 字符串常量 字符串字面值或常量是括在双引号 “” 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。 您可以使用空格做分隔符,把一个很长的字符串常量进行分行。 下面的实例显示了一些字符串常量。下面这三种形式所显示的字符串是相同的。 ``` "hello, dear" "hello, \ dear" "hello, " "d" "ear" ``` ### 定义常量 在 C 中,有两种简单的定义常量的方式: 1. 使用 **#define** 预处理器。 2. 使用 **const** 关键字。 #### #define 预处理器 下面是使用 #define 预处理器定义常量的形式: ``` #define identifier value ``` **例:** ```c #include #define LENGTH 10 #define WIDTH 5 #define NEWLINE '\n' int main() { int area; area = LENGTH * WIDTH; printf("value of area : %d", area); printf("%c", NEWLINE); return 0; } ``` 当上面的代码被编译和执行时,它会产生下列结果: ``` value of area : 50 ``` ### const 关键字 您可以使用 **const** 前缀声明指定类型的常量,如下所示: ```c const type variable = value; ``` ![img](https://www.runoob.com/wp-content/uploads/2014/09/c-const-2021-01-15.png) const 声明常量要在一个语句内完成: ![img](https://www.runoob.com/wp-content/uploads/2014/09/c-const-2021-01-15-2.png) **例:** ```c #include int main() { const int LENGTH = 10; const int WIDTH = 5; const char NEWLINE = '\n'; int area; area = LENGTH * WIDTH; printf("value of area : %d", area); printf("%c", NEWLINE); return 0; } ``` 当上面的代码被编译和执行时,它会产生下列结果: ``` value of area : 50 ``` 把常量定义为大写字母形式,是一个很好的编程习惯。 ## C 存储类 hideToggle 点我查看 存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C 程序中可用的存储类: - auto - register - static - extern ### auto 存储类 **auto** 存储类是所有局部变量默认的存储类。 ```c { int mount; auto int month; } ``` 上面的实例定义了两个带有相同存储类的变量,auto 只能用在函数内,即 auto 只能修饰局部变量。 ### register 存储类 **register** 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。 ```c { register int miles; } ``` 寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。 ### static 存储类 **static** 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。 static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。 全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。 以下实例演示了 static 修饰全局变量和局部变量的应用: ```c #include /* 函数声明 */ void func1(void); static int count=10; /* 全局变量 - static 是默认的 */ int main() { while (count--) { func1(); } return 0; } void func1(void) { /* 'thingy' 是 'func1' 的局部变量 - 只初始化一次 * 每次调用函数 'func1' 'thingy' 值不会被重置。 */ static int thingy=5; thingy++; printf(" thingy 为 %d , count 为 %d\n", thingy, count); } ``` 实例中 count 作为全局变量可以在函数内使用,thingy 使用 static 修饰后,不会在每次调用时重置。 可能您现在还无法理解这个实例,因为我已经使用了函数和全局变量,这两个概念目前为止还没进行讲解。即使您现在不能完全理解,也没有关系,后续的章节我们会详细讲解。当上面的代码被编译和执行时,它会产生下列结果: ``` thingy 为 6 , count 为 9 thingy 为 7 , count 为 8 thingy 为 8 , count 为 7 thingy 为 9 , count 为 6 thingy 为 10 , count 为 5 thingy 为 11 , count 为 4 thingy 为 12 , count 为 3 thingy 为 13 , count 为 2 thingy 为 14 , count 为 1 thingy 为 15 , count 为 0 ``` ### extern 存储类 **extern** 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 **extern** 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。 当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 *extern* 来得到已定义的变量或函数的引用。可以这么理解,*extern* 是用来在另一个文件中声明一个全局变量或函数。 extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示: **第一个文件:main.c** ```c #include int count ; extern void write_extern(); int main() { count = 5; write_extern(); } ``` **第二个文件:support.c** ```c #include extern int count; void write_extern(void) { printf("count is %d\n", count); } ``` 在这里,第二个文件中的 *extern* 关键字用于声明已经在第一个文件 main.c 中定义的 *count*。现在 ,编译这两个文件,如下所示: ```bash $ gcc main.c support.c ``` 这会产生 **a.out** 可执行程序,当程序被执行时,它会产生下列结果: ``` count is 5 ``` endhideToggle ## C 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符,并提供了以下类型的运算符: - 算术运算符 - 关系运算符 - 逻辑运算符 - 位运算符 - 赋值运算符 - 杂项运算符 本章将逐一介绍算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和其他运算符。 ### 算术运算符 下表显示了 C 语言支持的所有算术运算符。假设变量 **A** 的值为 10,变量 **B** 的值为 20,则: | 运算符 | 描述 | 实例 | | ------ | -------------------------------- | ---------------- | | + | 把两个操作数相加 | A + B 将得到 30 | | - | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 | | * | 把两个操作数相乘 | A * B 将得到 200 | | / | 分子除以分母 | B / A 将得到 2 | | % | 取模运算符,整除后的余数 | B % A 将得到 0 | | ++ | 自增运算符,整数值增加 1 | A++ 将得到 11 | | – | 自减运算符,整数值减少 1 | A– 将得到 9 | ### 关系运算符 下表显示了 C 语言支持的所有关系运算符。假设变量 **A** 的值为 10,变量 **B** 的值为 20,则: | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | --------------- | | == | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 | | != | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 | | > | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 | | < | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 | | >= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 | | <= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 | ### 逻辑运算符 下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 **A** 的值为 1,变量 **B** 的值为 0,则: | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ----------------- | | && | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 | | \|\| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A \|\| B) 为真。 | | ! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 | ### 位运算符 位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示: | p | q | p & q | p \| q | p ^ q | | ---- | ---- | ----- | ------ | ----- | | 0 | 0 | 0 | 0 | 0 | | 0 | 1 | 0 | 1 | 1 | | 1 | 1 | 1 | 1 | 0 | | 1 | 0 | 0 | 1 | 1 | 假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示: A = 0011 1100 B = 0000 1101 -—————- A&B = 0000 1100 A|B = 0011 1101 A^B = 0011 0001 ~A = 1100 0011 下表显示了 C 语言支持的位运算符。假设变量 **A** 的值为 60,变量 **B** 的值为 13,则: | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | & | 按位与操作,按二进制位进行”与”运算。运算规则:`0&0=0; 0&1=0; 1&0=0; 1&1=1;` | (A & B) 将得到 12,即为 0000 1100 | | \| | 按位或运算符,按二进制位进行”或”运算。运算规则:`0 | 0=0; 0 | | ^ | 异或运算符,按二进制位进行”异或”运算。运算规则:`0^0=0; 0^1=1; 1^0=1; 1^1=0;` | (A ^ B) 将得到 49,即为 0011 0001 | | ~ | 取反运算符,按二进制位进行”取反”运算。运算规则:`~1=-2; ~0=-1;` | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 | | << | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 | | >> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 | ## 赋值运算符 下表列出了 C 语言支持的赋值运算符: | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ------------------------------- | | = | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C | | += | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A | | -= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A | | *= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A | | /= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A | | %= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A | | <<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 | | >>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 | | &= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 | | ^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 | | \|= | 按位或且赋值运算符 | C \|= 2 等同于 C = C \| 2 | ### 杂项运算符 ↦ sizeof & 三元 下表列出了 C 语言支持的其他一些重要的运算符,包括 **sizeof** 和 **? :**。 | 运算符 | 描述 | 实例 | | -------- | ---------------- | ------------------------------------ | | sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 | | & | 返回变量的地址。 | &a; 将给出变量的实际地址。 | | * | 指向一个变量。 | *a; 将指向一个变量。 | | ? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y | ## C 中的运算符优先级 运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符具有比加减运算符更高的优先级。 例如 x = 7 + 3 * 2,在这里,x 被赋值为 13,而不是 20,因为运算符 * 具有比 + 更高的优先级,所以首先计算乘法 3*2,然后再加上 7。 下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。 | 类别 | 运算符 | 结合性 | | ---------- | --------------------------------- | -------- | | 后缀 | () [] -> . ++ - - | 从左到右 | | 一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 | | 乘除 | * / % | 从左到右 | | 加减 | + - | 从左到右 | | 移位 | << >> | 从左到右 | | 关系 | < <= > >= | 从左到右 | | 相等 | == != | 从左到右 | | 位与 AND | & | 从左到右 | | 位异或 XOR | ^ | 从左到右 | | 位或 OR | \| | 从左到右 | | 逻辑与 AND | && | 从左到右 | | 逻辑或 OR | \|\| | 从左到右 | | 条件 | ?: | 从右到左 | | 赋值 | = += -= *= /= %=>>= <<= &= ^= \|= | 从右到左 | | 逗号 | , | 从左到右 | ## C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。 C 语言把任何**非零**和**非空**的值假定为 **true**,把**零**或 **null** 假定为 **false**。 下面是大多数编程语言中典型的判断结构的一般形式: ![C 中的判断语句](https://static.runoob.com/wp-content/uploads/c/C-decision-20200923-1.svg) ### 判断语句 C 语言提供了以下类型的判断语句。点击链接查看每个语句的细节。 | 语句 | 描述 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | [if 语句 ](https://www.runoob.com/cprogramming/c-if.html) | 一个 **if 语句** 由一个布尔表达式后跟一个或多个语句组成。 | | [if…else 语句 ](https://www.runoob.com/cprogramming/c-if-else.html) | 一个 **if 语句** 后可跟一个可选的 **else 语句**,else 语句在布尔表达式为假时执行。 | | [嵌套 if 语句 ](https://www.runoob.com/cprogramming/c-nested-if.html) | 您可以在一个 **if** 或 **else if** 语句内使用另一个 **if** 或 **else if** 语句。 | | [switch 语句 ](https://www.runoob.com/cprogramming/c-switch.html) | 一个 **switch** 语句允许测试一个变量等于多个值时的情况。 | | [嵌套 switch 语句 ](https://www.runoob.com/cprogramming/c-nested-switch.html) | 您可以在一个 **switch** 语句内使用另一个 **switch** 语句。 | ### ? : 运算符(三元运算符) 我们已经在前面的章节中讲解了 **条件运算符 ? :**,可以用来替代 **if…else** 语句。它的一般形式如下: ``` Exp1 ? Exp2 : Exp3; ``` 其中,Exp1、Exp2 和 Exp3 是表达式。请注意,冒号的使用和位置。 ? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个表达式的值。 ![img](https://www.runoob.com/wp-content/uploads/2014/09/Conditional-Statement-in-C-Programming-Lanuage-Ternary-Operator.png) **例:** 输入一个数字判断他是奇数还是偶数 ```c #include int main() { int num; printf("输入一个数字 : "); scanf("%d",&num); (num%2==0)?printf("偶数"):printf("奇数"); } ``` ## C 循环 有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的流程图: ![循环结构](https://www.runoob.com/wp-content/uploads/2015/12/loop.png) ### 循环类型 C 语言提供了以下几种循环类型。点击链接查看每个类型的细节。 | 循环类型 | 描述 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | [while 循环 ](https://www.runoob.com/cprogramming/c-while-loop.html) | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 | | [for 循环 ](https://www.runoob.com/cprogramming/c-for-loop.html) | 多次执行一个语句序列,简化管理循环变量的代码。 | | [do…while 循环 ](https://www.runoob.com/cprogramming/c-do-while-loop.html) | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 | | [嵌套循环 ](https://www.runoob.com/cprogramming/c-nested-loops.html) | 您可以在 while、for 或 do..while 循环内使用一个或多个循环。 | ### 循环控制语句 循环控制语句改变你代码的执行顺序。通过它可以实现代码的跳转。 | 控制语句 | 描述 | | ------------- | ------------------------------------------------------------ | | break 语句 | 终止**循环**或 **switch** 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。 | | continue 语句 | 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。 | | goto 语句 | 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 | ### 无限循环 如果条件永远不为假,则循环将变成无限循环。**for** 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,可以将某些条件表达式留空来构成一个无限循环。 ```c #include int main () { for( ; ; ) { printf("该循环会永远执行下去!\n"); } return 0; } ```