Skip to main content

数据类型与变量

数据类型

C 语言提供了丰富的数据类型来描述生活中的各种数据:用 整型 表示整数、用 字符型 表示字符、用 浮点型 表示小数。编译器只有知道数据的类型,才知道如何存储与计算。

数据类型总览

数据类型

内置类型

自定义类型

字符型 char

整型 short / int / long / long long

浮点型 float / double / long double

布尔类型 _Bool /

数组 array

结构体 struct

枚举 enum

联合体 union

字符型

  • char
  • [signed] char
  • unsigned char

整型

  • short [int], [signed] short [int], unsigned short [int]
  • int, [signed] int, unsigned int
  • long [int], [signed] long [int], unsigned long [int]
  • long long [int], [signed] long long [int], unsigned long long [int] (C99 引入)

浮点型

  • float, double, long double

布尔类型

C 语言原来并没有为布尔值单独设置一个类型,而是使用整数 0 表示假,非零值表示真。

在C99 中也引入了 布尔类型,是专门表示真假的。

布尔类型的使用得包含头文件 <stdbool.h>

#define bool _Bool
#define false 0
#define true 1

示例:

#include <stdio.h>
#include <stdbool.h>

int main(void) {
_Bool f1 = 1;
bool f2 = true;
if (f1 && f2) printf("i like C\n");
return 0;
}

数据类型长度

  • sizeof 是关键字/操作符,返回类型长度(单位:字节)。
  • 操作数可以是 类型表达式/变量。表达式不会被真实求值,只按其类型计算长度。
  • sizeof 的操作数如果不是类型,是表达式的时候,可以省略掉后边的括号。
  • 返回类型是 size_t(实现相关,常见为 unsigned longunsigned long long)。

示例:

#include <stdio.h>

int main(void) {
int a = 10;
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof a); // 变量名时可省略括号
printf("%zd\n", sizeof(int)); // 类型需括号
printf("%zd\n", sizeof(3 + 3.5));// 表达式按类型推断
return 0;
}

不同类型长度(示例环境 VS2022 x64):

char: 1
_Bool: 1
short: 2
int: 4
long: 4
long long: 8
float: 4
double: 8
long double: 8

注意: sizeof(s = b+1) 中的 s = b+1 不会执行,只按表达式类型求大小。

sizeof 运算符的返回值,C 语言只规定是无符号整数,并没有规定具体的类型,而是留给系统自己去决定,sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int,也有可能是 unsigned long,甚至是 unsigned long long,对应的 printf() 占位符分别是 %u%lu%llu。这样不利于程序的可移植性。

C 语言提供了一个解决方法,创造了一个类型别名 size_t,用来统一表示 sizeof 的返回值类型。对应当前系统的 sizeof 的返回值类型,可能是 unsigned int,也可能是 unsigned long long

signedunsigned

  • 字符型整型 可加 signed/unsigned
  • signed 关键字,表示一个类型带有正负号,包含负值。
  • unsigned 关键字,表示该类型不带有正负号,只能表示零和正整数。
  • int 默认等同于 signed int;但 char 的有符号性由实现决定(char 可能等于 signed charunsigned char)。

整数变量声明为 unsigned 的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。

#define SHRT_MIN (-32768) //有符 号16位 整型的 最小值
#define SHRT_MAX 32767 //有符 号16位 整型的 最大值
#define USHRT_MAX 0xffff //无 符 号16位 整型的 最大值
#define INT_MIN (-2147483647 - 1) //有符 号整型的 最小值
#define INT_MAX 2147483647 //有符 号整型的 最大值

C 语言规定 char 类型默认是否带有正负号,由当前系统决定。这就是说,char 不等同于 signed char,它有可能是 signed char,也有可能是 unsigned char

示例:

signed char c1 = -128; // 取值范围通常为 -128~127
unsigned char c2 = 255; // 取值范围通常为 0~255
unsigned int u = 42; // 只表示非负整数

取值范围

上述的数据类型很多,尤其整数型类型就有 shortintlonglong long 四种,为什么呢?

其实每一种数据类型有自己的取值范围,也就是存储的数值的最大值和最小值的区间,有了丰富的类型,就可以在适当的场景下去选择适合的类型。如果要查看当前系统上不同数据类型的极限值:

  • limits.h 文件中说明了整数类型的取值范围。
  • float.h 这个头文件中说明浮点类型的取值范围。

