[C语言快速入门] 基础知识和基本数据类型

发布时间 2023-10-11 19:37:25作者: BryceAi

[C语言快速入门] 基础知识和基本数据类型

这里主要的知识点主要来自这两个教学视频:
史上最强最细腻的linux嵌入式C语言学习教程【李慧芹老师】_哔哩哔哩_bilibili
C语言基础入门_C3程序猿_哔哩哔哩_bilibili
这本书:
《C Primer Plus(第6版)中文版》

在本文开篇之前,想简单写一下关于编写C语言程序的一些注意事项:

  • C语言程序的文件后缀名是.c,比如hello.c
  • Linux中C语言程序的编译推荐GNU编译器套件(GNU Compiler Collection,简称GCC)。
  • Windows中C语言程序的编译推荐Dev-C++。
  • 足够多的注释。
  • 注释叙述内容超过两行的建议使用/* */
  • 代码中适当使用空格和换行,使代码更加清晰易读。
  • 使用库函数时,要在程序开头包含相应的头文件。
  • 通过函数,将程序分割成多个模块,使程序更加清晰易读。

1. 入门

在本章节开始之前,需要先知道几个概念:

  • 所有的功能都是通过函数实现的。非函数部分只能是变量声明和函数定义。
  • C语言代码区分大小写。
  • C语言中的语句以分号;结尾。

以上概念若有必要,后面会详细介绍。

1.1 初识printf

#include <stdio.h>

int main(void)
{
    // printf函数用于向屏幕输出一行字符
    printf("Hello World!\n");
    return 0;
}

这是第一个程序,其功能是向屏幕输出一行字符“Hello World!”。你可以使用这个程序来测试你的编译器是否正确安装。向屏幕打印你喜欢的字符串。

下面解释一下这个程序的每一行代码的含义:

  • #include <stdio.h> 是一个预处理指令,告诉编译器在实际编译之前要包含stdio.h文件的内容。stdio.h是一个头文件,其中包含了一些输入输出的函数,比如printf函数。
  • int main(void) 是一个函数,函数名是main,返回值是int类型,参数是void,表示没有参数。int是整型的意思,void是空的意思。main函数是程序的入口,程序从main函数开始执行,到main函数结束,整个程序就结束了。
  • {是函数体的开始
  • //是注释符,//后面的内容是注释,不会被编译器编译。
  • printf是一个函数,用于向屏幕输出一行字符。printf函数的参数是一个字符串,字符串用双引号""括起来,字符串中的\n表示换行。;符号表示语句结束。
  • return 0;是函数的返回值,一般来说,0表示程序正常结束,非0表示程序异常结束。
  • }是函数体的结束。

下面详细解释这个程序的每一行代码:

1.2 头文件

本节有关预处理的内容,可以参考我之前的这篇文章

#include指令用于包含头文件,它有两种形式:

  • #include <头文件>:用于包含系统标准库头文件,通常用于引入标准库函数和类型的声明。
  • #include "头文件":用于包含用户自定义的头文件,通常用于引入自定义函数和类型的声明。

根据预处理过程可以看出,头文件里的内容会被包含到源文件中,然后再进行预处理。按照惯例,头文件的扩展名是.h,但是根据预处理过程可以看出,扩展名不是强制要求的。头文件中可以包含函数的声明、宏定义、结构体、枚举等内容。一般来说,头文件中只包含声明,不包含定义。但是这不是必须的,头文件中也可以包含定义。头文件中的内容会在预处理阶段被包含到源文件中,然后再进行编译。由预处理过程可以看出,包含的头文件只是简单的替换,所以在某些情况下,也可以用#include包含源文件。

最初,并没有官方的C库。后来,基于UNIX的C实现成为了标准。ANSI C委员会主要以这个标准为基础,开发了一个官方的标准库。在意识到C语言的应用范围不断扩大后,该委员会重新定义了这个库,使之可以应用于其他系统。

如果函数被定义为宏,那么可以通过#include指令包含定义宏函数的文件。通常,类似的宏都放在合适名称的头文件中。例如,许多系统都有ctype.h文件,该文件中包含了一些确定字符性质(如大写、数字等)的宏。

因此,头文件就是一些函数的声明、宏定义、结构体、枚举等内容的集合,可以通过#include指令包含到源文件中,然后再进行预处理。主要是为了方便代码的编写和阅读。

更通俗的说,头文件里有已经写好的代码,可以直接拿来用,不需要自己再写一遍。 比如stdio.h里就有printf函数(下面会详细说到这个函数)的声明,所以我们可以直接使用printf函数,而不需要自己再写一遍printf函数。

1.3 初识“声明”和“定义”

