[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
类型。变量名
是变量的名称。变量名可以由字母、数字和下划线组成,但是不能以数字开头。变量名是区分大小写的,也就是说,a
和A
是不同的变量名。变量名不能是关键字,也不能与库函数名相同。变量名的长度没有限制,变量名中不能包含空格。
变量除了上述的定义过程,还需要初始化过程。初始化过程是指给变量赋值的过程。在C语言中,变量的初始化过程为:
变量名 = 值; //初始化变量(这条语句前面必须有这个变量的定义)
类型名 变量名 = 值; //定义(声明)变量并初始化
输出不同进制的整数
一般情况下,C语言都假定整型常量是十进制数。然而,许多程序员很喜欢使用八进制和十六进制数。因为8和16都是2的幂,而10却不是。在C语言中,可以使用%d
输出一个十进制整数,也可以使用%o
输出一个八进制整数,使用%x
输出一个十六进制整数。注意,没有输出二进制整数的格式控制符。另外,要显示各进制数的前缀0
、0x
和0X
,必须分别使用%#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 short
、long
、long long
和unsigned
类型
C语言提供3个附属关键字修饰基本整数类型: short、long和unsigned。应记住以下几点。
short int
类型(或者简写为short
)占用的存储空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型。long int
或long
占用的存储空间可能比int
多,适用于较大数值的场合。与int
类似,long
是有符号类型。long long int
或long long
(C99标准加入)占用的储存空间可能比long
多,适用于更大数值的场合。该类型至少占64位。与int
类似,long long
是有符号类型。unsigned int
或unsigned
只用于非负值的场合。这种类型与有符号类型表示的范围不同,unsigned int
常称为“无符号整型”。例如,16位unsigned int
允许的取值范围是\([0,65535]\),而不是\([-32768,32767]\)。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整型可以表示更大的数。另外还有unsigned long int
或unsigned long
和unsigned int
或unsigned short
类型,unsigned long long int
或unsigned long long
。- 在任何有符号类型前面添加关键字
signed
,可强调使用有符号类型的意图。例如,short
、short int
、signed short
、signed 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.2
、3.14
等。值得注意的是,浮点型可以表示的数据范围比整型要大,但是浮点型的精度比整型要低。这也就决定了在一些要求精度的场合,要尽量降低使用浮点型。建议先了解一下浮点型数据的存储原理,对比整整型的存储原理之后,相信你会有更深刻的理解。
C语言提供了3种浮点型数据类型,分别是float
、double
和long 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。而字符型则更接近人类的思维,比如a
、b
、c
等。数据在人的脑海里是以字符的形式,而字符型若想存储在计算机中则要以数的形式。
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;
} 结构体变量名, 结构体变量名, ...;
结构体的使用和其他数据类型使用的方式有一些不同。这里要引入一个新的运算符,就是.
运算符。.
运算符的作用是获取结构体变量的成员。比如:
//结构体
struct 结构体名
{
数据类型1 成员名1;
数据类型2 成员名2;
数据类型3 成员名3;
...
数据类型n 成员名n;
} 结构体变量名, 结构体变量名, ...;
//结构体变量的定义
struct 结构体名 结构体变量名;
//结构体变量的成员的赋值
结构体变量名.成员名1 = 值1;
结构体变量名.成员名2 = 值2;
结构体变量名.成员名3 = 值3;
...
结构体变量名.成员名n = 值n;
//结构体变量的成员的使用
printf("输出结构体变量的成员:%d\n", 结构体变量名.成员名1);
printf("输出结构体变量的成员:%d\n", 结构体变量名.成员名2);
printf("输出结构体变量的成员:%d\n", 结构体变量名.成员名3);
...
printf("输出结构体变量的成员:%d\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代表星期三,以此类推。如果我们用枚举类型来表示星期几,那么我们就可以用MONDAY
、TUESDAY
、WEDNESDAY
、THURSDAY
、FRIDAY
、SATURDAY
、SUNDAY
来表示,这样我们就不需要记住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语言中的功能实现进行介绍。比如运算符、流程控制语句、函数等等。
标签:输出,函数,字符,int,数据类型,基础知识,枚举,printf,C语言 From: https://www.cnblogs.com/BryceAi/p/17749770.html