为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。

  • SCHAR_MIN , SCHAR_MAX : signed char 的最小值和最大值。
  • SHRT_MIN , SHRT_MAX : short 的最小值和最大值。
  • INT_MIN , INT_MAX : int 的最小值和最大值。
  • LONG_MIN , LONG_MAX : long 的最小值和最大值。
  • LLONG_MIN , LLONG_MAX : long long 的最小值和最大值。
  • UCHAR_MAX : unsigned char 的最大值。
  • USHRT_MAX : unsigned short 的最大值。
  • UINT_MAX : unsigned int 的最大值。
  • ULONG_MAX : unsigned long 的最大值。
  • ULLONG_MAX : unsigned long long 的最大值。

变量

C语言中把经常变化的值称为变量,不变的值称为常量。

// data_type name;

int age; //整型变量
char ch; //字符变量
double weight; //浮点型变量

创建

变量在创建的时候就给一个初始值,就叫初始化。

int age = 18;      // 整型变量
char ch = 'w'; // 字符变量
double w = 48.0; // 浮点变量
unsigned height = 100;

变量分类

在大括号外部定义的变量就是全局变量,全局变量的使用范围更广,整个工程中想使用,都是有办法使用的。

在大括号内部定义的变量就是局部变量,局部变量的使用范围是比较局限,只能在自己所在的局部范围内使用的。

#include <stdio.h>

int global = 2023; // 全局变量

int main()
{
int local = 2018; // 局部变量
printf("%d\n", local);
printf("%d\n", global);

return 0;
}

如果局部和全局变量,名字相同呢?

#include <stdio.h>

int n = 1000;
int main()
{
int n = 10;
printf("%d\n", n);
return 0;
}

局部变量和全局变量同名的时候,局部变量优先使用。

作用域与存储

内存

栈区
局部变量/函数参数

堆区
malloc/calloc/realloc/free

静态区
全局变量/静态变量

  • 局部变量:大括号内部定义,通常位于栈区
  • 全局/静态变量:大括号外部定义或 static,通常位于静态区
  • 动态分配:通过 malloc/calloc/realloc/free 使用堆区

同名遮蔽:

#include <stdio.h>
int n = 1000; // 全局
int main(void) {
int n = 10; // 局部,遮蔽全局
printf("%d\n", n); // 输出 10
return 0;
}

算术操作符

C语言中为了方便运算,提供了一系列操作符,+ - * / %,这些操作符都是双目操作符。

int x = 4 + 22;
int y = 61 - 23;
int sq = 5 * 5; // 25
float a = 6 / 4; // 1.0 (整除后再赋值给浮点)
float b = 6.0 / 4; // 1.5 (浮点除法)
int r = 6 % 4; // 2
printf("%d %f %f %d\n", x, a, b, r);
  • 整数除法会截断小数;至少一侧为浮点才能进行浮点除法。
  • 取模 % 只适用于整数;负号由左操作数决定。

赋值操作符

在变量创建的时候给一个初始值叫初始化,在变量创建好后,再给一个值,这叫赋值。

int a = 100; //初始化
a = 200;//赋值

连续赋值

int a = 3, b = 5, c = 0;
c = b = a + 3; // 建议拆开写,便于调试

C语言虽然支持这种连续赋值,但是写出的代码不容易理解,建议还是拆开来写,这样方便观察代码的执行细节。

int a = 3;
int b = 5;
int c = 0;
b = a+3;
c = b;

复合赋值

在写代码时,经常可能对一个数进行增、自自减的操作,如下代码:

int a = 10;
a += 3; // a = a + 3
a -= 2; // a = a - 2
a *= 4; // a = a * 4
a /= 5; // a = a / 5
a %= 7; // a = a % 7

这样代码C语言给提供了更加方便的写法:

int a = 10;
a += 3;
a -= 2;

/*
+= -=
*= /= %=
>>= <<=
&= |= ^=
*/

单目操作符

C语言中还为单目操作符。 ++、--、+、- 就是单目操作符的。有一些操作符只有一个操作数,被称单目操作符。

  • 前置 ++a:先 +1 后使用。
  • 后置 a++:先使用后 +1
