可恶的C语言——不知道是哪啥
联合
一、类型定义
1.自定义数据类型(typedef)
C语言提供了一个叫typedef的功能来声明一个已有的数据类型的新名字。
例如: typedef int Length;
使得Length成为int类型的别名。这样Length这个名字就可以代替int出现在变量定义和参数声明的地方了:
Length a,b,len;
Length numbers[10];
typedef int Length;//Length就等价于int类型
typedef *char[10]String;//Strings是10个字符串的数组类型
typedef struct node{
int date;
struct node *next;
}aNode;
//或
typedef struct node aNode;//这样用aNode就可以代替struct dode
2.联合(union)
union的用处
储存
- 所有成员共享一个空间
- 同一时间只有一个成员是有效的
- union的大小是其最大的成员
初始化
- 对第一个成员做初始化
#include <stdio.h>
typedef union{
int i;
char ch[sizeof(int)];
}CHI;
int main(int argc,char const *argv[])
{
CHI chi;
int i;
chi.i=1234;
for(i=0;i<sizeof(int);i++)
{
printf("%02hhX",chi.ch[i]);
}
printf("\n");
return 0;
}
全局变量
一、全局变量
- 定义在函数外面的变量是全局变量
- 全局变量具有全局的生存期和作用域
- 他们与任何函数都无关
- 在任何函数内都可以使用它们
1.全局变量初始化
- 没有做初始化的全局变量会得到0值
- 指针会得到NULL值
- 只能用编译时刻已知的值来初始化全局变量
- 它们的初始化发生在main函数之前
2.被隐藏的全局变量
如果函数内部存在与全局变量同名的变量,则全局变量被隐藏。
二、静态本地变量
- 在本地变量定义时加上static修饰符就成为静态本地变量
- 当函数离开时,静态本地变量会继续存在并保存其值
- 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
- 静态本地变量实际上是特殊的全局变量
- 它们位于相同的内存区域
- 静态本地变量具有全局的生存期,函数内的局部作用域
- static在这里的意思是局部作用域(本地可访问)
三、补充
1.*返回指针的函数
- 返回本地变量的地址是危险的
- 返回全局变量或静态本地变量的地址是安全的
- 返回在函数内malloc的内存是安全的,但是容易造成问题
- 最好的做法是返回传入的指针
- 不要使用全局变量来在函数间传递参数和结果
- 尽量避免使用全局变量
- 使用全局变量和静态本地变量的函数是线程不安全的
编译预处理和宏
一、编译预处理指令
- #开头的是编译预处理指令
- 它们不是C语言的成分,但是C语言程序离不开它们
- #define用来定义一个宏
1.#define
#define<名字><值>
- 注意没有结尾的分号,因为不是C语句
- 名字必须为一个单词,值可以是各种东西
- 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字转换成值
- 完全的文本替换
- gcc–save–temps
2.宏
- 如果一个宏的值中有其他的宏的名字,也是会被替换的
- 如果一个宏的值超过一行,最后一行之前的行末需要加\
- 宏的值后面出现的注释不会被当作宏的值的一部分
3.没有值的宏
#define_DEBUG
这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过
4.预定义的宏
- _ LINE _
- _ FINE _
- _ DATE _
- _ TIME _
- _ STDC _
#include <stdio.h>
int main(int argc,char const *argv[])
{
printf("%s:%d\n",_FINE_,_LINE_);
printf("%s,%s\n",_DATE_,_TIME_);
retrun 0;
}
二、像函数的宏
*#define cube(x)((x)*(x) (x))
宏可以带参数
1.带参数的宏的原则
- 一切都要括号
- 整个值要括号
- 参数出现的每个地方都要括号
2.带参数的宏
-
可以带多个参数
如:
#define MIN(a,b) ((a)>(b)?(b):(a))
-
也可以组合(嵌套)使用其他宏
大程序结构
一、多个代码源文件
在Dev C++中新建一个项目,然后把几个源代码文件加进去。对于项目,Dev C++的编译会把一个项目中所有的源代码文件都编译后链接起来。有的IDE有分开的编译和构建两个按钮,前者是对单个源代码文件编译,后者是对整个项目做链接。
编译单元
一个.c文件是一个编译单元
编译器每次编译只处理一个编译单元
二、头文件
把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型。在使用和定义这个函数的地方都应该#include这个头文件,一般的做法是任何.c都有对应的同名.h,把所有对外公开的函数的原型和全局变量的声明都放进去。
1.#include
- #include是一个编译预处理指令,和宏一样,在编译之前就处理了
- 它把那个文件的全部文本内容原封不动地插入到它所在的地方
- 所以也不是一定要在.c文件的最前面#include
- #include有两种形式来指出要插入的文件
- ""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找。
- <>让编译器只在指定的目录去找
- 编译器自己知道自己的标准库的头文件在哪里
- 环境变量和编译器命令行参数也可以指定寻找头文件的目录
- #include不是用来引入库的
- stdio.h里只有printf的原型,printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中
- 现在的C语言编译器默认会引入所以的标准库
- #include <stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值是正确的类型
2.不对外公开的函数
- 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
- 在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量
三、声明
1.变量的声明
- int i;是变量的定义
- extern int i;是变量的声明
- 声明是不产生代码的东西,例如:函数原型,变量声明,结构声明,宏声明,枚举声明,类型声明,inline函数
- 定义是产生代码的东西
- 只有声明可以被放在头文件中,否则会造成一个项目中多个编译单元里有重名的实体
- *某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在
文件
一、格式化输入和输出
1.printf %[flags] [width] [.prec] [hlL] type
Flag | 含义 |
---|---|
- | 左对齐 |
+ | 在前面放+或- |
(space) | 正数留空 |
0 | 0填充 |
width或prec | 含义 |
---|---|
number | 最小字符数 |
* | 下一个参数是字符数 |
.number | 小数点后的位数 |
.* | 下一个参数是小数点后的位数 |
类型修饰 | 含义 |
---|---|
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
type | 用于 | type | 用于 |
---|---|---|---|
i或d | int | g | float |
u | unsigned int | G | float |
o | 八进制 | a或A | 十六进制浮点 |
x | 十六进制 | c | char |
X | 字母大写的十六进制 | s | 字符串 |
f或F | float,6 | p | 指针 |
e或E | 指数 | n | 读入或写入的个数 |
2.scanf:%[flag]type
flag | 含义 | flag | 含义 |
---|---|---|---|
* | 跳过 | l | long,double |
数字 | 最大字符数 | ll | long long |
hh | char | L | long double |
h | short |
type | 用于 | type | 用于 |
---|---|---|---|
d | int | s | 字符串(单词) |
i | 整数,可能为十六进制或八进制 | […] | 所允许的字符 |
u | unsigned int | p | 指针 |
o | 八进制 | ||
x | 十六进制 | ||
a,e,f,g | float | ||
c | char |
二、文件输入输出
- 用<来指定输入文件中
- 用>来指定把输出写到另一个文件中去
1.FILE
- FILE*fopen(const char * restrict path,const char *restrict mode);
- int fclose(FILE *stream);
- fscanf(FILE*,…)
- fprintf(FILE*,…)
2.fopen
r | 打开只读 |
---|---|
r+ | 打开读写,从文件头开始 |
w | 打开只写,如果不存在则新建,如果存在则清空 |
w+ | 打开读写,如果不存在则新建,如果存在则清空 |
a | 打开追加,如果不存在则新建,如果存在则从文件尾开始 |
…x | 只新建,如果文件已存在则不能打开 |
三、二进制文件
- 所有的文件最终都是二进制的
- 文本文件无非是用最简单的方式可以读写的文件
- more、tail
- cat
- vi
- 而二进制文件是需要专门的程序来读写的文件
- 文本文件的输入输出是格式化,可能经过转码
1.文本与二进制
-
Unix喜欢用文本文件来做数据存储和程序配置
-
交互式终端的出现使得人们喜欢用文本和计算机“talk”
-
Unix的shell提供了一些读写文本的小程序
-
Windows喜欢用二进制文件
-
DOS是草根文化,并不继承和熟悉Unix文化
-
PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层
2.程序需要文件的原因
- 配置
- Unix用文本,WIndows用注册表
- 数据
- 稍微有点量的数据都放数据库了
- 媒体
- 这个只能是二进制的
- 现实是程序通过第三方库来读写文件,很少直接读写二进制文件