首页 > 编程语言 > 详细

C语言核心技术-变量和数据类型

时间:2020-05-27 16:09:00      阅读:50      评论:0      收藏:0      [点我收藏+]

C语言核心技术-变量和数据类型

2.1 计算机的计算单位

2.1.1 容量单位

在购买电脑时,通常会选择高配置的内存、硬盘。例如2019款16寸的MacBookPro已经可以选配64G内存和4T的固态硬盘。
技术分享图片
而这里的32G和8T就是计算机常用的容量单位。

在物理层面,我们使用高低电平来记录信息,通常使用高电平表示1,低电平表示0,因此在计算机底层只能认识0,1两种状态。而0,1能够表示的内容太少,迫切需要更大的容量表示方法,因此诞生了字节(Byte),千字节(KB),兆字节(MB),吉字节(GB),太字节(TB),拍字节(PB),艾字节(EB),除了bit和Byte之外,从Byte到KB,MB,GB,TB,PB,EB,它们的换算都是以2的10次方即1024换算的。

1YB=1024ZB
1ZB=1024EB
1EB=1024PB
1PB=1024TB
1TB=1024GB
1GB=1024MB
1MB=1024KB
1KB=1024B
1Byte=8bit
  • 位(bit)是最小的计算机容量单位,通常用于门电路,只能存储0或者1
  • 字节(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/10243.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 计算机常用进制及其转换

2.2.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.2.2 计算机底层为什么只能识别二进制

我们目前主要使用的计算机都是大规模集成电路,是采用大规模和超大规模的集成电路作为逻辑元件的。集成电路按其功能、结构的不同,可以分为模拟集成电路、数字集成电路和数/模混合集成电路三大类。而我们的计算机主要是采用数字集成电路搭建的。逻辑门是数字逻辑电路的基本单元。常见的逻辑门包括“与”门,“或”门,“非”门,“异或”等等。通过逻辑门可以组合使用实现更为复杂的逻辑运算和数值运算。逻辑门可以通过控制高、低电平,从而实现逻辑运算。电源电压大小的波动对其没有影响,温度和工艺偏差对其工作的可靠性影响也比模拟电路小得多,所以相对稳定。因为数字计算机是由逻辑门组成,而逻辑电路最基础的状态就是两个——开和关。所以,数字电路是以二进制逻辑代数为数学基础。二进制的基本运算规则简单,运算操作方便,这样一来有利于简化计算机内部结构,提高运算速度。但是在日常开发中,通常都会使用八进制和十六进制,因为八进制和十六进制相对于二进制表示数据更加简洁,而且一个八进制表示三个二进制,一个十六进制表示四个二进制。例如1024使用二进制表示为0b100 0000 0000,使用八进制表示为02000,使用十六进制表示为0x400

2.2.3 二进制运算的基础

首先明确不同进制的值是如何计算的,这里以十进制和二进制为例子,阐述它们的计算过程。

十进制1024

1024=1*10^3+2*10^1+4*10^0=1000+20+4=1024

二进制的0b10000000000

0b10000000000 =1*2^10=1024

2.2.4 十进制转换二进制、八进制、十六进制

十进制转换二进制、八进制、十六进制可以采用短除法,即待转换的十进制数除以指定的进制(例如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/8 29 5
29/8 3 5
3/8 0 3

然后将余数的结果从下到上串联起来的结果:355,即十进制的237转换为二进制的结果为355。

2.2.5 二进制、八进制、十六进制转十进制

二进制、八进制、十六进制整数转十进制整数是使用按权展开法计算的,这里以二进制数据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.2.6 二进制转八进制、十六进制

二进制转八进制是按照从右往左,每3位二进制对应1位八进制,因为2的3次方等于8

二进制整数11001100转八进制计算过程

11 001 100 =0314

二进制转十六进制是按照从右往左,每4位二进制对应1位十六进制,因为2的4次方等于16。

二进制整数1100 1110转十六进制计算过程

1100 1110 =0xce

2.2.7 八进制、十六进制转二进制

八进制转二进制是按照从右往左,每1位八进制对应3位二进制。

八进制整数0127转二进制整数计算过程

0127=001 010 111

十六进制转二进制是按照从右往左,每1位十六进制对应4位二进制。

十六进制整数0x12f转换为二进制整数计算过程

0x12f=0001 0010 1111

2.2.8 二进制转十进制(小数)

二进制的小数转换为十进制的小数也是使用按权展开法计算的,只不过首先是从左向右数,从2-1依次递减开始计算的,2-1就是除以2,2^-2就是除以4,依次类推。

例如小数0.11001转换为十进制的计算过程如下所示。

0.11001=1*2^-1+1*2^-2+1*2^-5=0.5+0.25+1/32=0.75+0.03125=0.78125=25/32

2.2.9 十进制转二进制(小数)

十进制的小数转换为二进制的小数使用重复相乘法来计算的。

下面以一个小数0.78125为例子,介绍转换二进制的过程。
0.78125转换为分数的形式就是25/32

重复乘以2 取1
25/32 50/32=1+9/16 1
9/16 18/16=1+1/8 1
1/8 2/8=0+1/4 0
1/4 2/4=0+1/2 0
1/2 2/2=1+0 1

然后再将取1的结果从上到下串联起来,即0.78125转换为二进制的结果是0.11001

再计算一个小数0.34375,转换为分数形式为11/32,计算步骤如下所示

重复乘以2 取1
11/32 22/32=0+11/16 0
11/16 22/16=1+3/8 1
3/8 6/8=0+3/4 0
3/4 6/4=1+1/2 1
1/2 2/2=1+0 1

然后再将取1的结果从上到下串联起来,即0.34375转换为二进制的结果为0.01011。

2.3 数据类型

2.3.1 数据为什么要分类

数据类型就是给数据分类,其目的就是合理的利用计算机的内存空间,提高存储效率。
类型是抽象的概念,类型有大小,但是没有空间,系统不会给类型分配空间,但是会给类型定义的变量分配空间,例如定义变量 int age =28;时系统会给age变量分配四个字节的空间。
不同的数据类型占据不同的内存大小,其存储数据的极限也不一样、能够执行的运算也是不相同的。

2.3.2 C语言常用基本数据类型

C语言中基本数据类型有整型、浮点型、字符型,布尔型。其他的类型都是由基本数据类型封装而来的。

其中整数按照不同的字节大小有short,int,long,long long,其中long long 是C99标准支持。
浮点数按照精度不同有float,double以及long double,其中float表示单精度浮点型,double表示双精度浮点型。
字符只有char表示,用于存储单个字符。
布尔使用bool表示,C语言中的0表示false,1表示true。

2.3.3 sizeof

C语言提供了提供了sizeof()关键字来获取数据类型占据的内存空间。
sizeof()中可以传数据类型名或者变量名,当传递变量名实际上是求变量类型的大小。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/*
	sizeof运算符
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//使用printf函数输出各个基本数据类型占据的字节数量
	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中集成了32位编译器和64位编译器,C程序默认是以32位运行的。
技术分享图片

Windows 32位:sizeof()关键字的运行结果
技术分享图片

如果想要切换到64位下运行,只需要将X86换成x64即可
技术分享图片
Windows 64位:sizeof()关键字的运行结果
技术分享图片

在Windows平台下long类型无论是在32位还是64位都是占据四个字节,而Linux(Ubuntu18.04)则是占据8个字节。

Ubuntu18.04下sizeof()关键字测试

在Windows下long double占据的字节数为8,而在Ubuntu 18.04下long double占据16位字节。

//
// Created by guanglei on 8/28/19.
//
#include <stdio.h>
#include <float.h>
#include <stdbool.h>
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("long double 占据的字节数量是%d\n",sizeof(long double));
    printf("bool 占据的字节数量是%d\n",sizeof(bool));

    return 0;
}

程序运行结果
技术分享图片

2.3.5 数据类型的极限

数据类型都有其存储范围(即存储的最大值和最小值),C语言中的limits.h和float.h头文件中分别定义了整数和浮点数的极限。在使用数据类型时,切勿超过其极限,否则会造成程序异常。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>
/*

	数据类型的极限
		数据类型都有其存储范围(即存储的最大值和最小值),C语言中的limits.h和float.h头文件中分别定义了整数和浮点数的极限。在使用数据类型时,切勿超过其极限,否则会造成程序异常。

	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
    @version 2019/12/17

*/
int main(int argc, char* argv[])
{
	printf("int能存储的最大值是%d\tint能存储的最小值是%d,占据的字节数量是%d\n \n", INT_MAX, INT_MIN, sizeof(int));
	printf("double能存储的最大值是%e\t double能存储的最小值是%e,double占据的字节数量是%d\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.4 常量

任何基本类型都有变量和常量两种类型
常量是其值在程序运行期间不能修改,例如小数3.14,字符‘a‘都是常量,常量之所以不能被修改,是因为常量是在文字常量区。
内存在存储数据时,考虑到数据不同的用途和特点,把内存存储区域分为各种区域:栈区、堆区、代码区、文字常量区、全局区。每个区域的数据类型都有各自的特点。

在日常开发中常用的常量有字符常量、短整型常量、整型常量、长整型常量、单精度浮点型常量以及双精度浮点型常量。

结合C语言提供的printf函数以及格式符实现格式化输出常用基本数据类型的常量值

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    printf输出常用基本数据类型的常量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/17
*/
int main(int argc, char* argv[])
{
	//%c 用于输出一个字符,这里输出字符a 因此完整的输出内容为char 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用于输出单精度浮点型 3.14后的f表示将3.14当成单精度浮点数处理
	printf("float f = %f", 3.14f);
	//%ld用于输出双精度浮点型
	printf("double d = %lf", 3.14);
	system("pause");
	return 0;
}

除了字面量常量以外,C语言中还可以使用#define const_name const_value的方式来定义常量,该常量可以在多个方法中使用,提高代码的复用率。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define data 10
/*
	使用#define常量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/02
*/
void use_data() {
	//使用#define定义的常量可以在多个方法中使用
	printf("use_data() data = %d \n",data);
}

int main(int argc, char* argv[])
{	
	//使用#define定义的常量可以在多个方法中使用
	use_data();
	printf("main() data = %d \n", data);

	system("pause");
	return 0;
}

使用#define常量实现代码混肴

定义头文件define_code.h

#pragma once
#define _ int
#define __ main
#define ___ ()
#define ____ {
#define _____  printf("Windows10 1903 & Visual Studio 2019 Hello World With C \n");
#define ______ system("pause");
#define _______ return 0;
#define ________ }

定义源文件define_code.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "define_code.h"
/*
	define常量
	使用define实现代码混淆
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/02
*/
_ __ ___ ____ _____ ______ _______ ________

当运行程序时,控制台会输出Windows10 1903 & Visual Studio 2019 Hello World With C并换行。

2.5 变量

2.5.1 变量的本质

生活中随处可见变量,例如股市的涨跌,游戏人物的武力值都是不断变化的。
变量就是程序运行时其值可以被修改,而且在定义变量时系统会根据变量类型开辟内存空间,变量可以进行读(取值)写(赋值)操作,在同一时刻,内存中的变量只能存储一份值。如果对变量进行修改,新值会覆盖旧值。

这里可以通过一段程序结合Visual Studio 2019的调试功能查看内存中值的变化理解变量及其读写数据本质。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	变量在内存中的存储以及读写操作的本质

	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/17
*/
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");
	system("pause");
	return 0;
}

运行程序前,首先设置断点。
技术分享图片
然后启动程序
查看num的初始值为-858993460
技术分享图片
然后按F11查看控制台输出num的变量地址
然后通过Visual Studio 2019的调试->窗口->内存菜单查看内存,并在地址栏中输入整数变量num的地址,查看变量值。Visual Studio 2019中查看内存的值默认是一字节,不带符号,十六进制显示。因此20在内存中的表示结果为14
技术分享图片
可以通过鼠标右键设置成四字节、带符号显示。
技术分享图片
当程序执行num=30时,内存中的值被修改了
技术分享图片
程序运行结果
技术分享图片

在学习变量时需要掌握四点,分别是变量的定义,变量的初始化,变量的声明,变量的使用

2.5.2 变量的定义

变量在使用前必须要先定义即声明并赋初始值,否则会出现编译错误。定义变量时系统会针对变量的类型开辟指定的内存空间。

变量定义的目的是让变量在系统中存在,变量定义后系统给变量开辟内存空间,变量定义的格式是 类型名+类型名,例如 int number,number就是变量名,类型为int,意味着编译器会针对number变量开辟4个字节的内存空间。
变量名的本质就是空间内容的别名,操作变量就是操作变量代表的那块内存空间。

在定义变量时,变量名还要遵守以下的命名规则。

  • 变量名由字母、数字、下划线组成。
  • 变量名不能以数字开头。
  • 变量名不能是关键字,关键字是被C语言赋予了特殊的含义,但是可以包含关键字,Visual Studio 2019中蓝色的都是关键字。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	变量的命名规范
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
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;
}

2.5.3 变量的初始化

变量使用前必须赋值完成初始化,即变量定义时就给变量赋值,例如int age=0;,否则会发生编译错误:error C4700: 使用了未初始化的局部变量。
定义变量时赋值叫变量的初始化,而定义完成以后再赋值不叫初始化,只是单纯的赋值。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    变量的初始化:定义时赋值就是变量的初始化
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//变量定义的语法  变量类型 变量名
	int number;
	//变量使用前必须定义并初始化,初始化即定义时给变量赋值,否则会发生编译错误
	//printf("number = %d",number);


	 //定义时赋值叫变量的初始化
	int age = 26;

	//变量定义
	int val;
	//定义完成后再赋值就不叫初始化,只是单纯的赋值
	val = 12;
	system("pause");
	return 0;
}

在定义变量时可以针对变量的类型赋对应的初始值,例如整数赋值为0,浮点数赋值为0.0,字符类型可以初始化为‘\u00‘。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	基本数据类型变量的初始化
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18

*/
int main(int argc, char* argv[])
{
	//变量初始化时如果类型为整数,推荐初始化为0
	int number = 0;
	double dbl = 0.0;
	//字符型推荐初始化为\u00
	char c = ‘\u00‘;

	printf("c=%c\n",c);

	system("pause");
	return 0;
}

日常开发中通常都是先定义再使用变量,而且通常都需要在定义时初始化即赋值,如果使用了一个没有赋值的变量,程序会发生编译错误。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    变量使用的注意事项
	变量在使用前必须初始化赋值,否则会出现C4700错误-> "使用了未初始化的局部变量"
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//声明一个整数变量并未初始化
	int num;
	//编译错误错误	C4700	使用了未初始化的局部变量“num”
	printf("num =%d\n", num);
	system("pause");
	return 0;
}

