C语言 第一章-数据类型

数据类型反应了数据的数值范围以及对这类数据可以施加的运算。

一、常量与变量

1.常量

又叫字面量,指程序在运行过程中,数据不能被改变的量。

具体类型有:整型常量、实型常量(浮点)、字符常量、字符串常量、符号常量(宏定义)。

科学计数法:

0.25e+14 —– 0.251014
98.723E-3 —- 98.723
10-3

2.变量

变量是在程序运行过程中其值可以改变地量,它代表一个有名字、有特定属性的存储单元。变量应该先定义后使用。

变量名的规范:

  • 不能是C语言的标识符(for,if,or)

  • 第一个字符应该是字母或下划线

  • 区分大小写

变量与指针变量:

1
2
 a ---- 值    p ---- 址
&a ---- 址 *p ---- 值

3.符号常量和常变量

(1) 符号常量

1
#define PI (3.14)

(2) 常变量

1
const int PI = 3.14

(3) 二者的区别

  • 编译程序会在编译前进行预处理,将宏定义转化为对应语句。符号常量会被替换为对应数字。 符号常量仅仅是一个临时的符号
  • 常变量只能在定义时赋值。常变量仍然是变量,有数据类型,占存储空间,但无法改变其数值。

4.自动类型转换和强制类型转换

(1) 自动类型转换

若一个表达式中含有不同类型的变量和常量,在计算时,会自动将它们转换为统一类型。具体有:

  • 表达式中出现浮点数,会统一转换为双精度浮点,即 double 类型。

  • 浮点数赋值给整型,会舍弃小数部分。

(2) 强制类型转换

1
( 类型 ) ( 表达式 )

5.局部变量和全局变量及其存储方式

具体请看:

C语言 全局变量、局部变量和静态局部变量

6.动态内存分配

具体请看:

C语言 动态内存分配

二、整型

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
unsigned short num = -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
char x = 'a';

4.输入字符

使用 scanf :

1
scanf("%c", &x);

使用 getchar :

1
x = getchar();
1
getchar();
  • 可于清空缓冲区(清空输入后残余的空格、回车等无效字符)。

  • 可以加在程序的末尾,使程序运行后暂停,与 system("pause"); 作用相同。

5.输出字符

使用 printf :

1
2
printf("%c", x);  //输出字符
printf("%d", x); //输出字符对应的 ASCII 值

使用 putchar :

1
putchar(x);

五、字符串

1.字符串

在 C 语言中,字符串是以字符 ‘\0’ 作为结束标志,且不包含字符 ‘\0’ 的一维字符数组。

  • ‘\0’ = 0 ≠ ‘0’ = 48

  • ‘\0’标志着字符串的结束,必须有’\0’,但’\0’不属于字符串

2.表示方式

字符串需加双引号表示,

例如:

“Hello” , “World!”

3.存储方式

字符串常量 会作为字符数组存储在内存的某个位置,其大小为 字符数+1。

字符串变量 以带结束标志的字符数组存储,其大小 >= 字符数+1 。(字符数组可以留空)

4.两种字符串

(1) 字符串指针

1
char *S = "Hello";
  • S 是一个指针,并且是 const char 类型的指针。

    1
    int * const p = &a;

    无法修改指向,可以修改指向的值

    1
    const int *p = &a;

    无法修改指向的值,可以修改地址

  • 无法通过 S 修改字符串,但可以修改 S 的指向。

    1
    2
    char *S = "Hello";
    S = "World";

    此时并不是成功修改了字符串的值,而是舍弃 “Hello” 后,向系统申请了一片空间放 “World”,并指向它。没有修改字符串的值,而是修改了指针 S 的指向。

  • 实际上是定义了一个指针,并指向静态存储区中的字符串常量。因为字符串常量只读,所以无法修改。

    《C Primer Plus》(P279)

    编译器有可能允许通过字符串指针修改字符串,但这是危险的。

    在C语言中,每一个字符串常量仅会被存储一次。存储之后,每次使用 “字符串” 时,就会访问对应区域,“字符串”可以视为指向对应字符串的指针。因此,若通过字符串指针修改了对应区域的内容,便会影响所有使用该字符串常量的代码。(指针及其内容不对应)

    例如:

    1
    2
    3
    4
    char  *word = "Hello";
    word[0] = a;
    printf("%s", word);
    printf("%s", "hello");

    运行结果:

    1
    2
    aello  //这似乎没有什么问题
    aello //常量和打印出的常量不对应