对于变量、函数、结构体、联合体等,都需要先声明后使用。声明的作用是告诉编译器,这个变量、函数、结构体、联合体等存在,可以使用。

现在只需要知道其格式大致为:

类型名 变量名;
类型名 函数名(参数列表);

后面会针对具体的类型进行详细的讲解。

int main(void)这个函数的前面可以被看做是声明给int main(void)函数使用的声明部分,后面的{}可以被看做为这个int main(void)函数的实现部分。而由于main函数是程序的入口,所以也可以被看做是整个程序的声明部分。

由此,联系前面的头文件部分可以看出头文件的逻辑,头文件就是为了程序开始运行时,告诉编译器这个程序有哪些函数,这些函数的参数是什么,返回值是什么等信息,也就是为了程序运行做准备。

而对于定义,可以简单的理解为给变量分配内存空间,或者给函数实现

1.4 main()函数

函数是C语言程序的基本组成部分,所有的功能都是通过函数实现的。非函数部分只能是变量声明和函数定义。

函数是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、子程序、过程作用相同,但是细节上略有不同。一些函数执行某些动作,如printf()把数据打印到屏幕上;一些函数找出一个值供程序使用,如strlen()把指定字符串的长度返回给程序。一般而言,函数可以同时具备以上两种功能。

简单来说,函数是一系列语句的集合,它们一起执行一个任务。函数体现了模块化设计的思想,即将一个大的程序分割成若干个小的模块,每个模块完成一个特定的功能,这样可以使程序更加清晰易读。函数可以重复使用,可以在不同的程序中使用,也可以在同一个程序中多次使用。

每个C程序都有一个主函数,即main()函数,所有的C程序都从main()函数开始执行,到main()函数的return语句结束。

main函数的格式为:

//有参数的main函数
int main(int argc, char *argv[])
{
    //函数体
    return 0;
}

//无参数的main函数
int main(void)
{
    //函数体
    return 0;
}

这里有几个疑问:

  • 为什么C语言程序的入口是main()函数呢?
    • 因为这是C语言的规定。
  • 为什么main()函数的返回值是int类型,而不是void类型呢?
    • 这是因为main()函数的返回值是程序的返回值,0表示程序正常结束,非0表示程序异常结束。
  • main()函数的返回值返回给谁呢?
    • 这个问题需要结合操作系统来理解,一般来说,main()函数的返回值会返回给操作系统,操作系统会根据这个返回值来判断程序是否正常结束,如果程序正常结束,那么操作系统会返回0,否则返回非0

在刚开始阶段,我们只需要知道main()函数的格式即可,后面会详细讲解。下面编写程序,为必要说明,我们都采用无参数的main()函数。注意,所有符号均为英文符号,我们在编写代码时,要注意符号的使用,比如{}()[]等,都要成对出现

具体细节会在后面详细讲解。现在只需要知道main()函数是程序的入口,我们下面在讲解到函数之前,所有的测试内容都是写在main()函数中的//函数体部分。

1.5 注释

标准C语言中有两种编写注释的方式。传统上,注释语句以/*开头,以*/结束。注释可以包含任意多的字符,并总被作为空白符处理。
从C99开始,注释也可以用//开始,延伸到(但不包括)下一个行终结符。

具体形式为:

//注释内容

或者

/*注释内容1
注释内容2
注释内容3*/

1.6 printf()函数

printf()函数用于向屏幕输出一行字符,其格式为:

printf("字符串", 参数列表);

printf()函数的print是打印的意思,f是format的意思,表示格式化输出。printf()函数的参数是一个字符串,字符串用双引号""括起来,字符串中的\n表示换行。printf()函数的参数列表可以是多个,多个参数之间用逗号,隔开。

在C语言中,使用;表示语句结束,所以printf()函数的最后要加上;。具体而言,编译器会扫描当前行的语句,发现"后,会一直扫描到下一个",然后将这两个"之间的内容作为一个字符串,然后将这个字符串作为printf()函数的参数,然后执行printf()函数,最后执行;,表示语句结束。如果你已经阅读了我的这篇文章,那么你应该知道,这个过程是在预处理阶段完成的。并且优先级是:行尾 > " > ;

我们可以利用这个函数向屏幕输出一行字符,比如:

printf("1 Hello World!\n");
printf("2 Hello World!                  This is a test!\n");
printf("3 Hello World!\nThis is a test!\n");
printf("4 Hello World!\n""this is a test!\n");
printf("5 Hello World!\n"
       "this is a test!\n");
printf("6"" ""H""e""l""l""o"" ""W""o""r""l""d""!""\n");

将以上内容放在main()函数中(return 0;语句之前),编译运行,可以看到屏幕上输出了一系列字符。如下:

1 Hello World!
2 Hello World!                  This is a test!
3 Hello World!
This is a test!
4 Hello World!
this is a test!
5 Hello World!
this is a test!
6 Hello World!