int a = 10;
int b = ++a; // a=11, b=11
int c = a--; // c=11, a=10
  • 一元正负号:
int x = +10;     // 等同于 10
int y = -x; // 取相反数

强制类型转换

在操作符中还有一种特殊的操作符是强制类型转换,语法形式如下:

(类型)
int a = (int)3.14; // 仅取整数部分 => 3

尽量避免不必要的强转,优先通过合理的类型设计解决告警。

printf

基本用法
#include <stdio.h>
int main(void)
{
printf("Hello World");
return 0;
}

上面命令会在屏幕上输出一行文字“Hello World”

printf() 不会在行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。

为了让光标移到下一行的开头,可以在输出文本的结尾,添加一个换行符 \n

#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}

如果文本内部有换行,也是通过插入换行符来实现,如下方代码:

#include <stdio.h>
int main(void)
{
printf("Hello\nWorld\n");
printf("Hello\n");
printf("World\n");
return 0;
}

printf() 是在标准库的头文件 stdio.h 定义的。使用这个函数之前,必须在源码文件头部引入这个头文件。

占位符

printf() 可以在输出文本中指定占位符。所谓“占位符”,就是这个位置可以用其他值代入。

// 输出 There are 3 apples
#include <stdio.h>
int main()
{
printf("There are %d apples\n", 3);
return 0;
}

上面示例中,There are %d apples\n 是输出文本,里面的 %d 就是占位符,表示这个位置要用其他值来替换。占位符的第一个字符一律为百分号 %,第二个字符表示占位符的类型,%d 表示这里代入的值必须是一个整数。
printf() 的第二个参数就是替换占位符的值,上面的例子是整数 3 替换 %d。执行后的输出结果就是 There are 3 apples

常用的占位符除了 %d,还有 %s 表示代入的是字符串。

#include <stdio.h>
int main()
{
printf("%s will come tonight\n", "zhangsan");
return 0;
}

上面示例中,%s 表示代入的是一个字符串,所以 printf() 的第二个参数就必须是字符串,这个例子是 zhangsan。执行后的输出就是 zhangsan will come tonight

输出文本里面可以使用多个占位符。

#include <stdio.h>
int main()
{
printf("%s says it is %d o'clock\n", "lisi", 21);
return 0;
}

上面示例中,输出文本 %s says it is %d o'clock 有两个占位符,第一个是字符串占位符 %s,第二个是整数占位符 %d,分别对应 printf() 的第二个参数(lisi)和第三个参数(21)。执行后的输出就是 lisi says it is 21 o'clock

参数与占位符是一一对应关系,如果有 n 个占位符,printf() 的参数就应该有 n + 1 个。如果参数个数少于对应的占位符,printf() 可能会输出内存中的任意值。

占位符列举

printf() 的占位符有许多种类,与 C 语言的数据类型相对应。下面按照字母顺序,列出常用的占位符,方便查找。

  • %a:十六进制浮点数,字母输出为小写。
  • %A:十六进制浮点数,字母输出为大写。
  • %c:字符。
  • %d:十进制整数(int)。
  • %e:使用科学计数法的浮点数,指数部分的 e 为小写。
  • %E:使用科学计数法的浮点数,指数部分的 E 为大写。
  • %i:整数,基本等同于 %d
  • %f:小数(包含 float 和 double)。
  • %g:6 个有效数字的浮点数。整数部分一旦超过 6 位,就会自动转为科学计数法,指数部分的 e 为小写。
  • %G:等同于 %g,唯一的区别是指数部分的 E 为大写。
  • %hd:十进制 short int 类型。
  • %ho:八进制 short int 类型。
  • %hx:十六进制 short int 类型。
  • %hu:unsigned short int 类型。
  • %ld:十进制 long int 类型。
  • %lo:八进制 long int 类型。
  • %lx:十六进制 long int 类型。
  • %lu:unsigned long int 类型。
  • %lld:十进制 long long int 类型。
  • %llo:八进制 long long int 类型。
  • %llx:十六进制 long long int 类型。
  • %llu:unsigned long long int 类型。
  • %Le:科学计数法表示的 long double 类型浮点数。
  • %Lf:long double 类型浮点数。
  • %n:已输出的字符串数量(本身不输出,只把数量写入变量)。
  • %o:八进制整数。
  • %p:指针(打印地址)。
  • %s:字符串。
  • %u:无符号整数(unsigned int)。
  • %x:十六进制整数。
  • %zd:size_t 类型。
  • %%:输出一个百分号。

