C语言基础程序设计

时间:2022-08-28 23:36:52

1 概论

程序(指令和数据的集合)在运行时,首先会被加载到内存(此时称为进程),然后由CPU通过控制器的译码从内存中读取指令,并按照指令的要求,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按照地址把结果送到内存中去。这就是计算机工作最基本的原理了。

但是作为准备成为一名优秀的C语言程序员,必须更加深入的了解数据在计算机内存存储和CPU计算的内幕。

2 进制

2.1 进制的介绍

在最底层,计算机数据都是采用二进制的补码来进行存储和运算的,但是为了方便使用,日常生活中使用更多的进制类型是八进制(0开头,包含0-7之间的八个数据),十进制(0-9之间的10个数据)和十六进制(0x开头,包含0-9和a-f之间的16个字母加数字的组合),其中八进制的典型用例就是linux系统的权限,十六进制就是变量的内存地址,而c语言中的整数默认就采用十进制来表示。 
c语言没有提供二进制的数据表现形式!!!

2.2 进制的运算与转换

相同进制类型数据进行运算时会遵守加法:逢R进1;减法:借1当R,其中R就表示进制。而每个八进制的数据都会对应3个二进制的数据(2^3=8),同理每个十六进制的数据都会对应4个二进制的数据(2^4=6),因此八进制转二进制以及十六进制转二进制的相互转换是相对十进制转二进制相对简单。

2.2.1 二进制,八进制,十六进制转换成十进制

在将二进制,八进制,十六进制转换为十进制时首先看看一个十进制的整数是如何计算的:

1234=1000+200+30+4=1*10^3+2*10^2+3*10^1+4*10^0

因此可以采用按权相加的形式计算二进制,八进制和十六进制转换为十进制的结果,

二进制转十进制的计算方式:

100101=1*2^5+1*2^2+1*2^0=32+4+1=37

八进制转十进制的计算方式:

27=2*8^1+7*8^0=15

十六进制转十进制的计算方式:

20d=2*16^2+13*16^0=512+13=525

2.2.2 二进制,八进制转换

二进制转换成八进制,从右向左,每3位为一组(不足3位用0补全),转换成八进制

101101=55

八进制转换成二进制,用3位二进制数替代每一位八进制数

55=101101

2.2.3二进制,十六进制转换

二进制转换为十六进制,从右向左,每4位一组(不足用0补齐),转换为十六进制

11011101=dd

十六进制转换为二进制,用4位二进制代替每一位十六进制的数

dd=11011101

2.2.4 十进制,二进制转换

十进制整数转换为二进制:方法是除以2取余,直到商数为0,逆序排列,以22为例:倒序的二进制结果就是10110

22/2 余 0 
11/2 余 1 
5 /2 余 1 
2/2 余 0 
1/2 余 1 
0

360面试题: 
十进制小数转换为二进制:方法是乘以2,直到小数部分为0为止,取整顺序排列,以0.725为例:顺序排列的二进制结果就是0.11

0.725*2=1.5 取整 1 
0.5*2=1 取整 1

2.4编程实现十进制转换成二进制

#define _CRT_SECURE_NO_WARNINGS//关闭VS2015的安全检查
#include <stdio.h>
/*
十进制的正整数转换为二进制
@author Tony 18601767221@163.com
@since 20160525 12:05
*/
void decimal() {
/*
1.该程序只能将0-32767之间的正整数转换为对应的二进制形式,将转换后的结果存储于数组中,因此
声明一个16个长度的整数数组来保存二进制的结果
*/
int numbers[16] = { 0 }; //声明一个1包含16个整数元素的数组 初始化所有的元素值为0
/*
2.使用scanf函数读取用户输入的0-32767之间的正整数,使用循环来除以2,直至商数为0时,将除以2的余数保存在数组中
*/
int number = 0;
printf("请输入一个0-32767之间的整数\n");
scanf("%d", &number);
for (int i = 0; i < 15; i++) { //因为只考虑正数,因此最高位的符号位为0,少一次循环除以2
int remainder = number % 2; //余数
int quotient = number / 2; //商数
//将除以2之后的商数赋值给number
number = quotient;
//将余数赋值给数组中的元素
numbers[i] = remainder;
}

/*
3.反向遍历数组中的元素,也就是16个二进制的整数,从最后一个元素到第一个元素
*/

for (int i = 15; i >= 0; i--) {
printf("%d", numbers[i]);
//每四个二进制整数之间使用table分割
if (i % 4 == 0) {
printf("\t");
}
}
}