(2) 字符串数组

1
char S[] = "Hello";
  • S 既是字符串,也是字符数组。

  • 通过 S ,可以对字符串做修改,但无法修改指向。

    例如:

    1
    S[2]='c'

    此时,S[ ] = ‘H’,’e’,’c’,’l’,’o’,’\0’

  • 实际上是定义了一个数组,再将静态存储区态存储区中的字符串常量拷贝过来。因此,此时有两个相同的字符串。

5.定义字符串

(1) 法1

1
char word[] = {'H', 'E', 'L', 'L', 'O'};

只是字符数组,不是字符串

(2) 法2

1
char word[] = {'H', 'E', 'L', 'L', 'O', '\0'};

既是字符数组,又是字符串

(3) 法3

1
char word[] = "HELLO";

既是字符数组,又是字符串,系统会自动补上末尾的 ‘\0’;字符串大小为5,字符数组大小为6

(4) 法4

1
char word[10] = {'H', 'E', 'L', 'L', 'O'};

既是字符数组,又是字符串,系统会自动将多余空间都设置为 ‘\0’;字符串大小为5,字符数组大小为10

(5) 法5

1
char word[n] = {'H', 'E', 'L', 'L', 'O'};

既是字符数组,又是字符串,系统会自动将多余空间都设置为 ‘\0’;仅在 n >= 5+1 时,才可以执行

6.字符串输入输出

(1) 逐个输入

for循环,以字符的输入方式逐个输入。

(2) scanf和printf

1
2
scanf ("%s", word);
printf ("%s", word);

需要注意的几点:

  • scanf 无需带 取值符号& 是因为字符数组的名称本身就代表着这个数组第一个元素的地址。

  • scanf 仅适用于 字符串数组 。因为 scanf 是传入地址,再对地址上的数值做修改,而 字符串指针 不支持数值的修改。

  • scanf仅在读取 %c 时才会读取空格,否则都会自动跳过空格。因此,使用此种方法输入时,遇到空格、TAB、回车都会自动结束读取。每次只能读取一个单词

  • 不安全,当读入的字符超过 length-1 时,会出错。

    更安全的做法:

    假设 length = 7

    scanf (“%6s”, word);

    如此就能只读入 length-1 个,防止出错

(3) gets()和puts()

输入:

1
gets(str);

需要注意的几点:

  • 不会跳过空格,读入字符串,直至回车。

  • 回车不会读入,并且会自动在末尾不上 ‘\0’ 。

  • 超出范围会出错,并不安全!!!

    可用 scanf 、fgets() 、gets_s() 替代,

    具体请看:

    C语言 不安全的gets()

输出:

1
puts(str);           

7.字符串函数

需要先 #include <string.h>

(1) strcat

1
strcat(字符串1, 字符串2);

将 字符串2 连接到 字符串1 之后。

(2) strcpy

1
strcpy(字符串1, 字符串2);

将 字符串2 复制给 字符串1 。

(3) strcmp

1
strcmp(字符串1, 字符串2);

比较 字符串1 和 字符串2 的大小,若相等返回0;若 字符串1 更大则返回正数;若 字符串2 大则返回负数。

比较方法与字典单词排序类似:

从首字母开始,按 ASCII 值比较,不相等则结束比较返回结果,相等则继续比较下一位。

小写字母大于大写字母。

(4) strlen