2.5.4 变量的声明

变量的声明表示告诉编译器该变量已经存在,此处通过编译,但是不会再开辟内存空间。
变量的声明分为自动识别显示声明两种,日常开发中通常使用自动识别声明变量。
如果变量定义在使用前面,编译器可以自动识别变量声明,因为编译器在编译时是从上到下逐语句编译。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    声明变量-自动识别
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//编译器从上到下逐行编译,
	int number = 10; // 变量的定义
	//变量的定义在使用之前,此处会自动识别变量的声明
	printf("number = %d \n", number);//变量的使用
	system("pause");
	return 0;
}

如果变量的定义不再使用的前面,可以使用extern关键字显示声明变量,在声明时不用赋值,否则会引发变量“重定义,多次初始化的编译错误”。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
    声明变量-显示化声明变量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/

extern int age;

int main(int argc, char* argv[])
{
	printf("age = %d \n", age);
	system("pause");
	return 0;
}
//变量的定义在使用之后
int age = 26;

但是如果最终变量没有定义在使用之后,程序运行时还是会出现异常,例如这里如果注释int age = 26;就会出现 无法解析的外部符号 age。

2.5.5 变量的使用

变量的使用表示对变量的读写操作,所谓读就是获取变量的值,例如使用printf()输出变量的值,写就是赋值以及各种变量的运算,C语言中使用"="表示赋值,赋值是将右边的值赋值给左边的变量,也就是操作内存空间。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	变量的读写操作
	打印输出就是读操作
	赋值操作就是写操作
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
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 <stdio.h>
#include <stdlib.h>
/*

	变量的算术运算
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
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;
}

2.5.6 变量交换的时空复杂度分析

变量的交换在后期的排序算法等应用场景中会经常使用到。
C语言的变量交换可以分别使用 中间变量、算术运算和异或运算三种方式实现。
时空复杂度就是讨论算法所耗用的时间和空间,时空复杂度也被用来算法的优缺点。

在具体实现的变量交换之前,首先实现一个方法,该方法用于打印输出两个待交换变量的参数值。

需要注意的是改方法如果在别处(例如swap_with_temp()方法)被调用,必须要声明或者实现在调用(例如swap_with_temp()方法)之前。否则程序会编译错误。

/*
	打印输出两个整数参数的值
*/
void println(int left, int right) {
	printf("left = %d right = %d\n", left, right);
}