void main() {
decimal(); //调用方法
system("pause");
}

3 计算机信息存储

先理清楚两个传输和存储单位:位(bit)和字节(byte) 
计算机最底层的数据都是二进制的01010组成,因此最小的传输传输单位(宽带的网速就是以bit来计算的)为位 (bit),1bit可以存储的值为0或者1。

字节(Byte)是计算机中最常用的基本存储单位,1个字节表示8个位,也就是

1Byte=8bit

而在计算机的日常使用中,下载的速度通常是KB或者MB,1KB=1024Byte,1MB=1024KB,

笔记本的移动硬盘或者服务器的硬盘通常都是GB或者TB,1GB=1024MB,1TB=1024GB

为什么32位操作系统只能使用4G不到的内存?

4G=2^2*2^10*2^10*2^10=2^32

因为操作系统运行时还要占据一定的内存,所以4G内存条的可用内存大概是3.2G左右 
而KB,MB,GB,TB之间是采用2的十次方也就是1024进行运算的。

随着数据的日益增长,以BAT为代表的互联网公司处理的海量数据已经达到PB级别,1PB到底有多大?

1PB=1024TB=1024*1024GB=1024*1024*1024MB=(2^30) MB

4 常量

人生实际上就是一场打怪升级的游戏,而当你注册成为一个新的玩家之后,绑定的身份证信息是永远不会变的,这个实际上就是常量。 
C语言中的常量可以用两种方式来实现,分别是const varriableName=value;#define CONST_NAME CONST_VALUE。其中const修饰的变量有内存地址,可以通过引用指针来修改常量值,而#define是真正意义上的常量,因为它在CPU寄存器内部,C语言能操作内存,但是没有直接操作CPU的方法(无法直接获取地址值和变量值)。

使用指针修改const的常量示例:

#include <stdio.h>
/*
使用指针修改常量的值
@author tony 18601767221@163.com
@since 20160524 20:11
*/
void change_constants() {
const int score = 99; //使用const定义常量

//score = 12; //const只能避免变量显示修改,可以通过内存地址来修改

// * 根据地址取出内容 (int *) 强制类型转换,转换为非常量类型
*(int*)(&score) = 98; //通过指针修改const变量的值

printf("score =%d \n",score);
}

使用常量可以提高代码的复用性,实现批量修改:

#define PI 3.1415926 //当改变该常量的值,所有引用该常量的变量值都会修改
#include <stdio.h>
/*
使用常量提高代码的复用性
@author tony 18601767221@163.com
@since 20160524 20:11
*/
void use_define() {

double pi = PI; //预编译时会替换成pi的值

printf("pi = %.7f",pi);
}

5 变量

回归到计算机的本质:程序运行时,数据和指令都会被加载到内存中,而CPU会从内存中读取指令和数据,根据不同的指令对数据进行不同的逻辑计算,从而产生对应的计算结果存放到内存中,而变量正是用来存储运算过程中随着不同业务逻辑产生对应运算结果的容器。

5.1 变量的声明和初始化

有了变量,编译器就可以通过它声明时指定的数据类型,开辟对应的内存空间来存储数据。

变量名和函数名必须准守不能以数字开头,包含字母数字和下划线组成。 
在使用变量的时候还需要考虑到不同的编译器: 
VisualStudio2015提供了中文良好的支持(变量直接使用中文),而GCC编译器不支持中文。而且C语言严格区分大小写

因为C语言不会像Java那样针对类中的每个成员变量的不同数据类型赋初始值,所以我们在C语言中声明变量时必须初始化变量的值。

有些C语言的编译器(例如VC2010以前的编译器或者GCC编译器没有开启C++11支持)还会要求在函数调用之前声明并初始化变量。

#include <stdio.h>
/*
变量的声明和初始化赋值
@author Tony 18601767221@163.com
@since 20160524 20:41
*/
void varriable_declare() {

int age = 28; //声明整形变量并赋初始值为28
printf("age = %d\n");
int length, width, area; //一次性声明多个变量
length = 12;
width = 12;
area = length*width;
}

