文章目录
C语言基本数据类型关键字
还有一些从基本类型衍生的其他类型, 包括数组、 指针、 结构和联合。
常量与变量
有些数据在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,称为常量(constant)。其他数据在程序运行期间可能会被改变或被赋值,称为变量(variable)。
C语言中常量大致分为:
- 字面常量
- #define 定义的标识符常量(符号常量 / 明示常量)
- 枚举常量
- 使用const 可以定义值不可更改的变量,其本质是变量而不是常量
头文件 limits.h 和 float.h 分别提供了与整型和浮点型大小限制相关的详细信息,并定义了一系列供实现使用的常量。
整型 short、int、long、long long
C标准规定 short 不能比 int 长,int 不能比 long 长。各类型长度由计算机系统决定,ISO C基本数据类型只规定了最小长度:
16位机中,short 和 int 最小长度是16
32位机中,long 的最小长度为32位
个人计算机上最常见的设置是:long long 64位,long 32位,int 32位,short 16位
整型字面常量
程序中使用的数字,
如果在 int 范围内 都被存储为 int 类型;
如果超出了 int 类型,被存储为 long int;
如果超出了 long,被存储为 unsigned long;
如果超出了 unsigned long,被存储为 long long 或 unsigned long long。
printf() 整型转换说明符
printf() 整型转换说明符
#include <stdio.h>
int main(void) {
unsigned short a = 12;
unsigned int b = 345;
unsigned long c = 6789;
int d = -125;
printf("a = %hu\n", a);
printf("a = %ho\n", a);
printf("a = %hx\n", a);
printf("a = %hX\n", a);
printf("+++++++++++++++++++++++++++++++++++\n");
printf("b = %u\n", b);
printf("b = %o\n", b);
printf("b = %x\n", b);
printf("b = %X\n", b);
printf("+++++++++++++++++++++++++++++++++++\n");
printf("c = %lu\n", c);
printf("c = %lo\n", c);
printf("c = %lx\n", c);
printf("c = %lX\n", c);
printf("+++++++++++++++++++++++++++++++++++\n");
printf("%X\n", d); //出错
return 0;
}
有符号整型和无符号整型
有符号数以二进制补码表示法存储在内存中,最高位为符号位,0代表正数,1代表负数。 无符号数的二进制位都用来表示数值。
可移植类型 stdint.h、 inttypes.h
C 语言提供了许多有用的整数类型。但是,某些类型名在不同系统中的功能不一样。C99 新增了两个头文件 stdint.h 和 inttypes.h,以确保C语言的类型在各系统中的功能相同。
精确宽度类型
C语言为现有类型创建了更多类型名。这些新的类型名定义在 stdint.h 头文件中。例如,int32_t表示32位的有符号整数类型。在使用32位int的系统中,头文件会把int32_t作为int的别名。不同的系统也可以定义相同的类型名。例如,int为16位、long为32位的系统会把int32_t作为long的别名。然后,使用int32_t类型编写程序,并包含stdint.h头文件时,编译器会把int或long替换成与当前系统匹配的类型。
int32_t表示整数类型的宽度正好是32位。但是,计算机的底层系统可能不支持。因此,精确宽度整数类型是可选项。
最小宽度类型
C99和C11提供了第2类别名集合。一些类型名保证所表示的类型一定是至少有指定宽度的最小整数类型。这组类型集合被称为最小宽度类型(minimum width type)。例如,int_least8_t是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。如果某系统的最小整数类型是16位,可能不会定义 int8_t 类型。尽管如此,该系统仍可使用int_least8_t类型,但可能把该类型实现为16位的整数类型。
最快最小宽度类型
一些程序员更关心速度而非空间。为此,C99和C11定义了一组可使计算达到最快的类型集合。这组类型集合被称为最快最小宽度类型
(fastst minimum width type)。例如,int_fast8_t 被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
最大整数类型
有些程序员需要系统的最大整数类型。为此,C99定义了最大的有符号整数类型intmax_t,可储存任何有效的有符号整数值。类似地,unitmax_t表示最大的无符号整数类型。顺带一提,这些类型有可能比long long和unsigned long类型更大,因为C编译器除了实现标准规定的类型以外,还可利用C语言实现其他类型。例如,一些编译器在标准引入 long long 类型之前,已提前实现了该类型。
sizeof 关键字
sizeof是C语言的内置运算符, 以字节为单位给出指定类型的大小。sizeof 的结果类型是 size_t 类型,size_t 在 stddef.h 中被定义为 unsigned int。C99和C11为 size_t 提供 %zd 转换说明,一些不支持C99和C11的编译器可用%u或%lu代替。
sizeof 操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void 类型等。
#include <stdio.h>
int main(void)
{
int arr[10];
int name[10] = { 1,2,3 };
printf("%u\n", sizeof arr); // 40
printf("%u\n", sizeof name); // 40
printf("%u\n", sizeof("my name is Wei Li!")); //19
printf("%u\n", sizeof 100LL); //8
printf("%u\n", sizeof 15.f); //4
printf("%u\n", sizeof 15.); //8
printf("%u\n", sizeof(double)); //8
printf("%u\n", sizeof(long long)); //8
return 0;
}
字符型 char
在C语言中,用单引号括起来的单个字符被称为字符常量(character constant)。C语言规定了char类型占1字节,用于存储一个单一ASCII字符的ASCII码。
C语言将字符常量视为 int 类型而非char类型。
char grade = ‘FATE’
编译器把4个独立的8位ASCII码储存在一个32位存储单元中。如果把这样的字符常量赋给char类型变量grade,只有最后8位有效。因此,grade的值是’E’。
#include <stdio.h>
int main(void)
{
char c = 'c';
printf("c = %c\n", c); //c
printf("c = %d\n", c); //99
//%c和%d决定了数据的显示方式而不是存储方式
printf("%d\n", sizeof('c')); //4
printf("%d\n", sizeof(c)); //1
//C语言将 'c' 存储在4字节的内存单元中
char grade = 'FATE';
printf("%c\n", grade); // E
return 0;
}
ASCII码对照表
字符型字面常量
int main(void)
{
//1. 使用单引号括起来的单个字符
char a = 'A';
//2. 使用ASCII码的十进制表示
char a = 65;
//3. 使用ASCII码的八进制和十六进制表示,这种方式可以嵌套到字符串中
char ch = '\0101'; //字符A
char ch = '\x41'; // 字符A
//4. 使用转义序列表示。
char beep = '\a'; //蜂鸣字符
return 0;
}
转义字符
转义序列 \0oo 和 \xhh 是ASCII码的八进制和十六表示。
beep = '\007'; //蜂鸣字符\a
beep = '\x41'; // ‘A’
//对于八进制,省略前面的0,写成 '\07' 甚至 '\7' 都可以,编译器仍会解释为八进制。
#include <stdio.h>
int main(void)
{
char beep = 7; //蜂鸣字符,计算机会发出声响,不会输出到显示器
char a = '\007';
char b = '\07';
char c = '\7';
char d = '\x07';
printf("%c%d \x41\n", beep, beep);
//将 '\x41' 嵌入字符串
printf("%c%d \x41\n", a, a);
printf("%c%d \x41\n", b, b);
printf("%c%d \x41\n", c, c);
printf("%c%d \x41\n", d, d);
return 0;
}
char有符号还是无符号
C标准规定char类型为1字节。
有些C编译器把char实现为有符号类型,那么char可表示的范围是-128~127。
有些C编译器把char实现为无符号类型,那么char可表示的范围是0~255。
C90标准允许在char前面使用 signed 或 unsigned 修饰。这样,signed char 表示有符号类型,unsigned char 表示无符号类型。这在用char类型处理小整数时很有用。如果只用char处理字符,那么char前面无需使用任何修饰符。
真值类型 _Bool
布尔类型只有两个值:真(true)假(false)
C语言中“非0为真”用数字“1”来表示,“0为假”用数字“0”来表示。 C99标准添加了 _Bool 类型用于表示布尔值。_Bool 类型只有两个值0和1, 虽然对0和1而言,1位的存储空间足够了,但大多数的编译器都将 _Bool 类型实现为1字节。
#include <stdio.h>
int main() {
_Bool b;
for (int i = -3; i < 4; i++) {
b = i; // C语言非0为真'1' , 0为假'0'
if (b) {
printf("i = %d b = %d true\n", i, b);
}
else {
printf("i = %d b = %d false\n", i, b);
}
}
printf("size of b = %zd\n", sizeof b); // _Bool 在MSVC中是1字节
return 0;
}
为提高与C++的兼容性,C99中提供了一个头文件 stdbool.h,其中 定义了 bool 代表_Bool,true代表1,false代表0。
实型 float 、double 、long double
实型又叫浮点型,C语言只保证long double类型至少与double类型的精度相同。
浮点型字面常量
printf() 浮点型转换说明
printf() 浮点型转换说明符
根据C标准printf() 中没有设计 %lf 转换说明。
复数和虚数
复数包括实数和虚数:
实数:有理数和无理数的总称。
无理数就是无限不循环小数。
有理数就包括整数和分数。
虚数:
规定 i2=-1,i=
−
1
\sqrt{-1}
−1 。i (imaginary)称为虚数单位。形如c=a+bi(a,b均为实数且b≠0)的数称为虚数,当a=0且b≠0时,称c为纯虚数。虚数运算和实数运算法则完全一致,都满足(乘法或加法)结合律,分配律和交换律。
复数:
形如z=a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位。当虚部等于零时,这个复数可以视为实数。
C语言3种复数类型:
float _Complex 、double _Complex 和 long double _Complex 。
C语言3种虚数类型:
float _Imaginary、double _Imaginary 和 long double _Imaginary。
float _Complex类型的变量应包含两个float类型的值,分别表示复数的实部和虚部。
字符串 String
在C中,字符串(character string)是用双引号括起来的以’\0’结尾的字符序列。
C语言没有专门用于储存字符串的变量类型,字符串中的字符都被储存在char类型的数组中。数组是顺序存储的相同类型元素的集合。C语言用空字符(null character,转义序列为 \0,ASCII码为0)标记字符串的结束,这表明数组的容量必须至少比待存储字符串中的字符数多1。
#include <stdio.h>
int main(void)
{
char name[40]; //定义一个长度为40的char数组
printf("What's your name?");
//从第一个非空白字符开始,读取一个单词,遇到空白字符结束。读取结束scanf自动添加\0
scanf("%s", name);
printf("Hello, %s\n", name);
return 0;
}
printf() 转换说明
用 printf() 打印数据时,每种数据类型都有对应的转换说明。
double 对应 %f , long double 对应 %Lf。float 没有专门的转换说明(有的编译器可能设计了 %lf,但这不是C标准),这是因为 printf() 函数的 float 类型参数会自动转换成 double 类型。
在%和转换字符之间插入转换说明修饰符可修饰基本的转换说明。如果要插入多个字符,其书写顺序应该与表2中列出的顺序相同。
#include<stdio.h>
int main(void)
{
//浮点数的精度
float pi = 823.55688;
printf("%e\n", pi); //
printf("%.3e\n", pi); //小数点右边保留3位数
printf("%f\n", pi); //
printf("%.3f\n", pi); //小数点右边保留3位数
printf("%.f\n", pi); //四舍五入保留整数 824
printf("%.0f\n", pi); //四舍五入保留整数 824
printf("%g\n", pi); // 指数小于默认有效数字位数显示成823.557
printf("%.2g\n", pi); // 指数(02)等于有效数字(2)显示成 8.2e+02
printf("%.5g\n", pi); // 指数(02)小于有效数字(5)显示成 823.56
printf("------------------------\n");
// 字符串精度
printf("%.4s\n", "MyNameIsDennisRitch"); //只能打印四个字符 MyNa
printf("------------------------\n");
return 0;
}
#include <stdio.h>
int main(void){
int a = 123;
int b = -123;
float pi = 823.55688;
// .5精度 10 字段宽度 - 左对齐 (默认是右对齐)
printf("*%.5d*\n", a); // 在前边填充0达到精度
printf("*%10.5d*\n", a);
printf("*%-10.5d*\n", a);
printf("1----------------------\n");
// "+" 显示代数符号(+和-)
printf("*%+-10.5d*\n", a);
printf("*%+-10.5d*\n", b);
printf("2----------------------\n");
// " " 显示代数符号,负号不变,正号被空格代替
printf("*% -10.5d*\n", a);
printf("*% -10.5d*\n", b);
// 同时出现标记" "和"+", " "被"+"覆盖
printf("*%+ -10.5d*\n", a);
// " "和"+"标记不能和%o、%x搭配使用,因为%o和%x用于转换无符号整数。
printf("*%+-10.5o*\n", a);
printf("*%+-10.5x*\n", a);
printf("3----------------------\n");
// "#" %o和%x将无符号数转换成8进制和16进制,使用"#"标记显示前缀0和0x
printf("*%#-10o*\n", a);
printf("*%#-10x*\n", a);
// 遇到指定精度或填充0时,前缀显示在最前面
printf("*%#-10.5X*\n", a);
// 遇到指定精度时,当精度大于整数位数,前缀0被隐藏
printf("*%#-10.6o*\n", 123u);
printf("*%#-10.5o*\n", 123u);
printf("4----------------------\n");
// "#" 对于所有的浮点格式,保证了即使后面没有任何数字,也打印一个小数点字符
printf("*%10.f*\n", pi); //
printf("*%10.0f*\n", pi);
printf("*%#10.f*\n", pi);
printf("*%#+-10.0f*\n", pi);
printf("5----------------------\n");
// "0" 用前导0代替空格填充字段宽度,遇到"-"标记被忽略。对于整数,遇到指定精度被忽略
printf("*%0#+10.2f*\n", pi);
printf("*%0#10x*\n", a);
printf("*%+010d*\n", a);
//指定精度.5 0标记失效
printf("*%+010.5d*\n", a);
return 0;
}
打印较长字符串
- 使用多个 printf() 语句
- 用反斜杠(\)和 Enter(或Return)键组合来断行
- 在两个用双引号括起来的字符串之间用空白字符隔开,编译器会把多个字符串看作是一个字符串。
int main(void)
{
printf("aaaaaaa" "bbbbbbb"
"cccccccc\n");
printf("ccccccccccccccc\
dddddddd");
return 0;
}
printf() 的返回值
printf() 返回打印字符的个数。如果有输出错误,printf() 返回一个负值(printf() 的旧版本会返回不同的值)。通常在检查输出错误时可能会用到(写入文件时很常用)。
转换说明不匹配
#include <stdio.h>
int main(void) {
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
printf("%ld %ld\n", n3, n4);
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
printf("%ld %ld %ld %ld %ld %ld\n", n1, n2, n3, n4);
return 0;
}
3.0e+00 3.0e+00 3.1e+46 2.4e-308
2000000000 1234567890
0 1074266112 0 1074266112
0 1074266112 0 1074266112 2000000000 1234567890
float 8字节, double 8字节, long 四字节
第一行,float类型的值作为printf()参数时会被转换成double类型。 n1的值被自动转换成8字节。
第一行输出说明%e转换说明没有把整数转换成浮点数。 %e 让printf()函数认为待打印的值是double类型 。当printf()处理n3时, 除了查看n3的4字节外, 还会查看n3相邻的4字节, 共8字节单元。 接着将8字节单元中的位组合解释成浮点数,最终得到的结果 是无意义的值。
第三3行输出显示, n3和n4的转换说明是匹配的,但打印结果是虚假的。
printf(" %ld %ld %ld %ld\n", n1, n2, n3, n4);
该调用告诉计算机把变量n1、n2、n3和n4的值传递给程序。 程序把传入的值放入被称为栈(stack) 的内存区域。 计算机根据变量类型(不是根据转换说明) 把这些值放入栈中。 因此, n1被储存在栈中, 占8字节(float类型被转换成double类型),n2也在栈中占8字节, n3和n4在栈中分别占4字节。 然后, 控制转到printf()函数。
该函数根据转换说明(不是根据变量类型) 从栈中读取值。 %ld转换说明表明 printf() 应该读取4字节, 所以 printf() 读取栈中的前4字节作为第1个值。 这是 n1的前半部分, 将被解释成一个 long 类型的整数。 根据下一个%ld转换说明, printf()再读取4字节, 这是n1的后半部分, 将被解释成第2个long类型的整数。 类似地, 根据第3个和第4个%ld, printf()读取n2的前半部分和后半部分, 并解释成两个long类型的整数。 因此, 对于n3和n4, 虽然用对了转换说明, 但printf()还是读错了字节。第四行printf()函数添加了两个%ld并且成功将n3、n4的值输出足以证明。
scanf() 转换说明
printf() 把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。
scanf() 把输入的字符串转换成整数、浮点数、字符或字符串。
可以在表4的转换说明中(百分号和转换字符之间)使用修饰符。如果要使用多个修饰符,必须按表5所列的顺序书写。
printf() 使用%f、%e、%E、%g 、%G转换说明来打印float类型和double类型。而 scanf() 只把它们用于 float 类型,对于 double 类型要使用 %lf 修饰符,long double 使用 %Lf 修饰符。
scanf() 函数使用空白(换行符、制表符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白。
%d读取一个整数
- 首先跳过前面的空白字符
- 如果遇到一个除数字和符号(+/-)以外的字符X,scanf() 读取中断且不给指定变量赋值,并返回已成功读取的项数。下一个 scanf() 函数将从字符X开始读取。
- 如果遇到一个数字,保存该数字,读取下一个数字,直到遇到空格或者非数字,读取结束。
- 如果遇到一个符号,保存符号,读取下一个字符X。
(1) X是非数字,scanf() 读取中断且不给指定变量赋值,并返回已成功读取的项数,下一个scanf 函数将从字符X开始读取。
(2) X是数字,继续读取下一个直到遇到非数字,读取结束。 - 使用字段宽度,scanf() 会在字段结尾或遇到空白字符处停止读取
- 如果一个 scanf() 带多个转换说明,规定在第1个出错处停止读取输入,例如,原本要读取一个数字,但是输入了一个非数字字符。
int a = 1;
char b = 'b';
char c = 'c';
printf("Input:");
scanf("%d%c", &a, &b);
scanf("%c", &c);
printf("a = %d\nb = %c\nc = %c\n", a, b, c);
printf("------------");
%s读取一个字符串
scanf() 跳过空白开始读取非空白字符,再次遇到空白。如果使用字段宽度,scanf() 在字段末尾或第1个空白字符处停止读取。
格式字符串中的空白
- 格式字符串中使用空白表示跳过下一个输入项前面的所有空白。所有空白的概念包括没有空格的特殊情况。
- 除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。
scanf("%d%d", &n , &m)
scanf("%d %d", &n , &m)
两个scanf行为相同
%c会读取空白字符。但如果把%c放在格式字符串中的空格后面,%c就会跟其它转换说明一样跳过空白字符。
scanf("%c", &ch)从输入中的第1个字符开始读取,
scanf(" %c", &ch)从第1个非空白字符开始读取。
格式字符串中的普通字符
scanf() 函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。
scanf("%d,%d", &n, &m);
第一个输入项后面必须紧跟逗号,由于scanf()会跳过输入项前面的空白,逗号后面可以输入任意空白。
(88,77) (88, 77)
scanf("%d ,%d", &n, &m);
逗号前面和后面可以有任意空白,(88,77) (88, 77) (88 , 77)
读取 (-13.45e12# 0)
如果其对应的转换说明是%d,scanf() 会读取3个字符(-13)并停在小数点处,小数点将被留在输入中作为下一次输入的首字符。
如果其对应的转换说明是%f,scanf() 会读取-13.45e12,并停在#符号处,而#将被留在输入中作为下一次输入的首字符;然后,scanf 把读取的字符序列-13.45e12转换成相应的浮点值,并储存在float类型的目标变量中。
如果其对应的转换说明是%s,scanf() 会读取-13.45e12#,并停在空格处,空格将被留在输入中作为下一次输入的首字符,然后,scanf() 把这 10个字符的字符码储存在目标字符数组中,并在末尾加上一个空字符。
如果其对应的转换说明是%c,scanf() 只会读取并储存第1个字符。
scanf的返回值
scanf() 会在第1个读取出错(例如,原本要读取一个数字,但是输入了一个非数字字符串)处停止读取并返回成功读取的项数,如果没有读取任何项,scanf() 便返回0。如果 scanf() 在转换值之前出了问题(例如,检测到文件结尾或遇到硬件问题),会返回一个特殊值EOF(其值通常被定义为-1)。
#include <stdio.h>
int main(void)
{
int a = 0, b = 0;
char c = 'A';
int num;
do {
num = scanf("%d,%c,%d", &a, &c, &b);
printf("a=%2d c=%2c b=%2d\n", a, c, b);
printf("num = %d\n", num);
fflush(stdin); //清空输入缓冲区
} while (num != 3);
printf("thank you!");
return 0;
}
printf() 和 scanf() 的*修饰符
printf 和scanf 都可以使用* 修饰符来修改转换说明的含义,
#include <stdio.h>
int main(void)
{
unsigned width, n;
int number = 256;
double weight = 242.6;
printf("输入宽度:");
scanf("%d", &width);
printf("number = %*d\n", width, number); //widht是字段宽度
printf("number = %*f\n", width, weight); //width代替*的位置指定字段宽度
scanf("%*d %*d %d", &n); //跳过两个%d输入项
printf("n = %d\n", n);
return 0;
}
getchar()和putchar()
getchar()和 putchar()都不是真正的函数,在stdio.h 头文件中它们被定义为宏。
char ch = getchar();
//读取一个字符,等同于 scanf(“%c”, &ch)
putchar(ch);
//打印一个字符ch,等同于 printf(“%c”, ch)
int main(void) {
char ch;
while ((ch = getchar()) != '#')
putchar(ch);
return 0;
}