使用中间变量实现变量交换

/*
	使用中间变量实现变量交换
	时空复杂度分析
	缺点:需要开辟新的内存空间
	优点:完成一次变量交换只需要执行三次赋值运算
*/
void swap_with_temp() {
	int left = 10;
	int right = 20;
	printf("通过中间变量实现变量交换变量交换之前:");
	println(left, right);

	int temp = left; // left=10 temp=10
	left = right; //right=20 left=20
	right = temp;// temp=10 right=10
	printf("通过中间变量实现变量交换变量交换之后:");
	println(left,right);
}

使用算术运算实现变量交换

/*
	使用算术运算实现变量交换
	时空复杂度分析
	优点:不需要引入中间变量,节省内存。算术运算还需要考虑数据越界的情况
	缺点:需要通过三次算术运算和三次赋值运算实现变量交换,相比使用中间变量而言计算次数多。
*/
void swap_with_arithmetic() {
	int left = 10;
	int right = 20;
	printf("通过算术运算实现变量交换之前:");
	println(left,right);
	left = left + right; // left=30 right=20
	right = left - right;// right=10 left=30
	left = left - right;// left=20 right=10

	printf("通过算术运算实现变量交换之后:");
	println(left, right);
}

使用异或运算实现变量交换

/*
	使用异或运算实现变量交换
	时空复杂度分析
	优点:不需要考虑算术运算的数据越界问题,也没有使用中间变量
	缺点:需要使用三次异或运算和三次赋值运算
*/
void swap_with_xor() {
	int left = 10;
	int right = 20;
	printf("通过异或运算实现变量交换之前:");
	println(left, right);
	left = left ^ right; // left=30 right=20
	right = left ^ right;// right=10 left=30
	left = left ^ right;// left=20 right=10

	printf("通过异或运算实现变量交换之后:");
	println(left, right);
}

然后在main函数中调用三个实现变量交换的函数