为什么变量一定要初始化呢? 
内存是随机分配的,当一个进程结束后,它占据的内存被释放,但是内存中的数据依然存在,如果不给变量赋初始值,系统会给变量初始化一个垃圾值。 
C语言基础程序设计

5.2 变量在内存中的存储机制以及CPU运算原理

当声明变量并初始化赋值时,编译器会创建一张表,表中包含变量地址,类型,变量名和内存中内存地址以及内存空间相关联,对变量的一切操作实际上是修改变量映射在内存地址关联的内存空间存储的值。因此不能使用未声明的变量!!!

这里我们有必要再聊聊CPU(寄存器)和内存,采用一段C语言嵌套简单的汇编代码(因为汇编可以直接操作CPU,看起来更加直观)来说明在计算机完成计算的过程中,它俩到底做了什么事?

#include <stdio.h>
/*
c嵌套汇编 演示赋值的原理
@author Tony 18601767221@163.com
@since 20160524 21:05
*/
void c_asm() {

int age = 0;
printf("age 变量的地址为%p\n",&age);
//汇编语言
_asm {

//变量的赋值是通过CPU的寄存器来实现的
mov eax, 28 //将28移动到eax寄存器
mov age,eax//将eax的值移动到age
}
printf("age =%d",age);
}

C语言基础程序设计

从调试的页面中就可以看得出赋值(也是一种运算)是在CPU的寄存器中完成的,而实际上各种逻辑运算的都是存储CPU的寄存器内部,然后通过内存地址传递给对应的存储空间。

C代码演示数据计算的过程:

#include <stdio.h>
/*
显示三个整数变量的地址和值信息
@author Tony 18601767221@163.com
@since 20160514 15:10
*/
void show_varriable_info(int length,int width,int area) {

printf("变量地址信息!!!\n");
printf("length变量对应的内存地址%p\n width变量对应的内存地址%p\n area变量对应的内存地址%p\n", &length, &width, &area);

printf("变量值信息!!!\n");
printf("length =%d\n width=%d\n area=%d\n", length, width, area);
}

/*
变量的运算
@author Tony 18601767221@163.com
@since 20160524 20:59
*/
void varriable_operation() {


int length = 2;
int width = 2;
int area = 0 ;

show_varriable_info(length,width,area);
area = length*width;
show_varriable_info(length, width, area);
}

6 数据类型

数据类型是对程序所处理的数据(变量的类型,方法的返回类型)的”抽象”,将计算机中可能出现的数据类型进行分类,先上个C语言数据类型的全家福: 
C语言基础程序设计

C语言中的数据类型所占据的内存空间是和操作系统相关,相同的数据类型在不同的平台上占据的内存空间可能是不一样的,可以使用sizeof关键字获取占据的内存空间大小,内存大小不同,存储的取值范围也会不同。

6.1 整数

整数用于存放像游戏中的玩家级别,人的年龄等等数据。

C语言中的整数按照占据的字节数从小到大可以分为short,int,long,long long,其中在16位系统上short和int占据的内存是一样的(占据两个字节),而在32位以上系统int和long占据的内存是一样的(占据四个字节。)

#include <stdio.h>
/*
int和long是等价的 在32位以上的操作系统上
@author Tony 18601767221@163.com
@since 20160525 12:36
*/
void int_long_info() {
printf("int在Windows10 X64位操作系统上占据的字节为:%d\n",sizeof(int));
printf("long在Windows10 X64位操作系统上占据的字节为:%d\n", sizeof(long));

}

void main() {
int_long_info();
getchar();
}

而long long是为了能够存储类似于QQ号或者身份证号等超大的整数(占据八个字节)

#include <stdio.h>
/*
大整数
@author Tony 18601767221@163.com
@since 20160525 12:53
*/
void longlong_data() {

long long ID = 421023197912237234; //定义一个身份证号
printf("ID=%lld\n", ID);
printf("long long 占据的字节数量为 :%d",sizeof(long long));
}

void main() {
longlong_data();
getchar();
}

C语言中的整数可以使用八进制,十进制和十六进制表示,同时整数还有有符号(singed)和无符号(unsinged)两种方式,其中无符号和有符号占据的内存空间是一样大,但是不能存储负数(最小值是0),最大值要大于有符号的最大值。