输出格式

printf() 可以定制占位符的输出格式。

限定宽度

printf() 允许限定占位符的最小宽度。

#include <stdio.h>
int main()
{
printf("%5d\n", 123); // 输出为 " 123"
return 0;
}

上面示例中,%5d 表示这个占位符的宽度至少为 5 位。如果不满 5 位,对应的值的前面会添加空格。
输出的值默认是右对齐,即输出内容前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的 % 后面插入一个 - 号。

#include <stdio.h>
int main()
{
printf("%-5d\n", 123); // 输出为 "123 "
return 0;
}

对于小数,这个限定符会限制所有数字的最小显示宽度。

#include <stdio.h>
int main()
{
printf("%12f\n", 123.45); // 输出 " 123.450000"
return 0;
}
显示正负号
#include <stdio.h>
int main()
{
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
return 0;
}
限定小数位数
#include <stdio.h>
int main()
{
printf("Number is %.2f\n", 0.5); // 输出 Number is 0.50
return 0;
}

与限定宽度结合:

#include <stdio.h>
int main()
{
printf("%6.2f\n", 0.5); // 输出为 " 0.50"
return 0;
}

两处限定值都可用 * 由参数传入:

#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5); // 等同于 printf("%6.2f\n", 0.5);
return 0;
}

(4)输出部分字符串

#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world"); // 输出 hello
return 0;
}

scanf

当有了变量,需要给变量输入值可以使用 scanf 函数;如果需要将变量的值输出在屏幕上,可以使用 printf 函数。

示例:

#include <stdio.h>

int main()
{
int score = 0;
printf("请输入成绩:");
scanf("%d", &score);
printf("成绩是:%d\n", score);
return 0;
}

注:标准输入一般指的是键盘,标准输出一般指的是屏幕。

基本用法

scanf("%d", &i);
  • 第一个参数是格式字符串(含占位符),其余参数是存放输入的变量。有多少个占位符,就有多少个变量。
  • %d 表示输入应为整数。&i 传入变量 i 的地址(指针变量除外)。

一次读入多个变量:

scanf("%d%d%f%f", &i, &j, &x, &y);
  • 数值占位符会自动忽略空白字符(空格、制表符、换行符等);数据之间有多个空格或分多行输入都不影响解读。

处理从上次停止处继续解读:

#include <stdio.h>

int main()
{
int x;
float y;
// 用户输入 "-13.45e12# 0"
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
return 0;
}

上例第一次 %d 读到 -13 停在 .;第二次 %f. 开始读到 .45e12,遇到 # 停止。也可写为:

#include <stdio.h>

int main()
{
int x;
float y;
// 用户输入 "-13.45e12# 0"
scanf("%d%f", &x, &y);
return 0;
}

返回值

  • 返回成功读取的变量个数;失败返回 0;在成功读取前出错或到达文件结尾返回 EOF(-1)
#include <stdio.h>

int main()
{
int a = 0;
int b = 0;
float f = 0.0f;
int r = scanf("%d %d %f", &a, &b, &f);
printf("a=%d b=%d f=%f\n", a, b, f);
printf("r = %d\n", r);
return 0;
}

(在 VS 环境,按 3 次 ctrl+z 结束:若只输入 2 个数后结束,r=2;若一个都不输直接结束,r=-1EOF。)

占位符

  • %c:字符(自动跳过前导空白)。若想跳过前导空白,写成 scanf(" %c", &ch);
  • %d:整数。
  • %ffloat
  • %lfdouble
  • %Lflong double
  • %s:字符串(从第一个非空白字符开始,直到遇到空白字符为止;末尾自动写入 \0)。
  • %[]:指定字符集合(如 %[0-9])。

读取字符串到数组时应限制最大长度,避免越界:

#include <stdio.h>

int main()
{
char name[11];
scanf("%10s", name); // 最多读取 10 个字符
return 0;
}

赋值忽略符

#include <stdio.h>

int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}

如果用户输入 2020/01/01 等不同分隔符,改为:

#include <stdio.h>

int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day); // %*c:匹配但不赋值,兼容任意分隔符
return 0;
}