Windows环境下执行程序,如果控制台窗口一闪而过,一般是因为程序执行完毕后,控制台窗口就关闭了。可以在程序的最后(return 0;语句之前)加上system("pause");语句,作用是暂停程序的执行,按任意键继续。这个语句的头文件是stdlib.h,注意要在程序开头包含这个头文件。

转义字符

在我们实际生活中,有一些特殊的字符,它们并没有实际的意义,但是我们需要用到它们,比如换行、制表符等。在C语言中,我们可以使用转义字符来表示这些特殊的字符。转义字符是以反斜杠\开头的字符,比如\n表示换行,\t表示制表符。下面是一些常用的转义字符:

转义字符 含义
\n 换行
\t 制表符
\\ 反斜杠
\" 双引号
\' 单引号
\? 问号
\a 警报
\b 退格
\f 换页
\r 回车
\v 垂直制表
\ooo 八进制ASCII
\xhh 十六进制ASCII
\0 空字符

转义字符表示一个字符,只是表现形式是两个字符组成的。下面分别解释一下这些转义字符的含义:

首先我们明确字符输出的具体过程。程序在执行的时候,会将字符串中的每一个字符输出到屏幕上,然后将光标移动到下一个字符的位置,再次输出,直到字符串的最后一个字符输出完毕。输出时光标所在的位置如果有字符,那么会覆盖掉原来的字符。

  • \n:换行,将光标移动到下一行的行首。
    • 当前一个字符输出后,遇到了\n,那么光标会移动到下一行的行首,然后输出下一个字符。
  • \t:制表符,将光标移动到下一个制表符位置。
    • 当前一个字符输出后,遇到了\t,那么光标会移动到下一个制表符位置,然后输出下一个字符。
    • 制表符(也叫制表位)的功能是在不使用表格的情况下在垂直方向按列对齐文本。
    • 一般情况下,制表符的位置是每隔8个字符。ASCII码中的字符每个占一个字符位置,汉字每个占两个字符位置。
  • \\:反斜杠,输出一个反斜杠。
    • 当前一个字符输出后,遇到了\\,那么输出一个反斜杠。
  • \":双引号,输出一个双引号。
    • 当前一个字符输出后,遇到了\",那么输出一个双引号。
  • \':单引号,输出一个单引号。
    • 当前一个字符输出后,遇到了\',那么输出一个单引号。
  • \?:问号,输出一个问号。
    • 当前一个字符输出后,遇到了\?,那么输出一个问号。
  • \a:警报,输出一个警报。
    • 当前一个字符输出后,遇到了\a,那么输出一个警报。
    • 警报的具体表现形式是:发出一声嘟的声音。
  • \b:退格,将光标移动到上一个字符的位置。
    • 当前一个字符输出后,遇到了\b,那么光标会移动到上一个字符的位置,然后输出下一个字符。
  • \f:换页,将光标移动到下一页的行首。
    • 当前一个字符输出后,遇到了\f,那么光标会移动到下一页的行首,然后输出下一个字符。
    • 一般在控制台中,这个转义字符没有效果。在一些文本编辑器中,这个转义字符的效果是将光标移动到下一页的行首。
  • \r:回车,将光标移动到当前行的行首。
    • 当前一个字符输出后,遇到了\r,那么光标会移动到当前行的行首,然后输出下一个字符。
  • \v:垂直制表,将光标移动到下一个垂直制表符位置。
    • 当前一个字符输出后,遇到了\v,那么光标会移动到下一个垂直制表符位置,然后输出下一个字符。
    • 具体的效果是将光标向下移动一个字符位置。
  • \ooo:八进制ASCII,输出一个八进制ASCII码对应的字符。
    • 当前一个字符输出后,遇到了\ooo,那么输出一个八进制ASCII码对应的字符。
    • 八进制ASCII码是指以八进制表示的ASCII码,比如\141表示的是字符a
  • \xhh:十六进制ASCII,输出一个十六进制ASCII码对应的字符。
    • 当前一个字符输出后,遇到了\xhh,那么输出一个十六进制ASCII码对应的字符。
    • 十六进制ASCII码是指以十六进制表示的ASCII码,比如\x61表示的是字符a
  • \0:空字符,输出一个空字符。
    • 当前一个字符输出后,遇到了\0,那么输出一个空字符。
    • 空字符的ASCII码是0,所以也可以写成\0
    • 注意,空字符和空格是不同的,空格的ASCII码是32。

1.7 C语言中的关键字

C语言中有32个关键字,关键字不能用作变量名、函数名、数组名等标识符。关键字的作用是用于定义变量、函数、结构体、联合体等。需要注意,这些关键字都是小写的。