整数的三种进制以及有符号和无符号的区别

#include <limits.h> //引入整数的极限值头文件
#include <stdio.h>//引入标准输入输出头文件
/*
整数的使用
@author Tony 18601767221@163.com
@since 20160525 12:53
*/
void int_use() {
int octal = 012; //定义八进制的整数变量
int decimal = 12; //定义十进制的整数变量
int hex = 0x12; //定义十六进制的整数变量

unsigned int u_int_max = UINT_MAX;
int int_max = INT_MAX;

printf("无符号int表示的最大值%u \n有符号int表示的最大值为%d",u_int_max,int_max);
}

每个整数的取值范围已经在limits.h头文件中使用常量的方式定义: 
C语言基础程序设计

其中最常用的整数是int,在使用的过程中需要注意值不能超过变量数据类型的取值范围,否则会造成数据溢出,竟而导致计算错误。

6.2 浮点数

浮点数就是数学中的小数,因为计算机的内存是有限的,不能存储数学中的无理数,例如float只能精确到小数点后6-7位(占据四个字节),而double能精确到小数点后15位(占据八个字节)。 
C语言中的浮点数可以采用数学中的小数(例如3.14,默认是double类型,加后缀f表示float类型)和科学记数法(例如2.98e5,字母e之前的数字称为尾数,e之后的数字称为指数,指数表示10次幂,尾数表示与这个幂值相乘)两种表示方式,其中指数必须是整数(正数和负数皆可)。

浮点数也可以采用十六进制来表示!

#include <float.h> //引入小数的极限值头文件
#include <stdio.h>//引入标准输入输出头文件
/*
浮点数的两种表示方法以及浮点数表示的极限值
@author Tony 18601767221@163.com
@since 20160525 14:15
*/
void float_double_info() {

float fl = 10.5; //赋值会进行类型转换操作,10.5默认是double类型
printf("fl=%.2f\n",fl); //%f 用于匹配浮点数格式 %.2f 表示小数点后保留两位,默认保留后六位

double value = 12.0e10; //采用科学记数法的形式表示浮点数 指数只能是整数

printf("\nvalue=%.1f\n",value);

double d_max_value = DBL_MAX;
double d_min_value = DBL_MIN;

printf("\n double所能存储的最大值为%e\n\n所能存储的最小值为%e\n",d_max_value,d_min_value);

double db_value = -12.0;
printf("db_value %f",db_value);
}

void main() {

float_double_info();
getchar();
}

浮点数的极限值存放在float.h头文件中,开发中尽量使用double 
C语言基础程序设计

使用算术运算结合C语言库函数实现计算中国GDP何时超过美国GDP

#include <stdio.h>
#include <Windows.h>
#include <math.h>

/*
计算中国GDP何时超过美国GDP
@author Tony 18601767221@163.com
@since 20160525 15:55
*/
void calc_gdp() {
//目前中国GDP约为12万美金 年增长率为6.5%,美国GDP约为18万美金,年增长率为3% 求哪一年中国的GDP可以超过美国

//定义变量保存中美当前的GDP
double ch = 110000.0;
double am = 180000.0;

//定义变量保存中美当前的年增长率
double chd = 1.07; //1.07相当于一年的增长率 而10年的增长率可以通过math头文件的pow(1.07,10)函数获取
double amd = 1.03;

//定义变量保存年份
int this_year = 2015;

for (int i = 1; i <= 50;i++) {


double ch_value = ch*pow(chd, i);//计算中国每年增长6.5%后的GDP
double am_value = am*pow(amd, i);//计算美国每年增长3%后的GDP

if (ch_value>am_value) {

this_year += i;
printf("%d年中国GDP超越美国:中国的GDP为%f\t美国的GDP为%f\n",this_year,ch_value,am_value);
MessageBoxA(0, "中国GDP超过美国","恭喜",0);

break;
}
}
}

给定三条边长,计算三角形面积

三角形面积公式为:

P=(a+b+c)/2 
S=p*(p-a)(p-b)(p-c) 
其中a b c表示三角形的三条边长