1
strlen(字符串);

返回字符串的长度。(而不是字符数组的长度,不包含结束标记以及标记之后的元素)

(5)* strassign

1
strassign(数组, 字符串常量);

将字符串常量复制给数组。

(6)* substring

1
substring(数组, 字符串, 起点, 长度);

求字符串中从起点开始,长度为length的子串,并赋给数组。

(7)* replace

1
replace(字符串, 子串1, 子串2);

用 子串2 替换字符串中检索得到的第一个 子串1 。

(8)* strinsert

1
strinsert(字符串, 位置, 子串);

在字符串中,位置之前插入子串 。

(9)* index

1
index(字符串, 子串, 位置);

在字符串中,位置之后查找子串,并返回其位置。

六、数组

1.概念

数组是一组有序数据的集合;数组中每一个元素都属于同一个数据类型。

2.定义数组

1
类型 数组名称[数组大小];

例如:

1
int a[10];
  • 数组大小应为整数

  • C99 之前,数组大小必须是编译时就确定的 常数

  • 数组一旦创建,其大小就不能改变

3.访问数组

1
数组名[下标]
  • 数组中的元素在内存中紧密排列

  • 数组的每一个单元就是一个变量

  • 通过 数组名称[下标] 访问每一个单元,下标范围为: 0 ~ 数组大小 - 1

  • 访问时应注意不能发生数组越界

  • 数组只能通过 数组+下标 访问,而不能通过数组本身访问

4.初始化

(1) 初始化为零

1
int a[ ] = {0, 0, 0, 0};

在大括号内写了 n 个 0,便会定义 n 个空间,并赋值为 0 。

1
int a[n] = {0};

定义 n 个空间,未被赋初值的空间会被自动初始化为 0 。

也可能是很大的随机数,视编译器而定

(2) 初始化为数值

1
int a[ ] = {1, 9, 9, 8};

在大括号内写了 n 个 数字,便会定义 n 个空间,并赋值为对应数字。

1
int a[n] = {1, 9, 9, 8};

定义 n 个空间,未被赋初值的空间会被自动初始化为 0 。

5.多维数组

以二维数组为例

(1) 定义

1
类型 数组名称[数组大小1][数组大小2];

数组大小2 必须给出,原因如下:

1
int a = [2][4];

会申请一个两行四列的空间,又因为多维数组在C语言中按行优先存放,因此必须确定数组大小2的值。

(2) 访问

1
数组名[下标][下标]

(3) 理解

可以将二维数组看作每一个元素都是一个数组的数组。

例如int a[2][5] ,可以看作数组 a 有两个元素,每个元素又分别是一个长度为5的一维数组。

(4) 初始化

① 分别给每行赋值:

1
int a[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};

② 也可以不加括号:

1
int a[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};

② 的效果与 ① 相同,但 ① 的形式更不容易出错。

③ 对部分元素赋初值:

1
int a[2][4] = {{1}, {5, 6}};

仅对部分元素赋初值,未赋值元素自动初始化为 0

④ 省略 数组大小1:

1
int a[ ][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};

(5) 特别提醒

如果数组大小较大(大概 106 级别),则需要将其定义在主函数外,否则会使函数异常退出。

原因是:

局部变量分配至动态存储区,由于系统栈的空间相对较小,将会异常退出。而全局变量分配至静态存储区,允许的空间更大。

6.const数组

待更新

7.static数组

待更新

8.return数组

可以通过定义 static数组动态分配 的方法,从函数中返回数组。

相关博文: C语言 从函数中返回数组

七、指针

具体请看:

C语言 指针

八、结构体 共用体 枚举

1.结构体

具体请看:

C语言 结构体

2.共用体

具体请看:

C语言 共用体

3.枚举

具体请看:

C语言 枚举

九、数据类型和数据结构

数据类型:一种数据的数值范围和操作集合。

数据结构:一组数据的存储、使用及它们之间的关系。

参考