我的全栈之路-C语言基础之数据存储
- 2.1 计算机的计算单位
- 2.1.1 容量单位
- 2.1.2 速度单位
- 2.2 计算机底层为什么只能识别二进制
- 2.3 进制
- 2.3.1 进制概述
- 2.3.2 十进制转换二进制、八进制、十六进制
- 2.3.3 二进制、八进制、十六进制转十进制
- 2.3.4 二进制转八进制、十六进制
- 2.3.5 八进制、十六进制转二进制
- 2.3 进制
- 2.4 数值在计算机的存取方式
- 2.4.1 数值在计算机的存储方式
- 2.4.2 数值在计算机的获取方式
- 2.5 常量
- 2.5.1 常量概述
- 2.5.2 日常开发中常用的常量
- 2.5.3 const常量
- 2.5.4 define常量
- 2.6 变量
- 2.6.1 变量的概述
- 2.6.2 变量的定义
- 2.6.3 变量的声明
- 2.6.4 变量的使用
- 2.6.5 变量的内存机制
- 2.6.6 变量交换
- 2.7 读取键盘的输入
- 2.8 数据类型
- 2.8.1 数据类型概述
- 2.8.2 sizeof()关键字
- 2.8.3 数据类型的有符号和无符号
- 2.8.3.1 有符号和无符号常量(整型和长整型)
- 2.8.3.2 有符号和无符号变量
- 2.8.4 数据类型的极限
- 2.8.5 数据类型与printf
- 2.8.6 数据在内存中的存储
- 2.9 整型
- 2.9.1 整数常量
- 2.9.2 整数的极限
- 2.9.3 跨平台的整型
- 2.10 浮点型
- 2.10.1 浮点型常量
- 2.10.2 浮点数变量
- 2.10.3 浮点型极限
- 2.10.4 浮点数在内存中的存储
- 2.10.5 浮点数相等性判断
- 2.11 字符型
- 2.11.1 字符型常量
- 2.11.2 字符的本质
- 2.11.3 转义字符
- 2.12 字符串
- 2.13 布尔类型
- 2.14 数据类型转换
- 2.14.1 数据类型自动转换
- 2.14.2 数据类型强制转换
- 2.14.2 数据类型转换的原理
2.1 计算机的计算单位
2.1.1 容量单位
在购买电脑时,通常会选择高配置的内存、硬盘。例如最新款(2019)15寸的MacBookPro已经可以选配32G内存和4T的固态硬盘,而这里的32G和4T就是常用的容量单位。
在物理层面,我们使用高低电平来记录信息,通常使用高电平表示1,低电平表示0,因此在计算机底层只能认识0,1两种状态。而0,1能够表示的内容太少,迫切需要更大的容量表示方法,因此诞生了字节(Byte),千字节(KB),兆字节(MB),吉字节(GB),太字节(TB),拍字节(PB),艾字节(EB),它们之间的换算关系如下所示
1EB=1024PB
1PB=1024TB
1TB=1024GB
1GB=1024MB
1MB=1024KB
1KB=1024B
1Byte=8bit
位(bit)是最小的计算机容量单位,通常用于门电路。
而字节(Byte)、千字节(KB)、兆字节(MB)表示网络传输,文件大小,是最基本的容量计量单位。
吉字节(GB)通常用于表示计算机内存、磁盘的容量单位
太字节(TB),拍字节(PB)通常是用于表示云盘、移动硬盘的容量单位
艾字节(EB)通常是用于表示数据中心的容量单位
现在通常笔记本的内存通常是8G,16G,32G,64G等等,而运行在笔记本之上的操作系统普遍都是64位的,因为32位系统只能使用4G内存,下面是4G的内存换算
4G=2^2 * 2^10 * 2^10 * 2^10 =4*1024*1024*1024=2^32
在购买内存或者买移动硬盘时,通常使用的存储单位就是GB或者是TB,
但是在买4T的移动硬盘时,实际的可用容量却只有3T多,因为计算机的存储单位是以2的10次方(即1024)换算,而硬盘厂商们是以1000为换算单位。
4T的硬盘换算成位如下所示
4T=4*1024GB*1024MB*1024KB*1024B*8bit
而硬盘厂商的实际容量
4T=1000*1000*1000*1000*8
因此实际的可用容量是
4*1000*1000*1000*1000/1024/1024/1024/1024≈3.63T
而在一些互联网巨头(例如国内的BAT,国外的亚马逊、苹果、微软、谷歌,脸书)公司中,可能使用到比TB更大的海量数据,也就是PB或者EB,它们的换算单位如下所示。
1PB=1024TB
1EB=1024PB
2.1.2 速度单位
-
网络速度
网络常用的单位是Mbps
而网络带宽提供商(例如长城宽带)声称的百兆带宽实际上是100Mbit/s,但是100M光纤测试的峰值速度只会有12.5MB/s,它们之间的换算是100Mbit/s=(100/8)MB/s=12.5MB/s。 -
CPU速度
CPU的速度一般是由CPU的时钟频率所体现的,而时钟频率的单位是赫兹(Hz),目前主流的CPU时钟频率一般都在2GHz以上,而赫兹(Hz)其实就是秒分之一,也就是每秒钟的周期性变动重复次数的计量。
GHz即十亿赫兹(10^9Hz),2GHz就是二十亿赫兹,也就是说2GHz的CPU每秒可以变化20亿次。
1Khz=1000hz
1Mhz=1000khz
1Ghz=1000Mhz
2.2 计算机底层为什么只能识别二进制
我们目前主要使用的计算机都是大规模集成电路,是采用大规模和超大规模的集成电路作为逻辑元件的。集成电路按其功能、结构的不同,可以分为模拟集成电路、数字集成电路和数/模混合集成电路三大类。而我们的计算机主要是采用数字集成电路搭建的。逻辑门是数字逻辑电路的基本单元。常见的逻辑门包括“与”门,“或”门,“非”门,“异或”等等。通过逻辑门可以组合使用实现更为复杂的逻辑运算和数值运算。逻辑门可以通过控制高、低电平,从而实现逻辑运算。电源电压大小的波动对其没有影响,温度和工艺偏差对其工作的可靠性影响也比模拟电路小得多,所以相对稳定。因为数字计算机是由逻辑门组成,而逻辑电路最基础的状态就是两个——开和关。所以,数字电路是以二进制逻辑代数为数学基础。二进制的基本运算规则简单,运算操作方便,这样一来有利于简化计算机内部结构,提高运算速度。但是在日常开发中,通常都会使用八进制和十六进制,因为八进制和十六进制相对于二进制表示数据更加简洁,而且一个八进制表示三个二进制,一个十六进制表示四个二进制。例如1024使用二进制表示为0b100 0000 0000,使用八进制表示为02000,使用十六进制表示为0x400。
2.3 进制
2.3.1 进制概述
进制的定义:进制是一种计数方式,也称为进位计数法或者位值计数法,使用有限数字符号表示无限的数值,使用的数字符号的数目称为这种进位制的基数或者底数,例如十进制就是由0-9十个数字组成。在计算机内存中,都是以二进制的补码形式来存储数据的,生活中以十进制方式计算的数据居多,例如账户余额,开发人员的薪水等等。计算的内存地址、MAC地址等等通常都是使用十六进制表示的,Linux系统的权限系统采用八进制的数据表示的。相同进制类型数据进行运算时会遵守加法:逢R进1;减法:借1当R,其中R就表示进制。
计算机常用进制的组成、示例和使用场景:
进制名称 | 组成 | 数值示例 | 应用场景 |
---|---|---|---|
二进制 | 0,1 | 1010 | 计算机底层数据存储 |
八进制 | 0-7之间的8个整数 | 010 | linux权限系统 |
十进制 | 0-9之间的10个整数 | 12 | 整数 |
十六进制 | 0-9,a-f之间的10个整数加上6个字母 | 12f | 数据的内存地址 |
2.3.2 十进制转换二进制、八进制、十六进制
十进制转换二进制、八进制、十六进制可以采用短除法,即待转换的十进制数除以指定的进制(例如2,8,16),直到商数为0,求余数。
十进制101转换为二进制的计算过程
重复除以2 | 商数 | 余数 |
---|---|---|
101/2 | 50 | 1 |
50/2 | 25 | 0 |
25/2 | 12 | 1 |
12/2 | 6 | 0 |
6/2 | 3 | 0 |
3/2 | 1 | 1 |
1/2 | 0 | 1 |
然后将余数的结果从下到上串联起来的结果:1100101,即十进制的101转换为二进制的结果为1100101
十进制的237转换为二进制
重复除以2 | 商数 | 余数 |
---|---|---|
237/2 | 118 | 1 |
118/2 | 59 | 0 |
59/2 | 29 | 1 |
29/2 | 14 | 1 |
14/2 | 7 | 0 |
7/2 | 3 | 1 |
3/2 | 1 | 1 |
1/2 | 0 | 1 |
然后将余数的结果从下到上串联起来的结果:11101101,即十进制的237转换为二进制的结果为11101101。
2.3.3 二进制、八进制、十六进制转十进制
首先明确不同进制的值是如何计算的,这里以十进制和二进制为例子,阐述它们的计算过程。
十进制整数1024
1024=1*10^3+2*10^1+4*10^0=1000+20+4=1024
二进制整数 10000000000
10000000000 =1*2^10=1024
二进制、八进制、十六进制整数转十进制整数是使用按权展开法计算的,这里以二进制数据01100101为例子。从右往左开始数,如果二进制位为1,则依次用1*2^n
,n从0开始。
二进制整数01100101 转换为十进制整数的计算过程
01100101=126+1*25+1*22+1*20=64+32+4+1=101
八进制整数0127转换为十进制整数的计算过程
0127=1*8^2+2*8^1+7=87
十六进制整数0x12f转换为十进制整数的计算过程
0x12f=1*16^2+2*16^1+f*16^0=256+32+15=303
2.3.4 二进制转八进制、十六进制
二进制转八进制是按照从右往左,每3位二进制对应1位八进制,因为2的3次方等于8
二进制整数11001100转八进制计算过程
11 001 100 =0314
二进制转十六进制是按照从右往左,每4位二进制对应1位十六进制,因为2的4次方等于16。
二进制整数1100 1110转十六进制计算过程
1100 1110 =0xce
2.3.5 八进制、十六进制转二进制
八进制转二进制是按照从右往左,每1位八进制对应3位二进制。
八进制整数0127转二进制整数计算过程
0127=001 010 111
十六进制转二进制是按照从右往左,每1位十六进制对应4位二进制。
十六进制整数0x12f转换为二进制整数计算过程
0x12f=0001 0010 1111
2.4 数值在计算机的存取方式
2.4.1 数值在计算机的存储方式
数据在内存中有三种表现方式:原码、反码和补码
但是数据在内存中都是以二进制的补码存储,在理解补码之前首先得理解原码以及反码。
原码就是一个数据本身的二进制表现形式,而数值可以分为有符号和无符号两种。
有符号数其二进制表示最高位(最左边)是符号位,1表示负数,0表示正数。
- 例如无符号数15的原码使用一个字节表示为0000 1111
- 例如有符号15的原码使用一个字节表示为0000 1111
- 例如有符号-15的原码使用一个字节表示为1000 1111
反码
无符号数的反码等于原码
例如无符号数15的反码等于原码,即无符号数15的反码用一个字节表示为0000 1111
有符号数正数的反码也等于原码
例如有符号正数15的反码等于原码,即有符号数15的反码用一个字节表示为0000 1111
有符号负数的反码是原码最高位不变,其他位取反
例如有符号负数-15的反码是原码最高位不变,其他位取反用一个字节表示为1111 0000
补码
无符号数的补码等于反码
例如无符号数15的补码等于反码,即无符号数15的补码使用一个字节表示为0000 1111
有符号正数的补码等于反码
例如有符号正数15的补码等于反码,即有符号正数15的补码使用一个字节表示为0000 1111
有符号负数的补码等于反码加1
例如有符号负数-15的补码等于反码1111 0000加1,即有符号负数15的补码使用一个字节表示为11111 0001
数值 | 是否有符号 | 原码 | 反码 | 补码 |
---|---|---|---|---|
15 | 否 | 0000 1111 | 0000 1111 | 0000 1111 |
15 | 是 | 0000 1111 | 0000 1111 | 0000 1111 |
-15 | 是 | 1000 1111 | 1111 0000 | 1111 0001 |
无符号数以及有符号正数,计算机存储的是原码,因为原码等于反码
有符号负数计算机存储的是补码,补码等于原码取反加1,原码等于补码取反加1
2.4.2 数值在计算机的获取方式
在使用printf函数输出不同格式的数据时
如果是有符号,可以使用 %d, %ld,%lld,%f,%lf获取,因为int,long,long long ,float和double默认就是有符号类型的。
在解析有符号时,首先看内存中的二进制数据最高位是否为1;
如果是1,表示该数字是某个负数的补码,printf输出时还需要转换为原码,因为程序操作的是原码,此时需要根据符号位不变,其他位取反,末位加1。
如果是0,表示该数字是某个正数的补码,由于正数的补码等于原码,此时按照补码输出即可。
如果是无符号,可以使用%u,%lu,%llu,%o,%x获取数据。由于无符号数不需要考虑负数,因此printf()输出数据时按照内存存放的数据原样输出。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
printf格式化输出数据
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/31
*/
int main(int argc, char* argv[])
{
/*
声明占据一个字节的变量number,并赋值为-15
有符号负数,内存存储的是补码
-15的原码是1000 1111
-15的反码是1111 0000
-15的补码是1111 0001
因此内存中存放的是 1111 0001
*/
char number = -15;
/*
有符号输出 1111 0001
首先看最高位,最高位是1,表示该数是一个负数,需要转换成原码输出
补码 1111 0001 转换为原码前首先转换为反码 1000 1110 然后再加1 即1000 1111 按照十进制输出结果就是-15
因为最高位是1,表示该数字是负数,因此输出结果是-15
*/
printf("-15的有符号输出结果是 %d \n",number); //%d按照十进制解析,因此输出结果是-15
/*
无符号输出时不考虑符号位
因为 -15在内存中是使用补码存储,即1111 0001
不考虑符号位输出,结果是241,因为1111 0001 转换为二进制的结果是241
%u按照无符号整数解析,占据四个字节,但是number只占1个字节
但是printf输出时编译器会自动添加24个1, 因此使用按位与去掉编译器自动添加的24个1 number&0x000000ff
*/
printf("无符号输出-15的结果是%u\n",number&0x000000ff);
system("pause");
return 0;
}
如果以十六进制给某个变量赋值,十六进制不区分正负,内存原样存储,不需要考虑原码、反码和补码的换算。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
十六进制赋值输出
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/31
*/
int main(int argc, char* argv[])
{
/*
当给一个变量采用十六进制的值赋值时,内存原样存储,因为十六进制不区分正负
0x9b在内存中的存储 1001 1011
*/
char number = 0x9b;
/*
有符号获取 0x9b
首先将0x9b转换为二进制1001 1011,最高位是1,表示为number的补码
这里需要将1001 1011转换为原码
首先将1001 1011转换为反码 1001 1011 最高位不变,其他位取反 11100100
得到反码后加1,最后的结果是 11100101
一个字节的 1110 0101 最高位是负数 1100101转换为10进制的结果是101
因此最终打印输出number =%d 的结果是-101
*/
printf("16进制0x9b按照有符号输出的结果是 %d \n",number);
/*
无符号数输出时直接输出1001 1011转换为十进制的输出结果即可 1001 1011转换为十进制的结果是155
由于
*/
printf("16进制0x9b按照无符号输出的结果是 %u \n",number&0x000000ff);
// 1001 1011
printf("number = %x \n",number);
system("pause");
return 0;
}
2.5 常量
2.5.1 常量概述
任何基本类型都有变量和常量两种类型。
常量是其值在运行期间不能修改,例如小数3.14,字符'a'都是常量,常量之所以不能被修改,是因为常量是在文字常量区。内存在存储数据时,考虑到数据不同的用途和特点,把内存条分为各种区域:栈区、堆区、代码区、文字常量区、全局区。每个区域的数据类型都有各自的特点。
2.5.2 日常开发中常用的常量
在日常开发中常用的常量有字符常量、短整型常量、长整型常量、单精度浮点型常量以及双精度浮点型常量。
结合C语言提供的printf函数以及格式替换符实现格式化输出常量的值
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
常量
常量一旦定义后不能被修改(赋值)
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//日常开发中常用的常量
//%c 用于输出一个字符,这里输出字符a
printf("char c= %c \n",'a');
//%hd用于输出短整型
printf("short s= %hd \n",10);
//%d用于输出整型
printf("int i= %d \n",100);
//%ld用于输出长整型
printf("long l= %ld \n",10000);
//%lld用于输出长长整型
printf("long ll=%lld \n",100000000000000000);
//%f用于输出单精度浮点型
printf("float f= %f",3.14f);
//%ld用于输出双精度浮点型
printf("double d= %lf",3.14);
//常量一旦定义后不能被修改(赋值)
//10 = 20;
system("pause");
return 0;
}
2.5.3 const常量
C语言中除了字面量常量外,还可以使用const修饰变量,使其成为不能直接修改,即常量,但是可以通过获取变量地址来间接修改。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
const常量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
const int number = 12;
//这里会发生编译错误,因为const修饰的变量是常量,常量一旦定义后无法再次被赋值
//number = 18;
//const虽然无法直接修改,但是可以通过间接的方式依然可以修改
const long id = 10001;
printf("常量 id的内存地址是%p \n", &id);
printf("常量id修改之前的值是%d \n", id);
//通过间接的方式修改id的值
//(int*) 类型转换为非 常量地址
//* 根据地址取出内容
*(int*)(&id) = 10002;
printf("常量id修改之后的值是%d \n", id);
system("pause");
return 0;
}
2.5.4 define常量
C语言中除了使用const定义常量以外还可以使用#define来定义真正意义的常量,因为在C语言层面无法直接或者间接修改其值,因为C语言只能操作内存,不能操作寄存器,而#define定义的常量是在寄存器内部产生的。
在使用#define定义常量的语法为 #define CONST_NAME CONST_VALUE,需要注意的是使用#define定义常量时不需要使用分号。
#define _CRT_SECURE_NO_WARNINGS
//使用 #define定义常量
#define MY_NAME "Tony"
#define MY_GENDER "Boy"
#include
#include
/*
#define常量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//使用常量
printf("my name is %s and my gender is %s \n",MY_NAME,MY_GENDER);
system("pause");
return 0;
}
使用#define定义常量时建议使用有意义的常量名,使得别人在使用常量时能明确知道其含义,常量亦可以在多个方法中使用,如果需要修改常量,只需要修改一次便可以实现批量修改,效率高而且准确。
define常量的应用案例:实现代码混淆
首先定义头文件define.h,其内容为
/*
使用define实现代码混淆
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
#include
#include
#define _ void
#define __ main()
#define ___ {
#define ____ system("notepad");
#define _____ system("pause");
#define ______ }
然后在源文件define.c中使用#include包含define.h并引用define.h头文件中定义的常量值
#include "define.h"
/*
使用define实现代码混淆
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
_ __ ___ ____ _____ ______
然后在Visual Studio 2019中运行该程序,程序能够调用Windows的记事本程序。
2.6 变量
2.6.1 变量的概述
变量就是可以变化的量,在同一时刻,内存中的变量只能存储一份值。如果对变量进行修改,新值会覆盖旧值。
这里可以通过一段程序结合Visual Studio 2019的调试功能理解变量的本质。
#include
#include
/*
变量在内存中的存储
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
int num = 20;
//查看num的内存地址
printf("整数变量num的地址是%p\n", &num);
printf("整数变量num = %d\n", num);
num = 30;
printf("修改之后整数变量num的值是%d\n", num);
system("pause");
return 0;
}
首先通过printf的格式替换符%p获取变量的内存地址,然后通过Visual Studio 2019的调试->窗口->内存菜单查看内存,并在地址栏中输入整数变量num的地址,查看变量值。
Visual Studio 2019中查看内存的值默认是一字节,不带符号显示,可以通过鼠标右键设置成四字节、带符号显示。
然后逐语句执行,并观察变量的值。
生活中随处可见变量,例如股市的涨跌,游戏人物的武力值都是不断变化的。变量在使用前必须要先定义即声明并赋初始值,否则会出现编译错误。定义变量时系统会针对变量的类型开辟指定的内存空间。
2.6.2 变量的定义
变量的定义表示让变量在系统中存在,系统给变量开辟内存空间,变量定义的格式是 类型名+类型名,例如 int number,number就是变量名,类型为int,意味着编译器会针对number变量开辟4个字节的内存空间。
变量名的本质就是空间内容的别名,操作变量就是操作变量代表的那块内存空间。
变量使用前必须赋值完成初始化,即变量定义时就给变量赋值,例如int age=0;
,否则会发生编译错误:error C4700: 使用了未初始化的局部变量
。
在定义变量时,变量名还要遵守以下的命名规则。
- 变量名由字母、数字、下划线组成。
- 变量名不能以数字开头。
- 变量名不能是关键字,关键字是被C语言赋予了特殊的含义,但是可以包含关键字,Visual Studio 2019中蓝色的都是关键字。
#include
#include
/*
变量的声明赋值及其命名规范
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
//合法的标识符
int number;
//见名知意
int age;
char ch;
double db;
//变量名不能是关键字
//int void;
//变量名不能以数字开头
//int 1num;
/****************************************编译器特性*******************************/
//VC支持中文变量,GCC不支持中文命名
int 年龄 = 29;
printf("年龄 =%d\n", 年龄);
//在老版(C++11之前)的编译器中,变量声明必须放在函数调用之前
/****************************************编译器特性*******************************/
//声明多个变量
int one, two, three;
system("pause");
return 0;
}
定义变量时赋值叫变量的初始化,而定义完成以后再赋值不叫初始化,只是单纯的赋值。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的定义
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//变量定义的语法 变量类型 变量名
int number;
//变量使用前必须定义并初始化,初始化即定义时给变量赋值,否则会发生编译错误
//printf("number = %d",number);
//非法的变量名 会造成程序编译错误
// 不能以数字开头
//int 1number;
//只能由字母、数字、下划线组成,这里包含了空格
// int num ber;
//定义时赋值叫变量的初始化
int age = 26;
//变量定义
int val;
//定义完成后再赋值就不叫初始化,只是单纯的赋值
val = 12;
system("pause");
return 0;
}
2.6.3 变量的声明
变量的声明表示告诉编译器该变量已经存在,此处通过编译,但是不会再开辟内存空间。
如果变量定义在使用前面,编译器可以自动识别变量声明,因为编译器在编译时是从上到下逐语句编译。
如果变量的定义不再使用的前面,可以使用extern关键字显示声明变量,在声明时不用赋值,否则会引发变量“重定义,多次初始化的编译错误”。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的声明
变量的声明包含显示声明和非显示声明
一般情况下都是非显示声明,即变量的定义在使用前面。编译器编译时是从上到下逐语句编译的,当变量的定义在使用前面,编译器能够自动识别变量的声明
如果变量的定义在使用的后面,那么此时需要使用extern来显示声明变量,在声明时不用赋值,否则会引发变量“重定义,多次初始化的编译错误
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
//显示声明变量,不用赋值,告诉编译器在使用到该变量的地方编译通过
extern int age;
//extern int age=300; // error C2374: “age”: 重定义;多次初始化
int main(int argc, char* argv[])
{
//编译器从上到下逐行编译,
int number=10;
//变量的定义在使用之前,此处会自动识别变量的声明
printf("number = %d \n",number);
printf("age = %d",age);
system("pause");
return 0;
}
//变量的定义在使用之后
int age = 26;
但是如果最终变量没有定义在使用之后,程序运行时还是会出现异常,例如这里如果注释int age = 26;
就会出现 无法解析的外部符号 age。
日常开发中通常都是先定义再使用变量,而且通常都需要在定义时初始化即赋值,如果使用了一个没有赋值的变量,程序会发生编译错误。
#include
#include
/*
变量在使用前必须初始化赋值,否则会出现C4700错误-> "使用了未初始化的局部变量"
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main() {
//声明一个整数变量并未初始化
int num;
//编译错误错误 C4700 使用了未初始化的局部变量“num”
printf("num =%d\n", num);
system("pause");
return 0;
}
在定义变量时可以针对变量的类型赋对应的初始值,例如整数赋值为0,浮点数赋值为0.0。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量赋初始值
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//变量初始化时如果类型为整数,推荐初始化为0
int number = 0;
double dbl = 0.0;
char c = '\u00';
system("pause");
return 0;
}
2.6.4 变量的使用
变量的使用表示对变量的读写操作,所谓读就是获取变量的值,例如使用printf()输出变量的值,写就是赋值以及各种变量的运算,C语言中使用"="表示赋值,赋值是将右边的值赋值给左边的变量,也就是操作内存空间,除此以外后续还会学习各种运算符,例如算术运算符、逻辑运算符、关系运算符、三元运算符、位运算符等等。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的操作:读&写
打印输出就是读操作
赋值操作就是写操作
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//变量赋值 就是变量的写操作
int age = 26;
//printf函数就是对变量的读操作
printf("age = %d\n ",age);
//=是将右边的值赋值给左边的变量
int my_age = age;
//这里也会打印输出my_age = 26
printf("my_age = %d\n ", my_age);
system("pause");
return 0;
}
变量除了可以赋值以外还可以进行算术运算
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的操作:算术运算
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
int left = 10;
int right = 20;
int result = left + right;
printf("%d + %d = %d \n",left,right, result);
result = right - left;
printf("%d - %d = %d \n", right, left, result);
system("pause");
return 0;
}
需要注意的是变量所有的运算都是通过CPU来完成的。
#include
#include
/*
变量运算的原理
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
//只能给变量赋值
int a = 1;
int b = 2;
//分配四个字节的内存
int c;
printf("变量a的地址是%p\t,变量b的地址是%p\t,变量c的地址是%p\n", &a, &b, &c);
//数据的运算是在CPU的寄存器完成的
c = a + b;
c = b - a;
//对数据的操作是由CPU完成的,因此这里不能直接复制
//a + 1 = 4;
printf("c=%d\n", c);
system("pause");
return 0;
}
使用汇编语言实现变量的赋值以及变量的运算
#include
#include
/*
使用汇编语言实现变量的赋值以及运算来理解数据的运算是在CPU内部的寄存器完成的
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main() {
//申请四个字节的内存
int a;
printf("整数变量a的地址是%p\n", &a);
//变量的赋值都是通过CPU的寄存器来完成的
//这里借助汇编语言实现将10赋值给变量a
_asm {
mov eax, 10
mov a, eax
}
printf("整数变量a的值等于%d\n", a);
_asm {
//把变量a的值赋值给寄存器eax
mov eax, a
//将eax的值加5
add eax, 5
//把eax的值赋值给a
mov a, eax
}
printf("变量a加5之后的结果是%d\n", a);
system("pause");
return 0;
}
在运行程序时。可以通过调试查看寄存器EAX的变化,证明数据的运算都是通过CPU来完成的,然后将运算的结果存储在寄存器中。
2.6.5 变量的内存机制
当声明变量时,编译器会使用变量表维护变量的信息,包含变量名、变量类型以及变量的内存地址。而内存中通过变量地址关联变量值。如果在程序中使用了未声明的变量,则会发生编译错误。
#include
#include
/*
编译器和内存对变量的处理
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
//声明3个整数变量
int a, b, c;
//这里会发生编译错误 不能使用未声明的变量
//printf(" %d\n", d);
system("pause");
return 0;
}
2.6.6 变量交换
变量的交换,可以通过采用中间变量,算术(加减法或者乘除法)运算、异或运算
三种方式实现,其应用场景主要在使用在排序算法中,每种实现变量交换方法的时空复杂度有不同的考量。
通过中间变量实现变量交换
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量交换的三种方式
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/09/03
*/
int main(int argc, char* argv[])
{
int left = 5;
int right = 10;
printf("使用临时变量实现变量交换交换之前\t left=%d \t right=%d\n", left, right);
int middle = left;
left = right;
right = middle;
printf("使用临时变量实现变量交换交换之后\t left=%d \t right=%d\n", left, right);
system("pause");
return 0;
}
通过算术运算实现变量交换
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的交换:通过算术运算实现
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/09/03
*/
int main(int argc, char* argv[])
{
int left = 5;
int right = 10;
printf("使用算术运算实现变量交换交换之前\t left=%d \t right=%d\n", left, right);
left = left + right; // 加号变成乘号
right = left - right;//减号变成除号
left = left - right; //减号变成除号
printf("使用算术运算实现变量交换交换之后\t left=%d \t right=%d\n", left, right);
system("pause");
return 0;
}
使用异或运算实现变量的交换
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
变量的交换:使用异或运算实现变量交换
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/09/03
*/
int main(int argc, char* argv[])
{
int left = 5;
int right = 10;
printf("使用异或运算实现变量交换交换之前\t left=%d \t right=%d\n", left, right);
left = left ^ right;
right = left ^ right;
left = left ^ right;
printf("使用异或运算实现变量交换交换之后\t left=%d \t right=%d\n", left, right);
system("pause");
return 0;
}
2.7 读取键盘的输入
之前在定义整数变量并初始化值时只能写死一个值,整数变量建议初始化时为0,这样可以避免许多不必要的错误出现。
为了让程序变得更加灵活,这里引入C语言标准库函数scanf()函数实现基于终端的人机交互。当然日常应用(例如淘宝、京东)都是基于UI界面实现人机交互,但是底层处理的逻辑是一样的。
scanf()函数可以从键盘中读取用户输入的整数、小数、字符等等,该函数的参数需要传递数据格式和变量地址两个参数,数据格式指的就是用户输入的数据类型,例如整数、小数等等,变量地址就是&变量名
,&表示地址符号,变量名只能代表空间的内容。
scanf()函数是阻塞式的,当用户输入数据之后,数据会存储到标准输入缓存区中,然后scanf()函数负责从标准缓冲区中拿指定格式的数据,如果用户不输入数据,那么程序会一直阻塞在那里。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
scanf函数获取键盘的输入实现终端不带界面交互
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//变量初始化时如果类型为整数,推荐初始化为0
int age = 0;
printf("请输入你的年龄\n(按回车键结束)");
//当用户输入数据并回车之后先到达标准输入缓冲区
//scanf函数负责从标准缓冲区拿整数,此函数是阻塞的,即等到用户输入数据才会往下执行
//%d表示提取键盘输入的数字
//变量名仅仅代表变量的内容,如果想要获取变量的起始地址,需要使用&变量名
scanf("%d",&age);
printf("你的年龄是%d\n",age);
system("pause");
return 0;
}
2.8 数据类型
2.8.1 数据类型概述
数据类型就是给数据分类,其目的就是合理的利用内存空间,提高存储效率。
类型是抽象的概念,类型有大小,但是没有空间,系统不会给类型分配空间,但是会给类型定义的变量分配空间,例如定义变量 int age =28;
时系统会给age变量分配四个字节的空间。
不同的数据类型占据不同的内存大小,其存储数据的极限也不一样、能够执行的运算也是不相同的。
C语言中基本数据类型有整型、浮点型、字符型,布尔型。其他的类型都是由基本数据类型封装而来的。
其中整数按照不同的字节大小有short,int,long,long long(C99支持)。
浮点数按照精度不同有float,double,其中float表示单精度浮点型,double表示双精度浮点型。
字符只有char表示,用于存储单个字符。
布尔使用boolean表示,C语言中的0表示false,非0表示true。
2.8.2 sizeof()关键字
C语言提供了提供了sizeof()关键字来获取类型占据的内存空间。
sizeof()中可以传类型名或者变量名。传递变量名实际上是求变量类型的大小
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
sizeof关键字的使用
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
printf("char 占据的字节数量是%d\n",sizeof(char));
printf("short 占据的字节数量是%d\n",sizeof(short));
printf("int 占据的字节数量是%d\n",sizeof(int));
printf("long 占据的字节数量是%d\n",sizeof(long));
printf("long long 占据的字节数量是%d\n",sizeof(long long));
printf("float 占据的字节数量是%d\n",sizeof(float));
printf("double 占据的字节数量是%d\n",sizeof(double));
printf("bool 占据的字节数量是%d\n",sizeof(bool));
system("pause");
return 0;
}
需要注意的是C语言相同的数据类型在不同的操作系统环境下占据的空间是不一样的。
在Visual Studio 2019中,C程序默认是以32位运行的。
32位sizeof()关键字运算结果
如果想要切换到64位下运行,只需要将X86换成x64即可
在Windows平台下long类型无论是在32位还是64位都是占据四个字节,而Linux(Ubuntu18.04)则是占据8个字节。
Ubuntu18.04下sizeof()关键字测试
//
// Created by guanglei on 8/28/19.
//
#include
#include
int main(){
printf("char 占据的字节数量是%d\n",sizeof(char));
printf("short 占据的字节数量是%d\n",sizeof(short));
printf("int 占据的字节数量是%d\n",sizeof(int));
printf("long 占据的字节数量是%d\n",sizeof(long));
printf("long long 占据的字节数量是%d\n",sizeof(long long));
printf("float 占据的字节数量是%d\n",sizeof(float));
printf("double 占据的字节数量是%d\n",sizeof(double));
printf("_Bool 占据的字节数量是%d\n",sizeof(_Bool));
return 0;
}
2.8.3 数据类型的有符号和无符号
有符号表示数据有正负之分,而无符号则没有正负之分,即全是正数(大于等于0数)。
有符号数最高位(左边第一位)是符号位,如果是0表示该数据是整数,1表示该数据为负数。
以2个字节的数据 1为例子,其二进制表示为0000 0000 0000 0001因为是正数,因此最高位是0,-1的二进制表示为1000 0000 0000 0001
,因为是负数,因此最高位是1。
无符号数即没有符号位,全是数据位,还是以2个字节的数据1为例子,其二进制表示为0000 0000 0000 0001
。
2.8.3.1 有符号和无符号常量(整型和长整型)
整数常量默认是有符号的整数,如果想要变成无符号数,需要在正数后面加上U后缀。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
有符号数(整型,长整型)和无符号数(整型,长整型) 常量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//整型常量10默认是有符号的整数,如果想要变成无符号数,需要在10后面加上U,即10U
printf("%d \n",10);
printf("%u \n", 10U);
//长整型常量的有符号和无符号
//有符号长整型
printf("%ld \n",10L);
//无符号长整型
printf("%lu \n", 10U);
system("pause");
return 0;
}
2.8.3.2 有符号和无符号变量
C语言中的变量默认就是有符号的,使用signed修饰,例如signed int,但是通常直接使用int声明变量,而无符号的变量使用unsigned修饰,例如unsigned int,这里的unsigned不能省略。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
有符号和无符号变量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//整数默认就是有符号的,即默认是使用signed
signed int number = 12;
printf("number = %d ",number);
// 无符号必须加上unsigned
unsigned int value = 12;
printf("value = %u ", value);
system("pause");
return 0;
}
2.8.4 数据类型的极限
数据类型都有其存储范围(即存储的最大值和最小值),C语言中的limits.h和float.h头文件中分别定义了整数和浮点数的极限。在使用数据类型时,切勿超过其极限,否则会造成程序异常。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
/*
数据类型的极限
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
printf("int能存储的最大值是%d\tint能存储的最小值是%d,占据的字节数量是%d\n", INT_MAX, INT_MIN, sizeof(int));
printf("double能存储的最大值是%e\t double能存储的最小值是%e,double占据的字节数量是%d\n \n", DBL_MAX, DBL_MIN,sizeof(double));
printf("unsigned char 表示的最大值是%d\n",UCHAR_MAX);
//无符号的char存储的最大值是255 这里超过了最大范围,输出的结果不是预期的256,,而且0
unsigned char num = 255+1;
printf("num = %d\n",num);
system("pause");
return 0;
}
2.8.5 数据类型与printf
在使用prinf函数打印变量的值时,如果没有正确使用格式转换符,得到一个非预期的结果,因为printf函数不会进行类型转换的操作,只会按照传递的格式转换数据后输出到终端上。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
printf处理数据类型
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]){
int number = 10;
printf("number = %f \n",number);
printf("number = %f \n", (float)number);
float flt = 10.8;
//printf不会进行数据类型转换
printf("flt = %d \n",flt);
//使用强制类型转换实现输出正确的结果
printf("flt = %d \n", (int)flt);
return 0;
}
2.8.6 数据在内存中的存储
在手机、PC上,数据在内存中的存储是低位在低字节,高位在高字节。
而在Unix大型服务器上为了加速寻址,数据在内存中的存储是低位在高字节, 高位在低字节。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
数据在内存中的存储
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
int number = 10;
printf("number的内存地址是%p\n", &number);
printf("number = %d",number);
return 0;
}
2.9 整型
2.9.1 整数常量
C语言整数常量可以使用u后缀表示位无符号整数,使用l后缀表示long类型的整数,使用ll后缀表示为long long类型的整数
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
整数常量的三种类型
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/09/03
*/
int main(int argc, char* argv[])
{
printf("有符号整数常量%d\n",100);
printf("无符号整型常量%u\n",100u);
printf("有符号长整型常量%ld\n",2147483647L);
printf("有符号长长整型常量%lld\n",9223372036854775807LL);
system("pause");
return 0;
}
同时C语言中的整型常量支持八进制、十进制和十六进制三种进制类型,不支持二进制。
- 八进制由0-7之间的八个整数组成,八进制的常量值是以0开头。
- 十进制由0-9之间的十个整数组成。
- 十六进制由0-9,a-f之间的十个整数加上6个字母组成。
printf()函数针对整数的三种进制类型提供了对应的输出格式,其中八进制输出使用%o表示,十进制使用%d表示,十六进制使用%f表示,#表示输出进制的完整格式,例如八进制会在最左边填充一个0,十六进制会在最左边填充0x。
不同进制的输出不会改变数据原来的值,底层依然是以二进制存储,只是输出的表现形式变了。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
printf输出整数的三个进制表现方式
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
int number = 0;
printf("请输入一个整数\n");
scanf("%d",&number);
//不同进制的输出不会修改number的值,只是数据的表现形式变化了
// %o表示按照八进制解析,#表示输出完整进制格式,即以0开头
printf("%d按照八进制输出的结果是%#o\n",number,number);
printf("%d按照十进制进制输出的结果是%d\n",number, number);
// %x表示按照十六进制解析,#表示输出完整进制格式,即以0x开头
printf("%d按照十六进制输出的结果是%#x\n",number, number);
system("pause");
return 0;
}
2.9.2 整数的极限
整数按照占据不同的字节大小可以分为short,int,long和long long 四种类型,它们默认是有符号(signed)类型用于存储正负数,而对应的无符号类型(unsigned)则用来存储非负数的整数。
整数的极限定义以#define的方式在
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
整数的极限
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int int_limits_main(int argc, char* argv[])
{
//16位(嵌入式系统) int和short是等价的
printf("short能存储的最大值是%d\tshort能存储的最小值是%d,占据的字节数量是%d\n", SHRT_MAX, SHRT_MIN, sizeof(short));
printf("unsigned short能存储的最大值是%d\n", USHRT_MAX);
//32位和64位系统 int和long是等价的
printf("int能存储的最大值是%d\tint能存储的最小值是%d,占据的字节数量是%d\n", INT_MAX, INT_MIN, sizeof(int));
printf("unsigned int能存储的最大值是%d\n", UINT_MAX);
//无符号的整数 最小值都是0 即不能表示负数
printf("long能存储的最大值是%d\tlong能存储的最小值是%d,占据的字节数量是%d\n", LONG_MAX, LONG_MIN, sizeof(long));
printf("long long能存储的最大值是%lld\tlong long能存储的最小值是%lld,占据的字节数量是%d\n", LLONG_MAX, LLONG_MIN, sizeof(long long));
printf("unsigned long long 能存储的最大值是%llu\n", ULLONG_MAX);
system("pause");
return 0;
}
在使用整数参与运算时,需要考虑到数据范围对应的极限,否则会发生错误的结果
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
整数的越界
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//为了保证结果运算正确,必须在极限范围之内
unsigned short int shortnum = 65536;
printf("无符号short int所能存储的最大值是%d\n", USHRT_MAX);
printf("shortnum=%d", shortnum); //结果为0 因为chnum所能表示的最大值为65535,这里发生了越界,结果错误
system("pause");
return 0;
}
如果想要存储身份证号等超大类型的整数数据,可以使用无符号的long long类型来存储
#include
#include
/*
long long 的应用场景
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main() {
unsigned long long mobilePhone = 18601767221;
printf("mobilePhone=%llu\n", mobilePhone);
unsigned long long qq = 1079351401;
printf(" qq = %llu", qq);
system("pause");
return 0;
}
2.9.3 跨平台的整型
为了解决不同平台,相同的类型占据的大小不一致的问题,C语言标准委员会在C99标准中提出了跨平台的整数,在
例如int64_t在所有实现C99标准的编译器下占据的都是8个字节,int32_t在所有实现C99标准的编译器下占据的都是4个字节。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
跨平台的整数
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
long val = 100;
printf("windows下long占据的字节数量是%d\n", sizeof(val));
//在不同的平台下占据都是32字节
int32_t int_32_MAX_VALUE = INT32_MAX;
printf("sizeof(int_32_MAX_VALUE ) = %d\n", sizeof(int_32_MAX_VALUE));
printf("int_32_MAX_VALUE = %d\n", int_32_MAX_VALUE);
//在不同的平台下占据都是64字节
int64_t int_64_MAX_VALUE = INT64_MAX;
printf("sizeof(int_64_MAX_VALUE ) = %d\n", sizeof(int_64_MAX_VALUE));
printf("int_64_MAX_VALUE = %lld\n", int_64_MAX_VALUE);
system("pause");
return 0;
}
三角形面积计算:给定三角形的三条边,使用math.h头文件中的sqrt函数实现三角形的面积计算。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
根据给出的边长求面积
使用math.h文件中提供的开平方根函数
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
int a = 6;
int b = 8;
int c = 10;
int p = (a + b + c) / 2;
//sqrt返回float,这里使用赋值运算完成了类型转换
int s = sqrt(p * (p - a) * (p - b) * (p - c));
printf("三角形的面积是%d\n", s);
system("pause");
return 0;
}
2.10 浮点型
2.10.1 浮点型常量
浮点型即生活中使用的小数类型(例如3.14),例如账户的余额,银行的存款利率等等都是浮点型。
C语言中按照精度的不同分别使用float,double和long double表示,默认浮点类型是double,float占据四个字节,double占据8个字节,long double大于等于8个字节,Windows 32位和64位系统long double都是8个字节,Ubuntu18.04系统下long double是占据16个字节。
浮点数的常量可以使用十进制的小数和科学计数法表示,科学计数法可以存储特大或者特小的数字
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
浮点常量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//输出结果显示3.14占据8个字节,因为浮点数默认是double类型
printf("浮点数3.14占据的字节数量是%d\n",sizeof(3.14));
//如果以f结尾的就是float类型
printf("浮点数3.14f占据的字节数量是%d\n",sizeof(3.14f));
//十进制
float flt = 12.0f; //小数后面加f表示float类型
double dbl = 12.0; //小数默认是double类型
//科学计数法
double db1 = 0.12e3;
//e之前必须有数字,指数必须为整数
double db2 = 12000.124e5; //e5表示10的5次方
//%f默认输出小数点后六位
printf("flt = %f \n", flt);
printf("db1 = %f \t db2 = %f\n", db1, db2);
system("pause");
return 0;
}
2.10.2 浮点数变量
在初始化浮点数变量时,默认值建议为0.0或者0.0f,赋值时变量的值和变量的类型保持一致。
printf()函数输出float类型的变量使用格式符%f,输出double类型的变量使用%lf。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
浮点数变量
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//在赋值时尽量保证赋值号(=)左右两边类型一致,如果将表示范围大的值赋值给表示范围小的变量,可能会造成数据溢出,
float flt = 3.14f;
printf("flt = %f \n",flt);
flt = 0.0f;
printf("flt = %f \n", flt);
double dbl = 5.67;
//printf()默认输出小数后6位数
printf("dbl = %lf \n", dbl);
//如果只要输出小数点后2位数,可以使用格式符 %.2lf 实现
printf("dbl = %.2lf \n",dbl);
//请输入一个浮点数
printf("请输入一个浮点数\n");
scanf("%lf",&dbl);
printf("你输入的浮点数是%.2lf \n",dbl);
system("pause");
return 0;
}
2.10.3 浮点型极限
C语言在limits.h的头文件中使用常量定义了float和double以及long double的极限值,我们可以使用sizeof()关键字求出float,double和long double的字节数量以及使用常量
FLT_MAX,FLT_MIN求出float表示的最大值和最小值以及DBL_MAX,DBL_MIN求出double所能表示的最大值和最小值。
在windows上double和long double是等价的,但是在Linux(例如Ubuntu 18.04上)long double是占据16个字节,这也就意味着long double的极限比double更大。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
浮点数占据的内存数量和取值范围
在32位和64位Windows上double 和long double是等价的
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
printf("float占据的字节数量是%d\n", sizeof(float));
printf("float能存储的最大值是%e\t float能存储的最小值是%e \n", FLT_MAX, FLT_MIN);
printf("\n");
printf("double占据的字节数量是%d \n ",sizeof(double));
printf("double能存储的最大值是%e\t double能存储的最小值是%e \n", DBL_MAX, DBL_MIN);
printf("\n");
printf("long double占据的字节数量是%d\n", sizeof(long double));
printf("long double 能存储的最大值是%e\t long ouble能存储的最小值是%e \n", LDBL_MAX, LDBL_MIN);
system("pause");
return 0;
}
2.10.4 浮点数在内存中的存储
首先明确一点,无论是整型、浮点型还是字符等等数据类型在计算机底层都是以二进制的方式存储的。
浮点数在内存中的存储和整数不同,因为整数都可以转换为一一对应的二进制数据。
而浮点数的存储是由符号位(sign)+指数位(exponent)+小数位(fraction)组成。
其中float是由1位符号位+8位指数+23位小数组成,
而double是由1位符号位+11位指数位+52位小数位组成。
int和float同样占据四个字节的内存,但是float所能表示的最大值比int大得多,其根本原因是浮点数在内存中是以指数的方式存储。
我们都知道在内存中,一个float类型的实数变量是占据32位,即32个二进制的0或者1组成
四字节浮点数 最左边的第一位是最高位
0000 0000 0000 0000 0000 0000 0000 0000
从低位依次到高位叫第0位和第31位,这32位可以由三部分组成:
符号位:第31位数表示符号位,如果为0表示整数,如果为1表示负数
指数:第23位到第30位,这8个二进制表示该实数转化为规格化的二进制实数后的指数与127(127即所谓的偏移量)之和所谓阶码,规格化的二进制实数只能在-127-127之间。
小数位:第0位到第22位,最多可以表示23位二进制小数,否则超过了就会产生误差。
2.10.5 浮点数相等性判断
float占据四个字节,提供的有效位是6-7位,而double占据八个字节,提供的有效位数是15-16位,如果在使用float或者double表示实数时超过有效数字,若拿来进行关系运算(例如等于)的话,会得到一个错误的结果。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
浮点数的相等性判断
如果实数超过有效范围,使用==判断会出错
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
float flt1 = 1.00000000001;
float flt2 = 1.00000000000000000001;
//因为float的有效数字是6-7位 这里超出有效数字 计算不准确
printf(" flt1 == flt2 ? %d\n", (flt1 == flt2)); // 输出结果1表示相等 0则表示不相等
//double精确的有效位数是15-16位,这里也超出了有效数字,计算不够正确
double db1 = 1.00000000000000000000000000000001;
double db2 = 1.000000000000000000000000000000000000000000000000000000000000000000000000000000001;
printf(" db1 == db2 ? %d\n", (db1 == db2)); // 输出结果1表示相等 0则表示不相等
system("pause");
return 0;
}
中美GDP计算:给出当前美国和中国的GDP以及增长率,使用math.h的pow函数实现计算出中国GDP超过美国GDP的年份
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
给出当前中国、美国的GDP以及增长率,计算中国超过美国的年份
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//当前中美GDP
double ch_current_gdp = 14.6;
double us_current_gdp = 20.5;
//当前中美GDP的增长率
double ch_rate = 1.06;
double us_rate = 1.04;
double ch_gdp=0.0;
double us_gdp=0.0;
int year=2018;
for (int i = 1; i <= 100; i++) {
//使用pow函数计算中美每年增长后的GDP
ch_gdp = ch_current_gdp * pow(ch_rate, i);
us_gdp = us_current_gdp * pow(us_rate, i);
year++;
printf("%d年中国的GDP是%f\n", year, ch_gdp);
printf("%d年美国的GDP是%f\n", year, us_gdp);
if (ch_gdp > us_gdp) {
printf("在%d年,中国的GDP超越了美国的GDP", year);
break;
}
}
system("pause");
return 0;
}
2.11 字符型
2.11.1 字符型常量
在日常开发应用中,字符是最常用的数据类型。因为生活中的许多数据都是通过字符表示,而不是数字表示,字符能表示更多的含义,最典型的就是网络协议,例如超文本传输协议HTTP协议。
C语言中字符使用一对单引号('')表示,注意单引号只能作用域一个字符。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
字符常量(英文)
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//输出英文字符
printf("输出英文字符%c\n",'a');
printf("char占据的字节数量是%d\n",sizeof(char));
system("pause");
return 0;
}
C语言中的char只占用1个字节,因此不能存储中文,如果想要存储中文,需要使用wchar_t表示,然后还要进行本地化的设置
#define _CRT_SECURE_NO_WARNINGS
#include //引入本地化的头文件
#include
#include
/*
中文字符
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//设置本地化
setlocale(LC_ALL, "chs");
wchar_t ch = L'刘';
//使用wprintf()函数输出中文
wprintf(L"ch = %c \n",ch);
system("pause");
return 0;
}
除了使用prinf函数结合%c输出字符以外,C语言还提供了putchar()函数来输出字符
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
输出字符的两种方式
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
char c = 'A';
putchar(c);
printf("\n");
printf("c = %c \n",c);
system("pause");
return 0;
}
2.11.2 字符的本质
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
字符变量
字符变量的本质存储的是字符的ASC||码值
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//声明并初始化字符变量ch,初始化值为z
char ch = 'z';
printf("char ch = %c \n", ch);
printf("字符变量%c对应的整数是%d \n",ch,ch);
printf("char占据的字节大小是 %u \n",sizeof(ch));
system("pause");
return 0;
}
由于计算机最底层只能识别二进制的数据,但是字符不是二进制的数据。
如果将字符和特定的数值一一对应起来,这张对应表就是ASC||表。
如果字符变量ch存储的字符是'z',那么实际上存储的是字符z对应的ASC||值即整数122。即字符变量存储的本质就是存储的字符对应的ASC||值
日常开发中最常用的字符就是大小写字母以及数字字符
我们可以借助printf()函数的格式%c和%d实现输出字符对应的ASC||值。
数字字符'0'表示的整数是48,小写字符'a'表示的整数是97,大写字符'A'表示的整数时65。数字0表示空字符。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
常用的字符对应的ASC||表
输出字符使用%c,输出ASC||值使用%d
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
char ch = '0';
printf("字符%c对应的整数是%d\n", ch, ch);
ch = 'a';
printf("字符%c对应的整数是%d\n", ch, ch);
ch = 'A';
printf("字符%c对应的整数是%d\n", ch, ch);
int number = 97;
printf("数字%d对应的字符是%c\n", number, number);
number = 65;
printf("数字%d对应的字符是%c\n", number, number);
number = 48;
printf("数字%d对应的字符是%c\n", number, number);
system("pause");
return 0;
}
既然字符变量的本质存储的ASC||值,即整数。因此字符也可以参与加减运算。
由于ASC||码规定了小写字母''a' 表示97,然后一次递增,小写字母'z'表示122,而大写字母'A'表示65,然后依次递增,大写字母'Z'表示90。因此根据这个规律可以通过加减运算实现大小写字母转换。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
字符的大小写转换
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//小写转大写
char lower_ch = 'a';
// 0x20就是十进制的32
char upper_ch = lower_ch - 0x20;
printf("小写字母%c转换为大写字母的结果是%c \n",lower_ch,upper_ch);
//大写转小写
upper_ch = 'A';
lower_ch = upper_ch + 32;
printf("大写字母%c转换为小写字母的结果是%c \n", upper_ch, lower_ch);
system("pause");
return 0;
}
2.11.3 转义字符
一些特殊的符号无法直接显示时,我们使用\特殊字符
来表示。
例如\a表示发声,无法在终端上直接显示。
在日常开发中还会使用到各种常用的转义字符,例如\t实现tab的效果。
转义字符\n实现换行的效果,转义字符\实现路径转义。
%%输出百分号。
转义字符还可以使用八进制和十六进制表示,用于表示字符对应的ASC||码值。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
转义字符
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//声音无法输出在屏幕上显示,因此这里使用转字符\a实现发声
printf("%c",'\a');
//日常开发中常用的转移字符 \n实现换行
printf("hello \n");
printf("world \n");
// \t 实现tab效果
printf("C\t语\t言\t核\t心\t编\t程\n");
// \r 实现光标移到行首 由于光标移到行首,最终只会输出C语言核心编程
printf("CPrimerPlus%cC语言核心编程\n",'\r');
//路径转义 \\表示路径转义
//使用system函数使用电脑上的微信,调用成功的前提条件是电脑上安装了微信
system("\"C:\\Program Files (x86)\\Tencent\\WeChat\\WeChat.exe\"");
// %%实现输出百分号
printf("合格率为%%%d\n",90);
//八进制转义和十六进制转义
//八进制的62转换为十进制是50,50表示字符2
char ch = '\62';
printf("八进制的转义字符62转换为字符的结果是%c \n",ch);//2
//十六进制的62转换为十进制是98,98表示的字符是b
ch = '\x62';
printf("十六进制的转义字符62转换为字符的结果是%c\n", ch); //b
system("pause");
return 0;
}
2.12 字符串
字符串用于表示字符序列,也就是一串使用" "
包含起来的内容。
C语言中的字符串以\0结尾,这也就是意味着即使双引号""中什么都没有也会占据一个字节,而中文字符串中的每个字符同样会占据两个字节
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
C语言字符串以\0结尾
空字符串也会占据一个字节
中文占据两个字节
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//字符串是以/0结尾,这里字符串A占据2个字节
printf("字符串A占据的字节数量是%d\n", sizeof("A"));
//空字符串也是以\0结尾,因此这里占据一个字节
printf("\"\"占据的字节数量为%d\n", sizeof("")); //以\0结尾
//字符串单个中文占据两个字节
printf("字符串刘光磊占据的字节数量是%d\n", sizeof("刘光磊")); //每个中文占据两个字节,然后以\0结尾 因此是7个
system("pause");
return 0;
}
使用算术运算实现字符串加密解密
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
字符串加密解密简单实现
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
//使用字符数组初始化一个字符串 calc
char str[5] = { 'c','a','l','c','\0' };
//调用计算器
system(str);
printf("加密之前str = %s\n", str);
//循环 将字符+1 因为字符的本质就是ASC||码 因此可以当作整数来使用
for (int i = 0; i < 4; i++) {
str[i] += 1;
}
printf("加密之后str = %s\n", str);
//逆运算解密
for (int i = 0; i < 4; i++) {
str[i] -= 1;
}
printf("解密之后str = %s\n", str);
system("pause");
return 0;
}
2.13 布尔类型
bool类型只有两个值,即true和fasle,它们在内存中分别使用1和0表示,这样一个字节便可以存储bool类型的变量。
在C程序中使用bool类型的变量,需要引入头文件stdbool.h,后续的各种逻辑、关系运算以及选择结构if/else和while循环会大量使用bool类型的变量。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
/*
bool类型的使用
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[])
{
bool flag = true;
//占据的字节数量为1
printf("bool占据的字节数量是%d\n", sizeof(flag));
//成立的结果为1
printf("bool = %d\n", flag);
flag = false;
//不成立的结果为0
printf("bool = %d\n", flag);
//bool在if/else结构和关系运算的使用
int age = 20;
bool isAdult = age > 18;
if (isAdult==1) {
printf("你成年了\n");
}
else {
printf("你没有成年\n");
}
system("pause");
return 0;
}
2.14 数据类型转换
2.14.1 数据类型自动转换
当多种数据类型(整数、浮点数、字符)同时参与运算时,会发生自动类型转换,容量小的数据类型的变量与容量大的大数据类型的变量做运算时,结果自动提升为容量大的数据类型,防止运算时超过极限值,防止运算结果的精度丢失。此时容量大小指的是,数据类型表示数的范围大小,而不是占用内存大小,比如float容量大于long的容量。
自动类型转换的规则如下
char,short->int->long->float->double->long double
其中对应的有符号类型还会自动转换为无符号类型,char和char运算,short和short运算也会转换为int。
char和int,double的自动类型转换
#include
#include
/*
自动类型转换
在进行算术运算时,会发生自动类型转换 表示范围小的值自动转换为表示范围大的变量,保存精度
char->short>int->long->float->double->long double
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int type_convert_auto_main(int argc, char* argv[]) {
char ch = 'A';
printf("1.0占据的字节数量是%d\n", sizeof(1.0));
//ch+1自动转换为int类型,因此占据四个字节
printf("字符变量ch+1的字节数量是%d\n", sizeof(ch + 1));
printf("字符变量ch+1.0的字节数量是%d\n", sizeof(ch + 1.0));
getchar();
return 0;
}
int和unsigned int的自动类型转换
#include
#include
/*
自动类型转换 有符号类型int转换为无符号类型int
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
int number1 = -10;
unsigned int number2 = 5;
//有符号数和无符号数参与运算时,先将有符号数转换为无符号数
printf("number1按照无符号输出结果为%u\n",number1);
int result = number1 + number2;
//使用三目运算和算术运算输出表达式number1+number2大于0还是小于0
//因为number1按照无符号的结果是4294967286 因此结果是大于0
number1 + number2 > 0 ? printf("number1 + number2 > 0 \n"):printf("number1 + number2 < 0 \n");
printf("按照有符号整数输出 result = %d \n", result);
printf("按照无符号整数输出 result = %u \n", result);
system("pause");
return 0;
}
char、short的自动类型转换:由于char,short占据的内存空间过小,编译器规定,只要是char,short参与运算,都会自动转换为int类型。
#include
#include
/*
char,short的自动类型转换
char和short参与运算都会转换为int
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
char x = 10;
int target = x + x;
short s = 20;
int short2 = s + s;
int result = x + s;
system("pause");
return 0;
}
2.14.2 数据类型强制转换
强制类型转换就是在待转换的表达式的左边使用强转符(目标类型)实现
#include
#include
/*
强制类型转换
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
float fl = 10.8;
float flt = 10.3;
int num = (int)fl + flt; //20.3 先把fl强制转换为int类型,然后再和flt相加,再转换为int,因为赋值会进行自动类型转换
printf("num =%d\n", num);
num = (int)(fl + flt);//21 先把fl和flt相加后,强制转换为int
printf("num =%d\n", num);
getchar();
return 0;
}
强制类型转换只是临时改变变量的值
#include
#include
/*
强制类型转换的特性
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main() {
float flt = 3.14f;
int number = 0;
//强制类型转换只是临时改变变量flt的值,其本质是读取flt的值,然后强制转换为int类型的值
number = (float)flt;
//number =3
printf("number = %d", number);
// flt=3.14
printf("flt = %f", flt);
getchar();
return 0;
}
在使用printf函数打印输出变量值时,不会进行自动类型转换,如果想要获取预期的结果,需要使用强制类型转换实现。
#include
#include
/*
printf()函数与强制类型转换
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main() {
//因为printf函数不会进行类型转换,所以这里得到一个错误的结果
printf("%d\n", 12.1);
//12.1为浮点类型,这里使用强制类型转换实现转换为整数
printf("%d\n", (int)12.1);
printf("%f\n", 10); //整数按照浮点数解析,得到的结果就是0.000000
printf("%f\n", (float)10); //强制类型转换
getchar();
return 0;
}
强制类型转换案例:实现对小数点后三位实现四舍五入
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
实现对小数点后三位实现四舍五入
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
printf("请输入四舍五入的三位小数\n");
double input = 0.0;
scanf("%lf", &input);
double val = 1.235;
//1.234*100=123.4 123.4+0.5=123 123/100.0=1.23
// 1.235*100=123.5 123.5+0.5=124 124/100=1.24
// 1.24>1.235
// 1.24-1.235=0.05
//1.235+0.05=1.24
double result = (int)(input * 100 + 0.5) / 100.0;
printf("result =%.2f", result);
getchar();
return 0;
}
强制类型转换案例:账户余额的分大于4分就偷钱
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
实现偷钱程序
如果账户余额的分大于等于4分就不偷钱,小于等于3分就偷走
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int main(int argc, char* argv[]) {
printf("请输入你的账户余额\n");
double balance = 0.0;
scanf("%lf", &balance);
// 12.34*10=123.4 123.4+0.6=124 124/10.0=12.4 12.4>12.34
double rest = (int)((balance * 10) + 0.6) / 10.0;
printf("rest = %f", rest);
if (rest < balance) {
//
printf("可以偷钱%.2f元", balance - rest);
}
getchar();
return 0;
}
2.14.2 数据类型转换的原理
当在进行数据类型转换时,如果该数据是有符号的,在进行数据类型转换时按照符号位数来填充,如果是无符号则按照0来填充。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
/*
数据类型转换的内存原理
有符号 低字节转高字节按照符号位数填充
无符号 低字节转高字节按照0填充
@author liuguanglei [email protected]
@website ittimeline.net
@version 2019/08/28
*/
int convert_main(int argc, char* argv[]) {
//正数按照0填充
char ch = 1; // 二进制表示 0000 0001
int num = 1; // 二进制表示 0000 0000 0000 0000 0000 0000 0000 0001
//负数按照1填充
// 二进制表示 原码 1000 0001 反码 1111 1110 补码 1111 1111 ->ff
ch = -1;
// 二进制表示 原码 1000 0000 0000 0000 0000 0000 0000 00001
// 反码 1111 1111 1111 1111 1111 1111 1111 1110
// 补码 1111 1111 1111 1111 1111 1111 1111 1111 -> ffffffff
num = ch;
unsigned char data = 255 + 1; // 二进制补码 1 0000 0000 但是char只能占据8位,因此这里会截取8位即0000 0000,结果位0
printf("unsigned char data的地址是%p", &data);
printf("data = %d", data);
unsigned int u_num = -1; //赋值错误,能编译不意味着结果正确
// 1000 0000 0000 0000 0000 0000 0000 0000 0001
// 1111 1111 1111 1111 1111 1111 1111 1111 1110
// 1111 1111 1111 1111 1111 1111 1111 1111 1111 无符号解析结果为2的32次方即4294967295
for (int i = 0; i < u_num; i++) {
system("mspaint");
}
getchar();
return 0;
}