#define _CRT_SECURE_NO_WARNINGS//关闭VS2015的安全检查
#include <stdio.h>
#include <float.h>
#include <Windows.h>
#include <math.h>
/*
实现计算三角形的面积
@author Tony 18601767221@163.com
@since 20160525 16:10
*/
double calc_area(double one, double two, double three) {

double p = (one + two + three) / 2;
double area = sqrt(p*(p - one)*(p - two)*(p - three));
return area;
}

/*
根据指定的边长实现计算三角形的面积
@author Tony 18601767221@163.com
@since 20160525 16:17
*/
void calc_triangel() {

//初始化三角形的三条边
double one = 0.0;
double two = 0.0;
double three = 0.0;

//读取输入的三条边长存储到三个double变量中
printf("请输入三角形的第一条边\n", &one);
scanf("%lf", &one);
printf("请输入三角形的第二条边\n", &two);
scanf(" %lf", &two);
printf("请输入三角形的第三条边\n", &three);
scanf("%lf", &three);

double area = calc_area(one, two, three);
printf("三角形的面积为%.2lf\n", area);
system("pause"); //等待输入任意字符退出程序
}

6.3 字符

字符型主要是用于表示单个字母,数字和符号以及转义字符,C语言中的字符型使用char表示,表示一个字节的空间,也就意味着可以存储8个bit所能表示的整数(256)。char在内存中有符号(singed)最大值为7f,无符号最大整数值为ff。如果把一个字符当整数输出,输出的是这个字符的ASC||码表的值。

C语言提供了两种输出字符的方式:分别是putchar()和printf(“%c”)

#include <stdio.h>
#include <stdlib.h>

/*
输出字符的两种方式
@author Tony 18601767221@163.com
@since 20160525 16:55
*/
void output_char() {
char c = 'A'; //声明初始化一个字符变量并赋值为A
putchar(c); //使用putchar()函数实现字符内容的输出
printf("\n c=%c",c);//使用printf函数输出字符
}

在C语言中,一个字符变量占据一个字节,但是为了考虑兼容性,一个字符常量占据4个字节(以无符号整数的方式存储),而为了表示中文,采用宽字符(wchar_t)来存储,占据两个字节。

/*
字符的大小
@author Tony 18601767221@163.com
@since 20160525 17:05
*/
void char_size_info() {

char ch = 'A'; //字符占据1个字节
wchar_t wch = L'我'; //表示中文用宽字符,一般是以L开头,宽字符占据两个字节(百度服务器对中文检索会用到宽字符)

printf("%d\t%d\t%d\n", sizeof(ch), sizeof('A'), sizeof(wch));// 字符常量'A'占据4个字节

}

C语言中的字符串默认是以’\0’结尾,英文字符串中的英文字符占据一个字节,中文字符串中的中文字符占据两个字节。

/*
英文和中文字符串占据的大小
@author Tony 18601767221@163.com
@since 20160525 17:09
*/
void string_size() {

printf("空字符串占据的字节数量为%d \n", sizeof(""));//C语言的字符串以'\0'字符结尾 空字符串会占据一个字节
printf("英文字符串\"ABCDEF\"占据的字节数量为%d \n",sizeof("ABCDEF")); //英文字符串中的一个字符占据一个字节,而整个字符串的字节数量会在字节的数量上加1
printf("中文字符串\"万般皆下品唯有读书高\"占据的字节数量为%d\n",sizeof("万般皆下品唯有读书高"));//中文字符串中的汉字占据两个字节
}

ASC||码表规定了字符和数字的映射关系,每个字符都能找到对应的整数编号:

/*
ASC||码表 :字符和数字的映射关系,每个字符都有一个整数编号 '\0'的编号是0
@author Tony 18601767221@163.com
@since 20160525 17:09
*/
void asc_char_num() {

char charA = 'A'; //ASC||码表中规定了大写字符A对应的数字是65
printf("charA=%d\n",charA);
printf("charA=%c\n", charA);

char chara = 'a';//ASC||码表中规定了小写字符A对应的数字是97
printf("chara=%d\n", chara);
printf("chara=%c\n", chara);

char charZero = '0';//ASC||码表中规定了字符0对应的数字是48
printf("charZero=%d\n", charZero);
printf("charZero=%c\n", charZero);

}

通过ASC||码表规定的字符对应的整数编号实现大小写字符替换