/*
	变量交换的时空复杂度分析
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/

int main(int argc, char* argv[])
{
	//C语言程序的编译是从上到下开始编译的,如果想要调用函数,必须在main方法执行之前实现
	//声明、实现该函数
	swap_with_temp();
	swap_with_arithmetic();
	swap_with_xor();


	system("pause");
	return 0;
}

程序运行结果
技术分享图片

通过对比中间变量、算术运算和异或运算三种交换变量的方式实现,异或运算是最佳实现。

2.6 scanf函数的使用

之前在定义整数变量并初始化值时只能写死一个值,整数变量建议初始化时为0,这样可以避免许多不必要的错误出现。
为了让程序变得更加灵活,这里引入C语言标准库函数scanf()函数实现基于终端字符界面的人机交互。当然日常应用(例如淘宝、京东)都是基于UI界面实现人机交互,但是底层处理的逻辑是一样的。

scanf()函数可以从键盘中读取用户输入的整数、小数、字符等等,该函数的参数需要传递 提取数据格式和变量地址两个参数,提取数据格式指的就是用户输入的数据类型,例如整数、小数等等,变量地址就是&变量名
,&表示地址符号,变量名只能代表空间的内容,通过&变量名获取变量对应存储空间存储的值。
scanf()函数是阻塞式的,当用户输入数据之后,数据会存储到标准输入缓存区中,然后scanf()函数负责从标准缓冲区中拿指定格式的数据,如果用户不输入数据,那么程序会一直处于阻塞状态,直到用户输入数据按回车后程序继续往下执行。

使用scanf函数读取用户从键盘输入的整数数据

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

   使用scanf函数读取用户从键盘输入的整数数据
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
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;
}

程序运行结果
技术分享图片

使用scanf函数读取用户从键盘输入的小数数据

scanf函数获取用户输入小数时使用%lf数据格式符接收

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	使用scanf函数读取用户输入的小数
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	double salary = 0.0;
	printf("请输入你的工资(按回车结束)\n");
	scanf("%lf",&salary);
	//输出结果小数点后保留2位数
	printf("你的工资是%.2f\n",salary);
	system("pause");
	return 0;
}

技术分享图片
读取用户从键盘输入的浮点数并输出

2.7 整数

2.7.1 整数常量

C语言整数常量可以使用u后缀表示位无符号整数,使用l后缀表示long类型的整数,使用ll后缀表示为long long类型的整数

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

整数常量
	整数常量分为整型、长整型、长长整型
	同时每种整型还有有符号和无符号两种类型
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
    @version 2019/12/18

*/
int main(int argc, char* argv[])
{
	printf("有符号整数常量%d \n", 100);
	printf("无符号整数常量%d \n", 100u);

	printf("有符号长整数常量%ld \n", 2147483647L);
	printf("无符号长整数常量%lu \n", 2147483647UL);

	printf("有符号长长整数常量%lld \n", 9223372036854775807LL);
	printf("无符号长长整数常量%lld \n", 9223372036854775807ULL);
	system("pause");
	return 0;
}

2.7.2 整数的三种进制类型

C语言中的整型常量支持八进制、十进制和十六进制三种进制类型,不支持二进制。

  • 八进制由0-7之间的八个整数组成,八进制的常量值是以0开头。
  • 十进制由0-9之间的十个整数组成。
  • 十六进制由0-9,a-f之间的十个整数加上6个字母组成。
    printf()函数针对整数的三种进制类型提供了对应的输出格式,其中八进制输出使用%o表示,十进制使用%d表示,十六进制使用%f表示。
    #表示输出进制的完整格式,例如八进制会在最左边填充一个0,十六进制会在最左边填充0x。

不同进制的输出不会改变数据原来的值,底层依然是以二进制存储,只是输出的表现形式变了。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	整数的三种进制输出形式
	C语言不支持二进制输出
	八进制
	十进制
	十六进制
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//定义整型变量,初始化值为100
	int number = 100;
	//虽然输出结果有变化,但是原始number的值是没有变化的
	printf("100以八进制输出的结果是%#o\n", number);
	//不同进制的输出不会修改数据,只是数据的表现形式发生了变化。
	printf("100以十进制输出的结果是%d\n", number);
	//# 表示将进制的完整表现形式输出 例如十六进制的值是以0x开头,八进制的值是以0开头
	printf("100以十六进制输出的结果是%#x\n", number);

	system("pause");
	return 0;
}

2.7.3 整数的有符号和无符号

有符号表示数据有正负之分,数值二进制的最高位(即左边第一位)为符号位,0表示正数,1表示负数,其他位为数据位。以两个字节的数据1为例子,其二进制表示为0000 0000 0000 0001,因为是正数,因此最高位是0,-1的二进制表示为1000 0000 0000 0001,因为是负数,因此最高位是1。

无符号数表示数据没有正负之分,全是正数(即大于等于0的数),无符号数没有符号位,全是数据位。以两个字节的数据1为例子,其二进制表示为0000 0000 0000 0001。

C语言中的整数常量默认都是有符号的,如果要使用无符号的整数常量,需要在整数常量值后面添加U后缀。
C语言中的整数变量默认都是有符号的,如果要使用无符号的变量,需要在变量类型前面加上unsigned关键字。

有符号和无符号整数常量

printf函数针对无符号整数常量使用%u输出

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    有符号和无符号的整数常量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/17
*/
int main(int argc, char* argv[])
{
	// 10默认是有符号
	//有符号整型
	printf("%d \n", 10);
	//10U是无符号数 printf输出无符号int 使用格式 %u
	printf("%u \n", 10U);

	// 有符号长整型
	printf("%ld \n", 10L);
	//无符号长整型
	printf("%lu \n", 10UL);
	system("pause");
	return 0;
}

有符号和无符号整数变量

有符号数使用sign表示,整数默认是有符号数,因此通常省略不写
而无符号数必须使用unsigned声明

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	变量默认是有符号的,如果要声明无符号的变量,使用unsigned声明

	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/17
*/
int main(int argc, char* argv[])
{
	//整数变量默认是有符号的
	int age = 26;
	//无符号整数变量
	unsigned int data = 39;
	printf("age =%d \n", age);
	printf("data = %u \n", data);
	system("pause");
	return 0;
}

2.7.4 跨平台的整数