这些关键字分别是:

  • auto :自动变量,用于定义自动变量。
  • break :跳出循环,用于跳出循环。
  • case :用于switch语句中,表示某个值。
  • char :字符,用于定义字符变量。
  • const :常量,用于定义常量。
  • continue :继续,用于跳过循环体中剩余的语句,然后继续下一次循环。
  • default :默认,用于switch语句中,表示默认情况。
  • do :做,用于do-while循环。
  • double :双精度,用于定义双精度浮点数。
  • else :否则,用于if-else语句。
  • enum :枚举,用于定义枚举类型。
  • extern :外部,用于声明外部变量和函数。
  • float :浮点数,用于定义浮点数。
  • for :用于for循环。
  • goto :用于goto语句,表示跳转。
  • if :用于if语句,表示如果。
  • int :整型,用于定义整型变量。
  • long :长整型,用于定义长整型变量。
  • register :寄存器,用于定义寄存器变量。
  • return :返回,用于函数返回。
  • short :短整型,用于定义短整型变量。
  • signed :有符号,用于定义有符号变量。
  • sizeof :大小,用于计算数据类型或变量的长度。
  • static :静态,用于定义静态变量。
  • struct :结构体,用于定义结构体。
  • switch :用于switch语句。
  • typedef :类型定义,用于定义类型。
  • union :联合体,用于定义联合体。
  • unsigned :无符号,用于定义无符号变量。
  • void :空,用于定义空类型。
  • volatile :易变,用于定义易变变量。
  • while :用于while循环。

1999年的C99标准中增加了5个关键字:

  • _Bool :布尔类型,用于定义布尔类型。
  • _Complex :复数,用于定义复数类型。
  • _Imaginary :虚数,用于定义虚数类型。
  • inline :内联,用于定义内联函数。
  • restrict :限定,用于限定指针。

2011年的C11标准中增加了7个关键字:

  • _Alignas :对齐,用于内存对齐,指定对齐方式。
  • _Alignof :获取对齐,用于获取对齐方式。
  • _Atomic :原子,用于原子类型。
  • _Generic :泛型,用于泛型选择。
  • _Noreturn :无返回,用于函数无返回值。
  • _Static_assert :静态断言,用于静态断言。
  • _Thread_local :线程局部,用于线程局部变量。

2. 数据类型

在本章节开始之前,需要先知道几个概念:

  • C语言里是对于数据在内存的存储大小是没有明确规定的,只是规定以int类型的数据为基准,其他类型的数据的存储大小都是大于或小于int类型的数据的存储大小。
  • 针对上一条,需要补充说明的是,在一般应试教学中,int类型的数据的存储大小是4个字节,char类型的数据的存储大小是1个字节,short类型的数据的存储大小是2个字节,long类型的数据的存储大小是4个字节,long long类型的数据的存储大小是8个字节,float类型的数据的存储大小是4个字节,double类型的数据的存储大小是8个字节,long double类型的数据的存储大小是16个字节。

以上概念若有必要,后面会详细介绍。

2.1 基本数据类型

2.1.1 整型

相信你是知道的,计算机最底层的数据是二进制的,也就是0和1。现在我们需要使用计算机完成一些生活中的任务,比如计算1+1,那么我们就需要将1和1转换成二进制,然后再进行计算。由于在计算机设计初期,计算机的存储空间非常有限,所以计算机只能尽可能的去节省计算所要消耗的资源。需要根据不同的计算场景,设置不同的数据类型。比如在计算年龄差的时候,一般不会用到超过100的数字,因此只需要用到一个字节的存储空间就可以了。而在计算银行账户余额的时候,可能会用到超过100亿的数字,因此需要用到8个字节的存储空间。所以,数据类型的设置是根据不同的计算场景来设置的。

下面来看一下C语言中的整型数据类型。

2.1.1.1 int类型

int类型是有符号整型,即int类型的值必须是整数,可以是正整数、负整数或零。其取值范围依计算机系统而异。一般而言,储存一个int要占用一个机器字长。因此,早期的16位IBM PC兼容机使用16位来储存一个int值,其取值范围(即int值的取值范围)是 \([-32768, 32767]\) 。目前的个人计算机一般是32位,因此用32位储存一个int值。32位的int值的取值范围是 \([-2147483648, 2147483647]\) 。如果你的计算机是64位的,那么int值的取值范围是 \([-9223372036854775808, 9223372036854775807]\)

通常教学过程中,我们使用的是32位的计算机,所以int值的取值范围是 \([-2147483648, 2147483647]\)

C语言中,若要输出int类型的值,需要使用%d格式控制符,%d表示输出一个十进制整数。比如:

printf("输出1:%d\n", 1);    //输出1
printf("输出100:%d\n", 100);  //输出100
printf("输出-100:%d\n", -100); //输出-100

%d表示输出一个十进制整数,%d是格式控制符,下面会详细介绍到格式控制符,现在只需要知道%d表示输出一个十进制整数即可。

输出两个int类型的值,可以使用两个%d,比如:

printf("输出1和2:%d %d\n", 1, 2);    //输出1和2
printf("输出100和200:%d %d\n", 100, 200);  //输出100和200
printf("输出-100和-200:%d %d\n", -100, -200); //输出-100和-200

同理,输出更多个int类型的值,可以使用更多个%d,以及用,隔开字符串后的对应位置的值,比如:

printf("输出1、2和3:%d %d %d\n", 1, 2, 3);    //输出1、2和3
printf("输出100、200、300和400:%d %d %d %d\n", 100, 200, 300, 400);  //输出100、200、300和400
printf("输出-100、-200、-300和-400:%d %d %d %d\n", -100, -200, -300, -400); //输出-100、-200、-300和-400

printf()函数的参数列表可以是多个,多个参数之间用逗号,隔开。

变量

但是,如果在一个很大的程序中,我们需要用到某个值很多次,并且这个值需要根据情况变动。那么我们就需要将这个值定义为变量,这样可以方便我们使用这个值,并且可以根据情况改变这个值。

在C语言中,定义变量的过程为:

类型名 变量名;  //定义(声明)变量

对于以任何变量而言,以上述形式的声明过程既是定义过程。其中,类型名是变量的类型,比如本节的int类型。变量名是变量的名称。变量名可以由字母、数字和下划线组成,但是不能以数字开头。变量名是区分大小写的,也就是说,aA是不同的变量名。变量名不能是关键字,也不能与库函数名相同。变量名的长度没有限制,变量名中不能包含空格。

变量除了上述的定义过程,还需要初始化过程。初始化过程是指给变量赋值的过程。在C语言中,变量的初始化过程为:

变量名 = 值;  //初始化变量(这条语句前面必须有这个变量的定义)
类型名 变量名 = 值;  //定义(声明)变量并初始化
输出不同进制的整数

一般情况下,C语言都假定整型常量是十进制数。然而,许多程序员很喜欢使用八进制和十六进制数。因为8和16都是2的幂,而10却不是。在C语言中,可以使用%d输出一个十进制整数,也可以使用%o输出一个八进制整数,使用%x输出一个十六进制整数。注意,没有输出二进制整数的格式控制符。另外,要显示各进制数的前缀00x0X,必须分别使用%#o%#x%#X

下面来看一下输出不同进制的整数:

printf("输出1:%d\n", 1);    //输出1
printf("输出100:%d\n", 100);  //输出100
printf("输出-100:%d\n", -100); //输出-100
printf("输出1:%o\n", 1);    //输出1
printf("输出100:%o\n", 100);  //输出144
printf("输出-100:%o\n", -100); //输出37777777604
printf("输出1:%x\n", 1);    //输出1
printf("输出100:%x\n", 100);  //输出64
printf("输出-100:%x\n", -100); //输出ffffff9c
printf("输出1:%#o\n", 1);    //输出01
printf("输出100:%#o\n", 100);  //输出0144
printf("输出-100:%#o\n", -100); //输出037777777604
printf("输出1:%#x\n", 1);    //输出0x1
printf("输出100:%#x\n", 100);  //输出0x64
printf("输出-100:%#x\n", -100); //输出0xffffff9c
printf("输出1:%#X\n", 1);    //输出0X1
printf("输出100:%#X\n", 100);  //输出0X64
printf("输出-100:%#X\n", -100); //输出0XFFFFFF9C

2.1.1.2 shortlonglong longunsigned类型

C语言提供3个附属关键字修饰基本整数类型: short、long和unsigned。应记住以下几点。

  • short int类型(或者简写为short)占用的存储空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型。
  • long intlong占用的存储空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。
  • long long intlong long (C99标准加入)占用的储存空间可能比long多,适用于更大数值的场合。该类型至少占64位。与int类似,long long是有符号类型。
  • unsigned intunsigned只用于非负值的场合。这种类型与有符号类型表示的范围不同,unsigned int常称为“无符号整型”。例如,16位unsigned int允许的取值范围是\([0,65535]\),而不是\([-32768,32767]\)。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整型可以表示更大的数。另外还有unsigned long intunsigned longunsigned intunsigned short类型,unsigned long long intunsigned long long
  • 在任何有符号类型前面添加关键字signed,可强调使用有符号类型的意图。例如,shortshort intsigned shortsigned short int都表示同一种类型。

一般情况下,这些整型类型的所占字节数和取值范围如下:

类型名 所占字节数 取值范围
short \(2\) \([-2^{8\times2}, 2^{8\times2}-1]=[-32768, 32767]\)
int \(4\) \([-2^{8\times4}, 2^{8\times4}-1]=[-2147483648, 2147483647]\)
long \(4\) \([-2^{8\times4}, 2^{8\times4}-1]=[-2147483648, 2147483647]\)
long long \(8\) \([-2^{8\times8}, 2^{8\times8}-1]=[-9223372036854775808, 9223372036854775807]\)

对于不同编译器,这些整型类型的所占字节数和取值范围可能不同。如果是应试,请按照所学的课本记忆。

对于不同的整型类型,其格式控制符也不同,如下:

类型名 格式控制符
short %hd
int %d
long %ld
long long %lld

2.1.2 浮点型

浮点型通俗的理解就是小数,比如1.23.14等。值得注意的是,浮点型可以表示的数据范围比整型要大,但是浮点型的精度比整型要低。这也就决定了在一些要求精度的场合,要尽量降低使用浮点型。建议先了解一下浮点型数据的存储原理,对比整整型的存储原理之后,相信你会有更深刻的理解。

C语言提供了3种浮点型数据类型,分别是floatdoublelong double。一般来说,这3种浮点型数据类型的取值范围和所占字节数如下:

类型名 所占字节数 取值范围
float \(4\) \([-3.4\times10^{38}, 3.4\times10^{38}]\)
double \(8\) \([-1.7\times10^{308}, 1.7\times10^{308}]\)
long double \(16\) \([-1.1\times10^{4932}, 1.1\times10^{4932}]\)

对于不同编译器,这些浮点型类型的所占字节数和取值范围可能不同。如果是应试,请按照所学的课本记忆。

对于不同的浮点型类型,其格式控制符也不同,如下:

类型名 格式控制符
float %f
double %lf
long double %Lf

2.1.3 字符型

就计算机组成而言,显然数值更接近计算机底层,如前文所说,计算机最底层的数据是二进制的,也就是0和1。而字符型则更接近人类的思维,比如abc等。数据在人的脑海里是以字符的形式,而字符型若想存储在计算机中则要以数的形式。

char类型用于储存字符(如,字母或标点符号),但是从技术层面看,char是整数类型。因为char类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符,即用特定的整数表示特定的字符。

C语言中,字符型存储的编码是ASCII码,ASCII码是一种用于显示现代英语字符的编码。ASCII码使用7位二进制数表示一个字符,共有128个字符。ASCII码的取值范围是\([0,127]\)。ASCII码的具体内容如下:

ASCII码 字符 ASCII码 字符 ASCII码 字符 ASCII码 字符
\(0\) [NUL] \(32\) \(64\) @ \(96\) `
\(1\) [SOH] \(33\) ! \(65\) A \(97\) a
\(2\) [STX] \(34\) " \(66\) B \(98\) b
\(3\) [ETX] \(35\) # \(67\) C \(99\) c
\(4\) [EOT] \(36\) $ \(68\) D \(100\) d
\(5\) [ENQ] \(37\) % \(69\) E \(101\) e
\(6\) [ACK] \(38\) & \(70\) F \(102\) f
\(7\) [BEL] \(39\) ' \(71\) G \(103\) g
\(8\) [BS] \(40\) ( \(72\) H \(104\) h
\(9\) [HT] \(41\) ) \(73\) I \(105\) i
\(10\) [LF] \(42\) * \(74\) J \(106\) j
\(11\) [VT] \(43\) + \(75\) K \(107\) k
\(12\) [FF] \(44\) , \(76\) L \(108\) l
\(13\) [CR] \(45\) - \(77\) M \(109\) m
\(14\) [SO] \(46\) . \(78\) N \(110\) n
\(15\) [SI] \(47\) / \(79\) O \(111\) o
\(16\) [DLE] \(48\) 0 \(80\) P \(112\) p
\(17\) [DC1] \(49\) 1 \(81\) Q \(113\) q
\(18\) [DC2] \(50\) 2 \(82\) R \(114\) r
\(19\) [DC3] \(51\) 3 \(83\) S \(115\) s
\(20\) [DC4] \(52\) 4 \(84\) T \(116\) t
\(21\) [NAK] \(53\) 5 \(85\) U \(117\) u
\(22\) [SYN] \(54\) 6 \(86\) V \(118\) v
\(23\) [ETB] \(55\) 7 \(87\) W \(119\) w
\(24\) [CAN] \(56\) 8 \(88\) X \(120\) x
\(25\) [EM] \(57\) 9 \(89\) Y \(121\) y
\(26\) [SUB] \(58\) : \(90\) Z \(122\) z
\(27\) [ESC] \(59\) ; \(91\) [ \(123\) {
\(28\) [FS] \(60\) < \(92\) \ \(124\) |
\(29\) [GS] \(61\) = \(93\) ] \(125\) }
\(30\) [RS] \(62\) > \(94\) ^ \(126\) ~
\(31\) [US] \(63\) ? \(95\) _ \(127\) [DEL]

ASCII码的具体内容可以在这里查看。

字符型的格式控制符是%c,其定义声明方式和整型一样。

前文有提到,printf()函数的第一个参数是字符串,这种直接写在printf()函数中的字符串叫做字符串常量。字符串是由字符组成的,字符是字符串的基本组成单位。字符串常量的表现形式是用双引号""括起来的字符序列。比如"Hello World!\n"就是一个字符串常量。后面会详细介绍。

对于字符型,其字符常量的表现形式是用单引号''括起来的字符。比如'a'就是一个字符常量。字符常量的表现形式和字符串常量的表现形式很相似,但是字符常量只能包含一个字符,而字符串常量可以包含多个字符。

注意,在这里我们要先理清一下思路。首先,这些字符在计算机的存储方式是以二进制的形式存储的,也就是0和1。其次,字符型的数据类型是char,我们可以理解为,字符型是标记为char类型的整型。当编译器识别到数据为char类型的时候,编译器会将这个数据转换成ASCII码对应的整型,然后再将这个整型转换成二进制,反之亦然。因此我们要明确以下几点:

  • 字符不一定是有具体表示,前文提到的转义字符,比如\n,是一个字符,只不过这个字符常量是由两个字符组成的。因此,转义字符就是一个字符常量。我们可以使用char类型的变量来存储转义字符。
  • 字符常量只能存储一个字符,即便以'ABCD'的形式表示,也只能存储一个字符。
  • C语言规定字符常量按照整型处理。
  • 通过转义字符\xhh\ooo可以输出ASCII码对应的字符,其中\xhh表示以十六进制表示的ASCII码,\ooo表示以八进制表示的ASCII码。
  • 不同的格式控制符,字符常量也可以通过整型输出,或者输出为整型。但是并不推荐通过整型输出字符。

下面来看一下字符型的一些例子:

char a = 'a';
printf("输出字符a:%c\n", a);    //输出字符a
printf("输出字符a的ASCII码:%d\n", a);  //输出字符a的ASCII码,十进制
printf("输出字符a的ASCII码:%o\n", a);  //输出字符a的ASCII码,八进制
printf("输出字符a的ASCII码:%x\n", a);  //输出字符a的ASCII码,十六进制
printf("输出字符a的ASCII码:%#o\n", a);  //输出字符a的ASCII码,八进制,带前缀
printf("输出字符a的ASCII码:%#x\n", a);  //输出字符a的ASCII码,十六进制,带前缀
printf("输出字符a的ASCII码:%#X\n", a);  //输出字符a的ASCII码,十六进制,带前缀

2.2 构造数据类型

构造数据类型是指由基本数据类型构造出来的数据类型,比如数组类型、结构体类型、联合体类型和枚举类型。

2.2.1 数组类型

数组由数据类型相同的一系列元素组成。需要使用数组时,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。编译器根据这些信息正确地创建数组。普通变量可以使用的类型,数组元素都可以用。

形式上,在定义数组之后,我们会获得一个定长的,具有多个相同类型元素的数组。数组的长度是固定的,不能改变。数组的长度必须是整型常量,也就是说,数组的长度必须是在编译时就确定的。技术上,数组是在内存中连续存储的一系列元素。它的长度等于每个元素的长度乘以元素的个数。

数组的定义声明方式为:

类型名 数组名[数组长度];  //定义(声明)数组

注意数组长度必须是整型常量。

数组的赋值形式为:

数组名[下标] = 值;  //赋值

C语言规定数组下标从0开始,到数组长度减1结束。比如数组长度为10,那么数组的下标范围是\([0,9]\)。数组的下标也可以是变量,比如:

int a[10];
int i = 0;
a[i] = 1;

数组还允许用花括号{}括起来的一系列值进行赋值,各个值之间用逗号,隔开。比如:

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

花括号内的值的个数必须小于等于数组的长度,否则会报错。如果花括号内的值的个数小于数组的长度,那么数组的剩余元素一般会被初始化为0。

这里补充一下,其实数组的缺省(缺少省略)值在不同情况下是不一样的,这取决于数组的存储位置。如果数组是定义在函数体内部的,那么数组的缺省值是随机的,如果数组是定义在函数体外部的,那么数组的缺省值是0。
除了位置,不同编译器可能也会有不同的实现,因此这里只是一般情况下的情况。

数组的内容其实还设计后文的指针,这里先不做过多介绍。

2.2.2 结构体类型

有没有想过,普通的数和字符可以表示,那么如何表达复杂数据,比如一个人的信息,一个学生的信息,一个班级的信息,一个学校的信息等等。这些信息都是由多个数据组成的,比如一个人的信息可以由姓名、年龄、性别、身高、体重等等组成。这些信息都是由多个数据组成的,这些数据的类型可能不同,比如姓名是字符串,年龄是整型,性别是字符型,身高和体重是浮点型。这些数据的类型不同,但是都是用来描述一个人的信息的。这个时候,我们就需要用到结构体类型。

简单来说,结构体类型就是由多个数据类型不同的数据组成的数据类型。结构体类型的定义方式为:

struct 结构体名
{
    数据类型1 成员名1;
    数据类型2 成员名2;
    数据类型3 成员名3;
    ...
    数据类型n 成员名n;
};

注意最后的分号;不能省略。

结构体类型的定义方式和普通的数据类型的定义方式不同,普通的数据类型的定义方式为:

类型名 变量名;

结构体类型的结构定义方式为:

struct 结构体名 变量名;

此外,结构体类型的结构定义方式还有一种,就是在定义结构体类型的同时定义结构体变量,比如:

struct 结构体名
{
    数据类型1 成员名1;
    数据类型2 成员名2;
    数据类型3 成员名3;
    ...
    数据类型n 成员名n;
} 结构体变量名, 结构体变量名, ...;

2.2.3 联合体类型

本小节内容后续会再次补充。下面只是简单介绍一下联合体类型。

联合体类型和结构体类型类似,都是由多个数据组成的数据类型。不同的是,结构体类型的各个成员是独立的,而联合体类型的各个成员是共用的。联合体类型的结构定义方式为:

union 联合体名
{
    数据类型1 成员名1;
    数据类型2 成员名2;
    数据类型3 成员名3;
    ...
    数据类型n 成员名n;
};

联合体类型的结构定义方式为:

union 联合体名 变量名;

2.2.4 枚举类型

枚举类型是由一系列常量组成的数据类型。枚举的主要用处是方便程序员记忆,比如星期几,我们可以用1、2、3、4、5、6、7来表示,但是这样的话,我们就需要记住1代表星期一,2代表星期二,3代表星期三,以此类推。如果我们用枚举类型来表示星期几,那么我们就可以用MONDAYTUESDAYWEDNESDAYTHURSDAYFRIDAYSATURDAYSUNDAY来表示,这样我们就不需要记住1代表星期一,2代表星期二,3代表星期三,以此类推,我们只需要记住MONDAY代表星期一,TUESDAY代表星期二,WEDNESDAY代表星期三,以此类推即可。

枚举类型的定义方式为:

enum 枚举名
{
    枚举常量1,  //枚举常量1的值为0
    枚举常量2,  //枚举常量2的值为1
    枚举常量3,  //枚举常量3的值为2
    ...
    枚举常量n   //枚举常量n的值为n-1
};

枚举在没有赋值的情况下,枚举常量的值是从0开始,依次递增的。

我们也可以给枚举常量赋值,比如:

enum 枚举名
{
    枚举常量1 = 1,  //枚举常量1的值为1
    枚举常量2 = 2,  //枚举常量2的值为2
    枚举常量3 = 3,  //枚举常量3的值为3
    ...
    枚举常量n = n   //枚举常量n的值为n
};

或者也可以指定枚举的开始值,比如:

enum 枚举名
{
    枚举常量1 = 1,  //枚举常量1的值为1
    枚举常量2,      //枚举常量2的值为2
    枚举常量3,      //枚举常量3的值为3
    ...
    枚举常量n       //枚举常量n的值为n
};

也可以从中间指定的枚举量的值,比如:

enum 枚举名
{
    枚举常量1,  //枚举常量1的值为0
    枚举常量2,  //枚举常量2的值为1
    枚举常量3,  //枚举常量3的值为2
    枚举常量4 = 10, //枚举常量4的值为10
    枚举常量5,  //枚举常量5的值为11
    枚举常量6,  //枚举常量6的值为12
};

枚举的枚举常量在程序中的使用方式为:

枚举名 枚举变量名 = 枚举常量;
printf("输出枚举变量:%d\n", 枚举变量名);

这样就可以将枚举常量赋值给枚举变量。也可以直接使用枚举常量,比如:

printf("输出枚举常量:%d\n", 枚举常量);

结合预处理中的宏定义,我们可以看出,其实枚举常量和宏定义类似,或者说枚举是宏定义常量的一种快捷表达方式。



至此,本片笔记已经介绍了C语言中的基础数据部分。在下一篇文章里,会针对C语言中的功能实现进行介绍。比如运算符、流程控制语句、函数等等。