全局变量
全局变量
: 定义在所有函数之外的变量,属于 static 存储类,默认初始值为 0局部变量
: 函数的形参,函数体内定义的变量,代码块内定义的变量,属于 auto 存储类,需手动初始化,否则为垃圾值
注意:函数体外只能进行全局变量的初始化,不能进行赋值运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i; // 正确,i = 0
int i; // 正确,i = 0
i = 10; // 错误,不能进行赋值
int i = 10; // 正确,赋初值(初始化)
int i = 10; // 正确,赋初值(初始化)
int j = i; // 错误,不能取变量值
int i = 10 * 10; // 正确,编译器会自动优化为 int i = 100;
int i = 10; // 正确,赋初值(初始化)
int j = i + 10; // 错误,不能取变量值
定义常量
定义常量有两种方式
#define
宏替换const
关键字
1
2
3
4
5
6
7
#include <stdio.h>
#define PI 3.14
int main(){
const int I = 1;
printf("PI: %.2f, I: %d\n", PI, I);
}
基本类型
描述 | 数据类型 |
---|---|
字符 | char |
短整数 | short |
整数 | int |
长整数 | long |
单精度浮点数 | float |
双精度浮点数 | double |
无类型 | void |
每种数据类型占用的内存大小(这是在 64 位的 Linux 下占用的大小,其它系统会有所不同):
1
2
3
4
5
6
#include <stdio.h>
int main(){
printf("char: %d, short: %d, int: %d, long: %d, float: %d, double: %d, void: %d\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(float), sizeof(double), sizeof(void));
return 0;
}
1
2
3
4
$ gcc type.c
$ ./a.out
char: 1, short: 2, int: 4, long: 8, float: 4, double: 8, void: 1
NULL 值
NULL
在stdio.h
实际上是#define NULL ((void *) 0)
,而在 C++ 中则直接被定义为了 0,#define NULL 0
。
浮点数
1
2
3
4
float a = 1.0f;
double b = 1.0d;
long double ld = 1.0l; //长浮点数
// 如果不指定后缀f,则默认为double型
无符号数
char
short
int
long
默认都是有符号的,首位用来存储符号位。
如果不需要使用负数,则可以使用无符号数,只要在前面加上unsigned
即可。
如unsigned char
unsigned short
、unsigned int
、unsigned long
,其中unsigned int
可以简写为unsigned
。
运算符
算术运算符:+
-
*
/
%
取余 ++
自增 --
自减
关系运算符:==
!=
>
>=
<
<=
,结果为布尔值true/false
逻辑运算符:&&
与 ||
或 !
非
位运算符:假设 A = 60,B = 13,则:
1
2
3
4
5
6
7
8
A = 0011 1100
B = 0000 1101
A & B = 0000 1100,按位与
A | B = 0011 1101,按位或
A ^ B = 0011 0001,按位异或
~A = 1100 0011,按位非
A << 2 = 1111 0000,左移
A >> 2 = 0000 1111,右移(有符号)
赋值运算符:=
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
其它运算符:
sizeof(int)
返回变量长度(字节)&a
取变量a的内存地址*p
对指针p解引用?:
条件表达式(三目),如int a = (b > 10) ? 100 : 0
,如果 b 大于 10,则 a 为 100,否则为 0
运算符优先级(从高到低)
https://en.cppreference.com/w/c/language/operator_precedence
后缀、一元、乘除、加减、移位、关系、相等、位与、位异或、位或、逻辑与、逻辑或、?:
、赋值
自增,自减
- ++在前面叫做
前自增
(例如 ++a);前自增先进行自增操作,再进行其他操作; - ++在后面叫做
后自增
(例如 a++);后自增先进行其他操作,再进行自增操作; - 自减同理。
其实这样描述不好理解,我把前自增和后自增分别比喻为两个函数(自减同理):
1
2
3
4
5
6
7
8
9
10
int incrementAndGet(int *value) {
*value += 1;
return *value;
}
int getAndIncrement(int *value) {
int oldValue = *value;
*value += 1;
return oldValue;
}
例如:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main(){
int a=10, b=20, c=30, d=40, aa, bb, cc, dd;
aa = a++;
bb = ++b;
cc = c--;
dd = --d;
printf("a=%d aa=%d\nb=%d bb=%d\nc=%d cc=%d\nd=%d dd=%d\n", a, aa, b, bb, c, cc, d, dd);
return 0;
}
1
2
3
4
5
6
7
$ gcc test.c
$ ./a.out
a=11 aa=10
b=21 bb=21
c=29 cc=30
d=39 dd=39
存储类
存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。
auto
所有局部变量的默认存储类,只能修饰局部变量(不再建议使用)
1
2
3
4
{
int i = 10;
auto char c = 'a';
}
static
static 有两个作用:
- 指示编译器在程序的生命周期内保持
局部变量
的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。 - static 也可以应用于
全局变量
和函数
,全局变量
属于 static 存储类,当 static 修饰全局变量
或函数
时,会将其作用域限制在声明它的文件内(默认的作用域是工程内的所有文件)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void print(){
static int i=1;
printf("i = %d\n", i++);
}
int main(){
print();
print();
print();
print();
print();
return 0;
}
编译并运行
1
2
3
4
5
6
7
8
$ gcc a.c
$ ./a.out
i = 1
i = 2
i = 3
i = 4
i = 5
register
用于定义存储在寄存器中而不是 RAM 中的局部变量,这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用&
运算符(因为它没有内存位置)。用了 register 修饰符,并不意味着该变量就存储在寄存器中,这取决于硬件和实现的限制。
1
2
3
{
register int i = 1;
}
extern
放在函数或者变量前,以标示函数或者变量的定义(实现部分)在别的文件(也可以在当前文件),用来声明全局变量或函数。
a.c
1
2
3
4
5
6
7
#include <stdio.h>
int main(){
extern int i; // 可简写为 extern i;
printf("i = %d\n", i);
return 0;
}
b.c
1
int i = 10;
1
2
3
4
$ gcc a.c b.c
$ ./a.out
i = 10
函数默认都隐式声明为 extern,所以可以省略 extern
a.c
1
2
3
4
5
6
extern void print(); //可省略extern
int main(){
print();
return 0;
}
b.c
1
2
3
4
5
#include <stdio.h>
void print(){
printf("print\n");
}
1
2
3
4
$ gcc a.c b.c
$ ./a.out
print
注意变量
的声明
、定义
的区别:
定义
: 分配内存空间,如int i
,分配 4 个字节的内存
声明
: 不需要分配内存空间,如extern int i
,这只是告诉编译器,i
这个变量已经在别的文件定义了,不用为其分配内存,直接用就行
函数或者变量的声明都是为了提前使用,如果不需要提前使用,没有提前声明的必要性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 函数的声明
void print();
int max(int m, int n);
int min(int, int);
// 函数的定义
void print(){
printf("hello, world!\n");
}
// 变量的声明
extern int i;
extern i; // 可省略数据类型
// 变量的定义
int i;
// 变量的赋值
i = 10;
// 变量的初始化(定义的同时进行赋初值)
int i = 10;
变量初始值
- static 存储类的变量(全局变量,static 变量),其初始值为 0
- auto 存储类的变量,系统并不会对其进行初始化,它的值是垃圾值,在使用前要进行手动初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
char c;
short s;
int i;
long l;
float f;
double d;
int main() {
static int static_main;
int a1 = 10;
int a2 = 20;
int a = a1 * a2;
int auto_main;
printf("char: %c\nshort: %hd\nint: %d\nlong: %ld\nfloat: %f\ndouble: %lf\nstatic_main: %d\nauto_main: %d\n", c, s, i, l, f, d, static_main, auto_main);
return 0;
}
编译并运行
1
2
3
4
5
6
7
8
9
10
11
$ gcc a.c
$ ./a.out
char:
short: 0
int: 0
long: 0
float: 0.000000
double: 0.000000
static_main: 0
auto_main: -2099247248
数据类型转换
自动类型转换
- 若参与运算的数据类型不同,则先转换成同一类型,然后进行运算。
- 转换按数据长度增加的方向进行,以保证精度不降低。例如 int 型和 long 型运算时,先把 int 转成 long 型后再进行运算。
- 所有的浮点运算都是以双精度进行的,即使仅含 float 单精度量运算的表达式,也要先转换成 double 型,再作运算。
- char 型和 short 型参与运算时,必须先转换成 int 型。
- 在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型将转换为左边变量的类型。如果右边表达式的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度。
强制类型转换
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main() {
int a=10, b=3;
double result;
result = (double) a / b;
printf("(double) a / b = %f\n", result);
result = (double) (a / b);
printf("(double) (a / b) = %f\n", result);
return 0;
}
1
2
3
4
5
$ gcc a.c
$ ./a.out
(double) a / b = 3.333333
(double) (a / b) = 3.000000
注意,(double) 的优先级更高,所以第一个结果是这样算的:先将 a 转换为 double 类型,然后再和 b 相除,这时,b 也自动转换为 double 类型,除出来的结果是 3.333333…
而第二个结果是先将 a 和 b 相除,因为都是 int 类型,所以后面的小数部分直接舍去了,然后再转换为 double 类型,结果就是 3.000000
对数据类型的理解
在 C 语言中,定义一个变量必须指明数据类型,比如:short
短整型、int
整型、long
长整型、float
单精度浮点型、double
双精度浮点型、int *
整型指针、char *
字符指针、void *
裸指针(无具体类型)。
但是,这些所谓的数据类型都是写给编译器看的,在内存中,并不存在所谓的数据类型,比如一个 int 变量,在 64 位环境中占用 4 个字节的内存,这 4 个字节存储的只是这个变量的值(二进制)。
那为什么还要指明数据类型呢?最直接的原因是:为了让编译器知道给定的内存地址存储的是什么数据,数据长度是多少,数据格式什么,如何解析和使用。
另外,对于指针类型,指明数据类型有两层意义:
- 可以从数据类型中知道这是一个指针(实际是一个长整型数值,只不过这个值是一个内存地址)
- 还可以知道指针指向的数据的类型,进而知道对指针解引用时,要读取的数据长度
对于特殊指针void *
,只有一层意义,即表示这是一个指针,不知道指向的数据类型