/*
字母的大小写转换
@author Tony 18601767221@163.com
@since 20160525 17:54
*/
void upper_lower() {

printf("请输入一个字母\n");
char content = getchar();

if (content>=65&&content<97) {

char lower = content + 0x20;
printf("你输入的是大写字母%c,转换为小写的结果为%c\n",content, lower);
}

else if (content>=97&& content <=123) {

char upper = content - 0x20;
printf("你输入的是小写字母%c,转换为大写的结果为%c\n", content, upper);
}

}

6.4 宽字符

6.5 _Bool类型

_Bool类型是C99(ISO组织发布的C语言标准)后新增的一个数据类型,用于存储常用的关系运算符,逻辑运算符,三目运算符的运算结果(运算结果通常都是真或者假),_Bool类型的取值结果只能为true和false,占据1个字节的大小。

#include <stdio.h>
#include <stdbool.h>

void main() {

_Bool bl = false; //非0 表示true 0表示false
printf("_Bool占据的字节数量为%d\n", sizeof(bl));

bl ? printf("天气很好") : printf("天气很坏");

getchar();

}

7 数据类型转换

整数,浮点数和字符在进行混合运算时会发生自动类型转换,默认的转换规则是自动将低字节的变量类型转换为高字节的变量类型,例如char自动提升为int,这样保证了计算结果的正确,确保数据精度不溢出。

#include <stdio.h>
/*
数据类型的自动转换
@author Tony 18601767221@163.com
@since 20160525 22:53
*/
void auto_convert() {
//自动数据类型转换 在执行算术运算时:小数据类型会转换成大数据类型,保证计算的精度不会造成溢出
char ch = 'A';
printf("%d\n", sizeof(ch));
printf("%d\n", sizeof(ch + 1));//1默认是int 整个结果自动类型转换成int


printf("%.2f",50.0-12+'a');//多个数据参与运算时结果的数据类型是参与计算的最大的数据类型
}

但是生活中也存在着对数据”取整”的要求,例如银行帐户的余额等等,这时就需要使用到强制类型转换来实现。

#include <stdio.h>
/*
数据类型的强制转换
@author Tony 18601767221@163.com
@since 20160525 22:59
*/
void force_convert() {

//强制类型转换
printf("%d", (int)12.8); //在数据类型前面加上(强制转换的类型即可)

printf("\n%f", (float)10);

//定义两个变量
float fl = 10.3;
float ff = 10.8;


int sum = (int)fl + ff;
printf("\nsum=%d\n",sum); //20

sum = (int)(fl + ff);//21
printf("sum=%d\n", sum);
}

通过强制类型转换和算术运算实现对银行帐户金额的分实现四舍五入处理:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
通过强制类型转换实现对账户余额的分进行四舍五入处理
@author Tony 18601767221@163.com
@since 20160525 23:08
*/
void getmoney() {

double balance = 0.0; //初始化账户余额
printf("请输入你当前的账户余额\n");
scanf("%lf",&balance);

double res = (int)(balance * 10 + 0.5) / 10.0;

if (res<balance) {

printf("账户余额的分小于5分可以执行转账,转账的金额为%f\n元",balance-res);
}
else {
printf("账户余额的分大于等于5分,不能执行转账操作,账户的余额为%.2f\n",balance);
}


}

在将高字节的值强制转换为低字节变量类型时,可能会造成数据精度溢出的原因是什么呢?

首先必须明确高字节(这里以int为例)和低字节(这里以short为例)在内存中存储的机制。

#include <stdio.h>
/*
数据类型溢出的原理
@author Tony 18601767221@163.com
@since 20160525 12:08
*/
void convert_error() {
int intVal = 256; //初始化一个整数变量
char charVal = (int)intVal; //将int强转为char后进行赋值
printf("charVal=%d\n",charVal);//输出结果为0
}

下面分析下原因:

char所能表示的取值范围是0-255之间的256个数字,转换为二进制表示就是0000 0000 -1111 1111之间的数字。 
256的二进制表现形式为1 0000 0000,因此超过了char所能存储的数据范围,只能截取0000 0000,所以结果就是0了。

欢迎扫描下方的二维码,关注微信公众服务号-艺无止境,分享IT技术干货。 
C语言基础程序设计