C语言 第一章-数据类型
数据类型反应了数据的数值范围以及对这类数据可以施加的运算。
一、常量与变量
1.常量
又叫字面量,指程序在运行过程中,数据不能被改变的量。
具体类型有:整型常量、实型常量(浮点)、字符常量、字符串常量、符号常量(宏定义)。
科学计数法:
0.25e+14 —– 0.251014
98.723E-3 —- 98.72310-3
2.变量
变量是在程序运行过程中其值可以改变地量,它代表一个有名字、有特定属性的存储单元。变量应该先定义后使用。
变量名的规范:
不能是C语言的标识符(for,if,or)
第一个字符应该是字母或下划线
区分大小写
变量与指针变量:
1
2a ---- 值 p ---- 址
&a ---- 址 *p ---- 值
3.符号常量和常变量
(1) 符号常量
1 |
|
(2) 常变量
1 |
|
(3) 二者的区别
- 编译程序会在编译前进行预处理,将宏定义转化为对应语句。符号常量会被替换为对应数字。 符号常量仅仅是一个临时的符号 。
- 常变量只能在定义时赋值。常变量仍然是变量,有数据类型,占存储空间,但无法改变其数值。
4.自动类型转换和强制类型转换
(1) 自动类型转换
若一个表达式中含有不同类型的变量和常量,在计算时,会自动将它们转换为统一类型。具体有:
表达式中出现浮点数,会统一转换为双精度浮点,即 double 类型。
浮点数赋值给整型,会舍弃小数部分。
(2) 强制类型转换
1 |
|
5.局部变量和全局变量及其存储方式
具体请看:
6.动态内存分配
具体请看:
二、整型
1.int、long和long long
(1) int
①输入输出用 %d
②适用于绝对值<= 10 9 的数。(-2 31 ~2 31 -1)
int数防止超出范围的有用技巧:
普通方式:
1
int num = (a + b) / 2;
更好方式:
1
int num = (a - b) / 2 + b;
(2) long和long long
long 的输入输出用 %ld
long long 的输入输出用 %lld
如果 long long 型赋大于 231 - 1 的初值,则需要在数值后面加上 LL ,否则会编译失败。
2.整型的存储
(1) 整数的存储
按补码存储
(2) 首位为符号位
0 代表大于等于 0 ;1 代表小于 0 。
(3) 原码、反码和补码
对于正数,原码=反码=补码
对于负数,
原码:首位符号位为 1 表负数,其余位为数字的二进制表示。
反码:除了符号位以外,全部取反。
补码:反码 +1。
-15:
1
2
3
4
5原码:1 0 0 0 1 1 1 1
反码:1 1 1 1 0 0 0 0
补码:1 1 1 1 0 0 0 1(4) 原码、反码和补码的意义
使正数的补码(即正数本身)+ 负数的补码 = 一个溢出的 0
1
2
3
4
5
6
7
15: 0 0 0 0 1 1 1 1
-15: + 1 1 1 1 0 0 0 1
-----------------------------------
= 1 0 0 0 0 0 0 0 0
(4) 数值范围
注:
10000000 + 1000000 = 溢出的 0
所以 - 2^n^ 没有对应的正数,数值范围为 - 2 n ~ 2 n - 1
3.无符号整型 unsigned
(1) 输入输出用 %u
(2) 数值范围为 0 ~ 2 32 - 1
因为无需符号位,所以多了一位用于表示数字,数值范围扩大一倍
(3) 无符号整型的初衷
无符号整型的初衷并非扩大数值范围,而是为了做纯二进制运算,主要是为了移位。
(4) 无符号整型赋负值
给无符号整型变量赋值时,系统会先将其转换为补码形式,再赋给变量。
1 |
|
此时,num == 65535 == 216-1
4.进制
(1) 八进制
字面量以 0 开头
输出时,用 %o
(2) 十六进制
字面量以 0x 开头
输出时,用 %x
A ~ F 表示 11 ~ 16
三、浮点
1.单精度浮点 float
输入用 %f ,输出用 %f / %e
%e 为以指数形式输出
只能以指数形式输出,不能以指数形式输入
2.双精度浮点 double
输入用 %lf ,输出用 %f / %e
3.浮点数并不准确
在计算机中,浮点数是近似地表达一个数值。浮点数与实际数值之间存在无穷小的差距,并不是完全相等。
浮点数的比较:
引入一个小数,当数值之差小于这个小数时,便将两数视为相等。
1
2
const double eps = 1e-8;
#define Equ(a, b) ((fabs((a) - (b)) < (eps))如此,便可以用
Equ(a, b)
判断两数是否“相等”,例如:
1
2
3
4
5
6
if (Equ(a, b)){
}
if (!Equ(a, b)){
}
4.如何选择浮点类型
在C语言中,浮点数默认都为double形式。无特殊需求时,用double!!!
四、字符
1.表示方式
字符需加单引号表示。
例如: ‘a’ , ‘b’
2.存储方式
字符在数组中以整数形式存储。
‘a’ 的 ASCII 代码是97
‘A’ 的 ASCII 代码是65
小写字母 - 大写字母 =32
3.定义字符变量
1 |
|
4.输入字符
使用 scanf :
1 |
|
使用 getchar :
1 |
|
1getchar();
可于清空缓冲区(清空输入后残余的空格、回车等无效字符)。
可以加在程序的末尾,使程序运行后暂停,与
system("pause");
作用相同。
5.输出字符
使用 printf :
1 |
|
使用 putchar :
1 |
|
五、字符串
1.字符串
在 C 语言中,字符串是以字符 ‘\0’ 作为结束标志,且不包含字符 ‘\0’ 的一维字符数组。
‘\0’ = 0 ≠ ‘0’ = 48
‘\0’标志着字符串的结束,必须有’\0’,但’\0’不属于字符串
2.表示方式
字符串需加双引号表示,
例如:
“Hello” , “World!”
3.存储方式
字符串常量 会作为字符数组存储在内存的某个位置,其大小为 字符数+1。
字符串变量 以带结束标志的字符数组存储,其大小 >= 字符数+1 。(字符数组可以留空)
4.两种字符串
(1) 字符串指针
1 |
|
S 是一个指针,并且是 const char 类型的指针。
1
int * const p = &a;
无法修改指向,可以修改指向的值
1
const int *p = &a;
无法修改指向的值,可以修改地址
无法通过 S 修改字符串,但可以修改 S 的指向。
1
2char *S = "Hello";
S = "World";此时并不是成功修改了字符串的值,而是舍弃 “Hello” 后,向系统申请了一片空间放 “World”,并指向它。没有修改字符串的值,而是修改了指针 S 的指向。
实际上是定义了一个指针,并指向静态存储区中的字符串常量。因为字符串常量只读,所以无法修改。
《C Primer Plus》(P279)
编译器有可能允许通过字符串指针修改字符串,但这是危险的。
在C语言中,每一个字符串常量仅会被存储一次。存储之后,每次使用 “字符串” 时,就会访问对应区域,“字符串”可以视为指向对应字符串的指针。因此,若通过字符串指针修改了对应区域的内容,便会影响所有使用该字符串常量的代码。(指针及其内容不对应)
例如:
1
2
3
4char *word = "Hello";
word[0] = a;
printf("%s", word);
printf("%s", "hello");运行结果:
1
2aello //这似乎没有什么问题
aello //常量和打印出的常量不对应
(2) 字符串数组
1 |
|
S 既是字符串,也是字符数组。
通过 S ,可以对字符串做修改,但无法修改指向。
例如:
1
S[2]='c';
此时,S[ ] = ‘H’,’e’,’c’,’l’,’o’,’\0’
实际上是定义了一个数组,再将静态存储区态存储区中的字符串常量拷贝过来。因此,此时有两个相同的字符串。
5.定义字符串
(1) 法1
1 |
|
只是字符数组,不是字符串
(2) 法2
1 |
|
既是字符数组,又是字符串
(3) 法3
1 |
|
既是字符数组,又是字符串,系统会自动补上末尾的 ‘\0’;字符串大小为5,字符数组大小为6
(4) 法4
1 |
|
既是字符数组,又是字符串,系统会自动将多余空间都设置为 ‘\0’;字符串大小为5,字符数组大小为10
(5) 法5
1 |
|
既是字符数组,又是字符串,系统会自动将多余空间都设置为 ‘\0’;仅在 n >= 5+1 时,才可以执行
6.字符串输入输出
(1) 逐个输入
for循环,以字符的输入方式逐个输入。
(2) scanf和printf
1 |
|
需要注意的几点:
scanf 无需带 取值符号& 是因为字符数组的名称本身就代表着这个数组第一个元素的地址。
scanf 仅适用于 字符串数组 。因为 scanf 是传入地址,再对地址上的数值做修改,而 字符串指针 不支持数值的修改。
scanf仅在读取 %c 时才会读取空格,否则都会自动跳过空格。因此,使用此种方法输入时,遇到空格、TAB、回车都会自动结束读取。每次只能读取一个单词 。
不安全,当读入的字符超过 length-1 时,会出错。
更安全的做法:
假设 length = 7
scanf (“%6s”, word);
如此就能只读入 length-1 个,防止出错
(3) gets()和puts()
输入:
1 |
|
需要注意的几点:
不会跳过空格,读入字符串,直至回车。
回车不会读入,并且会自动在末尾不上 ‘\0’ 。
超出范围会出错,并不安全!!!
可用 scanf 、fgets() 、gets_s() 替代,
具体请看:
输出:
1 |
|
7.字符串函数
需要先 #include <string.h>
(1) strcat
1 |
|
将 字符串2 连接到 字符串1 之后。
(2) strcpy
1 |
|
将 字符串2 复制给 字符串1 。
(3) strcmp
1 |
|
比较 字符串1 和 字符串2 的大小,若相等返回0;若 字符串1 更大则返回正数;若 字符串2 大则返回负数。
比较方法与字典单词排序类似:
从首字母开始,按 ASCII 值比较,不相等则结束比较返回结果,相等则继续比较下一位。
小写字母大于大写字母。
(4) strlen
1 |
|
返回字符串的长度。(而不是字符数组的长度,不包含结束标记以及标记之后的元素)
(5)* strassign
1 |
|
将字符串常量复制给数组。
(6)* substring
1 |
|
求字符串中从起点开始,长度为length的子串,并赋给数组。
(7)* replace
1 |
|
用 子串2 替换字符串中检索得到的第一个 子串1 。
(8)* strinsert
1 |
|
在字符串中,位置之前插入子串 。
(9)* index
1 |
|
在字符串中,位置之后查找子串,并返回其位置。
六、数组
1.概念
数组是一组有序数据的集合;数组中每一个元素都属于同一个数据类型。
2.定义数组
1 |
|
例如:
1
int a[10];
数组大小应为整数
在 C99 之前,数组大小必须是编译时就确定的 常数
数组一旦创建,其大小就不能改变
3.访问数组
1 |
|
数组中的元素在内存中紧密排列
数组的每一个单元就是一个变量
通过 数组名称[下标] 访问每一个单元,下标范围为: 0 ~ 数组大小 - 1
访问时应注意不能发生数组越界
数组只能通过 数组+下标 访问,而不能通过数组本身访问
4.初始化
(1) 初始化为零
1 |
|
在大括号内写了 n 个 0,便会定义 n 个空间,并赋值为 0 。
1 |
|
定义 n 个空间,未被赋初值的空间会被自动初始化为 0 。
也可能是很大的随机数,视编译器而定
(2) 初始化为数值
1 |
|
在大括号内写了 n 个 数字,便会定义 n 个空间,并赋值为对应数字。
1 |
|
定义 n 个空间,未被赋初值的空间会被自动初始化为 0 。
5.多维数组
以二维数组为例
(1) 定义
1 |
|
数组大小2 必须给出,原因如下:
1
int a = [2][4];
会申请一个两行四列的空间,又因为多维数组在C语言中按行优先存放,因此必须确定数组大小2的值。
(2) 访问
1 |
|
(3) 理解
可以将二维数组看作每一个元素都是一个数组的数组。
例如int a[2][5]
,可以看作数组 a 有两个元素,每个元素又分别是一个长度为5的一维数组。
(4) 初始化
① 分别给每行赋值:
1 |
|
② 也可以不加括号:
1 |
|
② 的效果与 ① 相同,但 ① 的形式更不容易出错。
③ 对部分元素赋初值:
1 |
|
仅对部分元素赋初值,未赋值元素自动初始化为 0
④ 省略 数组大小1:
1 |
|
(5) 特别提醒
如果数组大小较大(大概 106 级别),则需要将其定义在主函数外,否则会使函数异常退出。
原因是:
局部变量分配至动态存储区,由于系统栈的空间相对较小,将会异常退出。而全局变量分配至静态存储区,允许的空间更大。
6.const数组
待更新
7.static数组
待更新
8.return数组
可以通过定义 static数组 和 动态分配 的方法,从函数中返回数组。
相关博文: C语言 从函数中返回数组
七、指针
具体请看:
八、结构体 共用体 枚举
1.结构体
具体请看:
2.共用体
具体请看:
3.枚举
具体请看:
九、数据类型和数据结构
数据类型:一种数据的数值范围和操作集合。
数据结构:一组数据的存储、使用及它们之间的关系。
参考
- C程序设计
- C Primer Plus
- C 语言教程 | 菜鸟教程