首页 C语言 变量与数据类型
文章
取消

C语言 变量与数据类型

此篇博客仍在整理中,内容质量及排版比较一般,还请见谅

全局变量

  • 全局变量: 定义在所有函数之外的变量,属于 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 值

NULLstdio.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 shortunsigned intunsigned 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 *,只有一层意义,即表示这是一个指针,不知道指向的数据类型

本文由作者按照 CC BY 4.0 进行授权

C语言 输入与输出

C语言 程序的内存布局