为了解决不同平台,相同的类型占据的大小不一致的问题,C语言标准委员会在C99标准中提出了跨平台的整数,在<stdint.h>头文件中定义,意味着同样的类型在不同的系统下的大小是一致的。
例如int64_t在所有实现C99标准的编译器下占据的都是8个字节,int32_t在所有实现C99标准的编译器下占据的都是4个字节。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
/*

    跨平台的整数
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
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;

}

2.7.5 整数的极限

整数按照占据不同的字节大小可以分为short,int,long和long long 四种类型,它们默认是有符号(signed)类型用于存储正负数,而对应的无符号类型(unsigned)则用来存储非负数的整数。
整数的极限定义在<limits.h>头文件中定义,Visual Studio 2019中可以选中一个极限值常量,然后使用快捷键F12转到定义,直接查看常量值的表示范围。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	三种整数类型的有符号和无符号的极限
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	printf("short能存储的最大值是%d\tshort能存储的最小值是%d,占据的字节数量是%d\n\n\n", SHRT_MAX, SHRT_MIN, sizeof(short));
	printf("unsigned short能存储的最大值是%d\n\n", USHRT_MAX);
	//32位和64位系统 int和long是等价的
	printf("int能存储的最大值是%d\tint能存储的最小值是%d,占据的字节数量是%d\n\n", INT_MAX, INT_MIN, sizeof(int));
	printf("unsigned int能存储的最大值是%u\n\n", UINT_MAX);
	//无符号的整数 最小值都是0 即不能表示负数
	printf("long能存储的最大值是%d\tlong能存储的最小值是%d,占据的字节数量是%d\n\n", LONG_MAX, LONG_MIN, sizeof(long));
	printf("long long能存储的最大值是%lld\tlong long能存储的最小值是%lld,占据的字节数量是%d\n\n", LLONG_MAX, LLONG_MIN, sizeof(long long));
	printf("unsigned long long 能存储的最大值是%llu\n", ULLONG_MAX);
	system("pause");
	return 0;
}

2.7.6 整数的注意事项和案例

在使用整数参与运算时,需要考虑到数据范围对应的极限,否则会发生错误的结果

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
    整数运算的越界
    @author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//为了保证结果运算正确,必须在极限范围之内
	printf("无符号short int所能存储的最大值是%d\n", USHRT_MAX);

	unsigned short int  shortnum = 65536;
	printf("shortnum=%d", shortnum); //结果为0 因为chnum所能表示的最大值为65535,这里发生了越界,结果错误
	system("pause");
	return 0;
}

如果想要存储身份证号等超大类型的整数数据,可以使用无符号的long long类型来存储

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    long long 类型的应用场景
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
    @website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	unsigned long long mobilePhone = 18601767221;
	printf("mobilePhone=%llu\n", mobilePhone);
	unsigned long long qq = 1079351401;
	printf("qq  = %llu", qq);
	system("pause");
	return 0;
}

根据给出的三角形三条边,使用头文件<math.h>中的sqrt函数计算三角形的面积

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/*
	根据给出的边长求面积
	使用math.h文件中提供的开平方根函数
	@author liuguanglei ittimelinedotnet@gmail.com
	@website ittimeline.net
	@version 2019/12/18
*/
int main(int argc, char* argv[])
{
	//根据给出的边长求面积
	//使用math.h文件中提供的开平方根函数
	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.8 浮点数

2.8.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 <stdio.h>
#include <stdlib.h>
/*
	浮点数常量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19

*/
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.8.2 浮点数变量

在初始化浮点数变量时,默认值建议为0.0或者0.0f,赋值时变量的值和变量的类型保持一致。
printf()函数输出float类型的变量使用格式符%f,输出double类型的变量使用%lf。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	浮点数变量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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.8.3 浮点数极限

C语言在<float.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 <stdio.h>
#include <stdlib.h>
#include <float.h>
/*

	浮点数的极限
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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.8.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.8.5 浮点数使用注意事项

float占据四个字节,提供的有效位是6-7位,而double占据八个字节,提供的有效位数是15-16位,如果在使用float或者double表示实数时超过有效数字,若拿来进行关系运算(例如等于)的话,会得到一个错误的结果。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
/*

	浮点数的使用注意事项
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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计算

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/*

	给出当前美国和中国的GDP以及增长率,使用math.h的pow函数实现计算出中国GDP超过美国GDP的年份
	@author liuguanglei ittimelinedotnet@gmail.com
	@website ittimeline.net
	@version 2019/12/19
*/
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.9 字符

2.9.1 字符型常量

在日常开发应用中,字符是最常用的数据类型。因为生活中的许多数据都是通过字符表示,而不是数字表示,字符能表示更多的含义,最典型的就是网络协议,例如超文本传输协议HTTP协议。

C语言中字符使用一对单引号‘‘表示,注意单引号只能作用域一个字符。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	字符常量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19

*/
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 <locale.h> //引入本地化的头文件
#include <stdio.h>
#include <stdlib.h>
/*

	C语言中文存储
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19

*/
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 <stdio.h>
#include <stdlib.h>
/*

	使用putchar和printf输出字符
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19

*/
int main(int argc, char* argv[])
{
	char c = ‘A‘;
	putchar(c);
	printf("\n");
	printf("c = %c \n", c);
	system("pause");
	system("pause");
	return 0;
}

2.9.2 字符的本质

由于计算机最底层只能识别二进制的数据,但是字符不是二进制的数据。
如果将字符和特定的数值一一对应起来,这张对应表就是ASC||表。
如果字符变量ch存储的字符是‘z‘,那么实际上存储的是字符z对应的ASC||值即整数122。即字符变量存储的本质就是存储的字符对应的ASC||值

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	字符与asc||码表
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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;
}

日常开发中最常用的字符就是大小写字母以及数字字符
我们可以借助printf()函数的格式%c和%d实现输出字符对应的ASC||值。
数字字符‘0‘表示的整数是48,小写字符‘a‘表示的整数是97,大写字符‘A‘表示的整数时65。数字0表示空字符。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	字符的本质
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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;
}

2.9.3 转义字符

一些特殊的符号无法直接显示时,我们使用\特殊字符来表示。
例如\a表示发声,无法在终端上直接显示。
在日常开发中还会使用到各种常用的转义字符,例如\t实现tab的效果。
转义字符\n实现换行的效果,转义字符\实现路径转义。
%%输出百分号。
转义字符还可以使用八进制和十六进制表示,用于表示字符对应的ASC||码值

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	转义字符
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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.9.4 字符的应用

既然字符变量的本质存储的ASC||值,即整数。因此字符也可以参与加减运算。
由于ASC||码规定了小写字母‘‘a‘ 表示97,然后一次递增,小写字母‘z‘表示122,而大写字母‘A‘表示65,然后依次递增,大写字母‘Z‘表示90。因此根据这个规律可以通过加减运算实现大小写字母转换。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    字符的大小写转换
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/20
*/
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.10 布尔

bool类型只有两个值,即true和fasle,它们在内存中分别使用1和0表示,这样一个字节便可以存储bool类型的变量。
在C程序中使用bool类型的变量,需要引入头文件stdbool.h,后续的各种逻辑、关系运算以及选择结构if/else和while循环会大量使用bool类型的变量。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/*
	bool类型及其应用
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/19
*/
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.11 数据类型转换

2.11.1 数据自动类型转换

当多种数据类型(整数、浮点数、字符)同时参与运算时,会发生自动类型转换,容量小的数据类型的变量与容量大的大数据类型的变量做运算时,结果自动提升为容量大的数据类型,防止运算时超过极限值,防止运算结果的精度丢失。此时容量大小指的是,数据类型表示数的范围大小,而不是占用内存大小,比如float容量大于long的容量。

自动类型转换的规则如下

char,short->int->long->float->double->long double

char和char运算,short和short运算也会转换为int。

char和int,double的自动类型转换

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    数据类型转换之自动类型转换

	char和int,double的自动类型转换

	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
int main(int argc, char* argv[])
{
	//大写字符‘A‘会被当作整数65处理
	char ch = ‘A‘;
	//因为char和int运算时会自动转换为int,而整数常量1默认的类型是int
	//因此 ch+1占据的字节数量是4
	printf("字符变量 A+1 占据的字节数量是%d\n",sizeof(ch+1));
	printf("字符‘A‘+1 = %d \n",(ch+1));

	//因为char和double运算时会自动转换为double,而浮点数常量1.0默认的类型是double
	//因此 ch+1.0占据的字节数量是8
	printf("浮点数1.0占据的字节数量是%d\n", sizeof(1.0));
	printf("字符变量 A+1.0 占据的字节数量是%d\n",sizeof(ch+1.0));
	printf("字符‘A‘+1.0 = %.2f\n", (ch + 1.0));


	system("pause");
	return 0;
}

其中对应的有符号类型还会自动转换为无符号类型
int和unsigned int的自动类型转换

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	自动类型转换
	有符号类型int转换为无符号类型int
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
int main(int argc, char* argv[])
{
	//有符号类型int转换为无符号类型int
	int number1 = -10;
	printf("整数10按照无符号输出的结果是%u\n",number1);

	unsigned int number2 = 5;

	int result = number1 + number2;
	printf("按照有符号整数输出 result = %d \n", result);
	printf("按照无符号整数输出 result = %u \n", result);
	//因为number1按照无符号的结果是4294967286 因此结果是大于0
	number1 + number2 > 0 ? printf("number1 + number2 > 0") : printf("number1 + number2 < 0");
	printf("\n");

	system("pause");
	return 0;
}

char、short的自动类型转换:由于char,short占据的内存空间过小,编译器规定,只要是char,short参与运算,都会自动转换为int类型。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	自动类型转换
	由于char,short占据的内存空间过小,编译器规定,只要是char,short参与运算,都会自动转换为int类型。
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
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.11.2 数据强制类型转换

强制类型转换就是在待转换的表达式的左边使用强转符(目标类型)实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	强制类型转换
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
int main(int argc, char* argv[])
{
	float fl = 10.8;
	float flt = 10.3;

	/*
		运算过程
		首先将10.8强制转换为整型10
		然后将10和10.3运算的结果是20.3
		最后将20.3的结果赋值给int,因为赋值也会发生自动类型转换
		因此最后的计算结果是20
	*/
	int number = (int)fl + flt;
	printf("number = %d \n",number);

	/*
		运算过程
		首先计算10.8+10.3,运算结果是21.1
		然后将21.1转换为整数21
		因此运算结果是21
	*/
	number = (int)(fl + flt);
	printf("number = %d \n", number);

	system("pause");
	return 0;
}

强制类型转换并没有改变变量原有的值。因为强制类型转换是在寄存器内部实现的,不会改变内存的值。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	强制类型转换
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version
*/
int main(int argc, char* argv[])
{
	float flt = 3.14f;
	int number = 0;
	number = (int)flt;
	printf("number = %d \n",number);

	//强制类型转换后并没有改变浮点数变量flt的值
	printf("flt = %.2f \n",flt);

	system("pause");
	return 0;
}

在使用printf()函数进行输出数据到控制台时,不会进行数据类型自动转换,如果想要输出的数据与格式符不匹配时,需要使用强制类型转换实现,否则会打印输出一个错误的结果。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	数据类型转换与printf函数
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/04
*/
int main(int argc, char* argv[])
{
	double dbl = 3.14;
	//printf在打印输出数据时不会进行自动类型转换,这里输出的是垃圾结果
	printf("dbl = %d \n",dbl);
	
	//如果想要将浮点数以整数的形式输出,需要使用强制类型转换实现 
	printf("dbl = %d \n",(int)dbl);

	int value = 10;
	printf("dbl = %f \n", value);
	printf("dbl = %f \n", (float)value);
	system("pause");
	return 0;
}

2.11.3 数据类型转换案例

类型转换案例:实现对小数点后三位实现四舍五入

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	使用强制类型转换实现浮点数后三位的四舍五入
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
int main(int argc, char* argv[])
{
	printf("请输入四舍五入的三位小数\n");
	double input = 0.0;
	scanf("%lf", &input);


	/*
	计算流程

		四舍
	// 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);		
	system("pause");
	return 0;
}

类型转换案例:账户余额的分大于4分就偷钱

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	账户余额的分大于4分就偷钱
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
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 = %.2f\n", rest);
	if (rest < balance) {
		//
		printf("可以偷钱%.2f元\n", balance - rest);
	}

	system("pause");
	return 0;
}

2.11.4 数据类型转换的内存原理

首先探讨自动类型转换(通常是低字节转换为高字节)的内存原理,而自动类型转换分为无符号数的类型转换和有符号数的类型转换,其最终的结论是低字节转换为高字节时按照高字节的符号位填充,
如果是有符号类型自动转换,而且该数为负数,则填充1,如果该数为负数,则填充0。 如果是无符号类型自动转换,按照0进行填充。

正整数(等同于无符号数) 低字节转换为高字节

首先声明char,int类型的两个变量,并赋值为1,然后将char变量赋值给int变量。

因为两个变量的值都是整数1,只是占据的字节大小不一样

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	数据类型转换的内存原理

	低字节转高字节,按照符号位填充(如果高字节符号位为0,则高位填充0,如果高字节符号位为1,则高位填充1)
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/04
*/
int main(int argc, char* argv[])
{
	//正数的低字节转高字节

	// 字符变量ch 在内存中占据一个字节 1 在内存中表示为 0000 0001
	char ch = 1;
	// 正数变量num 在内存中占据四个字节,1在内存中表示为 0000 0000 0000 0000 0000 0000 0000 0001
	int num = 1;
	// 低字节转高字节按照高位的符号位填充(如果高字节符号位为0,则高位填充0,如果高字节符号位为1,则高位填充1)
	num = ch;

	printf("ch在内存中的地址是%p\tnum在内存中的地址是%p\n",&ch,&num);

	system("pause");
	return 0;
}

并输出两个变量在内存中的内存地址。
技术分享图片
根据地址可以在Visual Studio 2019中查看内存中存储的值
技术分享图片
其中char以十六进制的一个字节表示的结果为01
技术分享图片
而int以十六进制的四个字节表示结果为0001
技术分享图片
根据进制转换关系,十六进制转换为二进制,在内存中的二进制表示分别为0000 00010000 0000 0000 0000 0000 0000 0000 0001,空格只是为了让数值更加容易阅读。

因此当低字节转换为高字节(例如char转换为int)时,按照高字节的符号位填充,如果高字节的符号位为0则填充0,否则填充1。

负数(等同于有符号数)低字节转高字节

首先声明char,int类型的两个变量。并赋值为-1,然后使用printf()函数结合格式转换符%p输出两个变量的内存地址

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	数据类型转换的内存原理

	低字节转高字节,按照符号位填充(如果高字节符号位为0,则高位填充0,如果高字节符号位为1,则高位填充1)
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/04
*/
int main(int argc, char* argv[])
{
	//正数的低字节转高字节

	// 字符变量ch 在内存中占据一个字节 1 在内存中(补码)表示为 1111 1111
	// 计算过程 一个字节 -1的原码1000 0001 取反 -> 1111 1110 加1 -> 1111 1111 十六进制表示为ff
	char ch = -1;


	// int 在内存中占据四个字节,1在内存中表示为 1111 1111 1111 1111 1111 1111 1111 1111 十六进制(补码)表示为ffff ffff
	// 四个字节-1的原码 1000 0000 0000 0000 0000 0000 0000 0001 取反 ->  1111 1111 1111 1111 1111 1111 1111 1110 加1 ->  1111 1111 1111 1111 1111 1111 1111 1111 
	int num = -1;
	// 低字节转高字节按照高位的符号位填充(如果高字节符号位为0,则高位填充0,如果高字节符号位为1,则高位填充1)
	num = ch;

	printf("ch在内存中的地址是%p\tnum在内存中的地址是%p\n", &ch, &num);

	system("pause");
	return 0;
}

然后分别在内存中使用查看一个字节-1的存储和四个字节-1的存储
技术分享图片
技术分享图片

而高字节往低字节转换 是按照低字节截取的结果。

2.12 字符串常量

字符串是由一个或者多个字符组成,和字符不同的是,字符串使用一对双引号""组成的。
其中一个英文字符占据一个字节,一个中文字符占据两个字节,而且字符串是以字符‘\0‘结尾,该字符是由编译器自动添加的。
使用printf输出字符串时,格式符是%s,遇到‘\0‘结束输出,同时要给出输出地起始址,而""除了表示字符串以外,还能取字符串首个元素的地址。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	字符串常量
	一个字符占一个字节
	字符串由多个字符组成

	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/21
*/
int main(int argc, char* argv[])
{
	//字符串刘光磊的字节数量是 7 
	//字符串是以\0结尾
	//一个英文字符占据1个字节
	printf("字符串hello占据的字节数量是%d\n",sizeof("hello"));
	//一个中文字符占据2个字节
	printf("字符串刘光磊占据的字节数量是%d\n",sizeof("刘光磊"));

	// ‘‘的作用是表示该内容是一个字符,取字符的ASCII值

	// "hello world" ""的作用 表示该内容是一个字符串 取字符串首个元素(h)的地址
	printf("%s\n","hello world");
	//跳过2个字符输出
	printf("%s\n","hello world"+2);
	//如果按照整型输出时。输出的是字符串首元素的地址
	printf("%d\n","hello world");

	// %s 遇到‘\0‘结束
	//因此这里只会输出hello
	printf("%s\n","hello \0 world");
	//这里会输出空白
	printf("%s\n","\0hello world");



	system("pause");
	return 0;
}

2.13 数值在计算机的存取方式

2.13.1 数值在计算机的存储方式

任何数据在计算机中是以补码的方式存储,而数据的表现形式有原码、反码和补码。

数据的表示分为有符号数和无符号数,有符号数其二进制的表示最高位为符号位,如果最高位为1,表示为负数,如果最高位为0,表示为正数
无符号数其二进制的表示最高位为数据位,无符号数都是正数

  • 原码
    原码表示数据本身的二进制
    例如无符号数15以一个字节的原码表示为0000 1111
    有符号 分为正数和负数,最高位表示符号位。正数15 以一个字节的原码表示为 00001111,而负数-15以一个字节的原码表示为1000 1111

  • 反码
    无符号数 反码等于原码,以一个字节的无符号整数15为例,其原码为0000 1111,其反码也是0000 1111
    有符号数 正数反码等于原码,负数就是最高位不变,其他位取反,以-15为例,其原码为 1000 1111,反码表示为1111 0000

  • 补码
    无符号数 补码等于原码
    有符号数 正数补码等于原码,负数就是反码的最低位加1即可,以-15为例,原码为 1000 1111 ,反码表示为1111 0000,补码就是反码加1,其结果是1111 0001。

综上所述,无符号数以及有符号正数计算机存的是原码,因为补码等于原码。而有符号负数计算机存储的是补码,补码等于原码取反加1。

原码 反码 补码
正数 数值二进制 原码 原码
负数 数值二进制 符号位不变,其他位取反 反码+1
15 0000 1111 0000 1111 0000 1111
-15 1000 1111 1111 0000 1111 0001

2.13.2 数据在计算机的获取方式

数据在计算机的获取方式可以按照有符号获取和无符号获取两种。

如果是有符号获取,可以使用%d,%ld,%lld,%f,%lf 格式符。
编译器在使用有符号获取数据时,首先看内存中数据的最高位(符号位)是否是1,如果是1,表示该数为负数的补码。因为数据的存储都是使用补码存储的,但是printf输出时输出的是原码
因此此时还需要将补码转换为原码,即符号位不变,其他位取反,然后加1。

如果内存中数据的最高位是0,表示该数为正数的补码,补码等于原码,不需要再进行取反加1的操作,原样输出即可。

如果是无符号获取,可以使用%u ,%lu,%llu,%o,%x 格式符。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    数据的存取
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/20
*/
int main(int argc, char* argv[])
{
	//-15 原码为1000 1111 但是在内存中存储的是补码。因此1000 1111 取反 1111 0000 加1 的结果是1111 0001 ,即-15在内存中的存储二进制表示为1111 0001
	char number = -15;
	//有符号输出 补码 1111 0001 取反 1000 1110 加1  1000 1111 
	printf("number = %d \n ",number);
	//无符号输出 1111  0001  原样输出 即1111 0001转换为十进制输出为241 
	printf("number = %u \n",number&0x00000000ff);

	system("pause");
	return 0;
}

如果给变量使用16进制赋值,存储数据时,内存原样存储。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	数据的存取
	如果给变量使用16进制赋值,存储数据时,内存原样存储。
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/20
*/
int main(int argc, char* argv[])
{
	//9b 内存中存放的数据为 1001 1011
	char number = 0x9b;

	//有符号取
	// 1001 1011 最高位是1 表示负数的补码,需要取反  1110 0100 加1 1110 0101  转换为十进制就是-101
	printf("number  =%d \n",number);

	//无符号取
	//内存原样输出
	printf("number = %x\n",number&0x00000000ff);
	system("pause");
	return 0;
}

char赋值越界的原理

char 在内存中占据一个字节
如果char是有符号其表示范围分为正数部分和负数部分。负数部分是1111 1111到 1000 0000,正数部分是0000 0000 到0111 1111,转换为十进制就是-127到-0和0到127,而通常将-0当作-128看待。
因此char的表示范围是-128到127

无符号char其表示范围为0000 0000 到1111 1111即0到255
无论是有符号数还是无符号数。其表示的数据个数是一样的,只是表示范围不同

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    字符类型的取值范围
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2019/12/20
*/
int main(int argc, char* argv[])
{
	// 十六进制的0xff 使用二进制表示为1111 1111 然后加2 的结果是1 0000 0001 

	char number = 0xff + 2;

	//有符号获取
	// 1 0000 0001 因为char只能存储一个字节,因此存储的是0000 0001 ,因此输出的结果是1 
	printf("number =%d \n",number&&0x00000000ff);
	 
	//无符号输出  因为char只能存储一个字节,因此存储的是0000 0001 ,因此输出的结果是1 
	printf("number =%x \n",number);

	//十六进制的0x80 转换为二进制是 1000 0000,即为-0的原码
	char zero = 0x80;
	//有符号输出 1000 0000 取反 1 111 1111 加1 1 0000 0000 的结果就是-128
	printf("zero = %d\n",zero);
	 
	system("pause");
	return 0;
}

计算机为什么要使用补码存储?

如果没有补码,使用原码计算9-6,可以换算成9+ -6
0000 1001 + 1000 0110=1000 1111,换算成十进制就是-15,其结果显然是错的。

如果采用补码运算
9的补码就是0000 1001
-6的补码就是 1000 0110 取反 1111 1001 加1 等于 1111 1010

即 0000 1001 +1111 1010=1 0000 0011 ,以1个字节长度结果,这里需要截取掉最高位的1 ,截取后的结果为0000 0011,转换为十进制的结果为3。

2.14 C语言类型限定符

C语言的类型限定符由四个关键字组成,分别是const,register,externvolatile

  • const
    const修饰变量为只读,本质还是变量,不能被修改,而且const修饰的变量必须被初始化
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

	const 修饰变量为只读,本质还是变量,不能被修改,而且const修饰的变量必须被初始化
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/03
*/
int main(int argc, char* argv[])
{
	const int number = 10;
	//这里会发生编译错误 因为const修饰的变量一旦被初始化后就不能再赋值
	//number = 20;
	const int value;
	//程序运行时会出错 error C4700: 使用了未初始化的局部变量“value”
	//printf(" value  = %d \n",value); 
	system("pause");
	return 0;
}
  • register
    register修饰变量时表示寄存器变量
    应用场景:当项目中需要高频繁使用变量(例如导航时的位置坐标 )
    CPU在处理程序时,首先从内存中获取指令和数据,并加载到CPU内部的寄存器中,然后进行相关的计算后,将结果返回给内存。

如果频繁使用的变量使用register修饰后,编译器尽量将这个变量放到寄存器中(因为寄存器是有限的),如果放入寄存器失败,将会放在内存,接下来CPU只从寄存器中获取,从而提高运行效率 。
register修饰的变量不需要取地址(例如&location),因为变量在内存中才有地址,但是register修饰的变量是存储在CPU内部的寄存器中。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*

    register 寄存器变量
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/04
*/
int main(int argc, char* argv[])
{
	//寄存器变量的声明
	register int location = 100;
	location = 200;

	printf("location = %d \n",location);
	//这里运行会报错,register修饰的变量是寄存器变量,不能获取内存地址 &表示获取内存地址
	//printf("location =%p",&location);
	system("pause");
	return 0;
}
  • volatile
    防止编译器优化代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	volatile 防止编译器优化代码
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/05
*/
int main(int argc, char* argv[])
{
	volatile int num = 10;
	num = 10;
	num = 10;
	num = 10;
	num = 10;
	num = 100;

	system("pause");
	return 0;
}

如果没有在num变量前面加上volatile关键字,则编译器会直接执行num=100,而这之前的语句会被编译器优化

  • extern
    声明一个变量,extern声明的变量没有建立存储空间。在声明时不用赋值,否则会引发变量“重定义,多次初始化的编译错误”。
    但是如果最终变量没有定义在使用之后,程序运行时还是会出现异常,例如这里如果注释int num = 20; 就会出现 无法解析的外部符号 num。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
	extern 声明变量,并没有开辟存储空间
	@author liuguanglei ittimelinedotnet@gmail.com
	@wechat 18601767221
	@website ittimeline.net
	@version 2020/01/05
*/

//此时并没有开辟存储空间 ,在声明时不用赋值,否则会引发变量“重定义,多次初始化的编译错误”。
extern int num;

int main(int argc, char* argv[])
{
	printf("num = %d\n", num);
	system("pause");
	return 0;
}
//但是如果最终变量没有定义在使用之后,程序运行时还是会出现异常,例如这里如果注释int num = 20; 就会出现 无法解析的外部符号 num。
int num = 20;

C语言核心技术-变量和数据类型

原文:https://www.cnblogs.com/ittimeline/p/12973315.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!