C语言基础程序设计

理解计算机信息存储和传输换算单位

读完本章你将收获以下几点

  1. 计算机信息存储和换算的单位
  2. 理解国内的网速和硬盘”真实”的计算方式

计算机信息存储换算单位

目前的单台PC服务器内存普遍达到了32-128G,笔记本(面向开发者或者设计人员)的内存已经达到16-32G,而手机的内存也普遍都在4-8G之间。那么1G到底有多大呢?这里有个换算公式,如下所示

1G=1024MB

1MB=1024KB

1KB=1024B

1B=8bit

其中B表示字节(Byte),也就是计算机最常用的基本存储单位,1个字节能存储8个位(bit),而一个位(bit)能够存储一个二进制数字(0或者1),位是度量计算机数据的最小单位

而日常使用的移动硬盘(以500G为例),通常实际使用的存储空间只有465G,因为计算机的存储单位是以2^10=1024换算的,而硬盘开发商是以10^3=1000来计算的,计算方式如下所示

计算机存储:500G=500*1024MB*1024KB*1024B

硬盘开发商:500G=500*1000*1000*1000

计算结果:500*1000*1000*1000/1024/1024/1024≈465G

计算机信息传输换算单位

家里的网络带宽是100Mb,但是实际的下载速度约等于12.5M,因为计算机的基本存储的单位为字节(Byte),而宽带产商们使用的位(bit)作为传输单位,因此通常需要根据宽带产商提供的数据除以8就是更加真实的下载速度。

以下是100Mb的下载速度的计算公式

100Mb=100*1024KB*1024B/8=12.5MB

完整的计算机信息存储换算单位

1 Byte(B) = 8 bit

1 Kilo Byte(KB) = 1024B

1 Mega Byte(MB) = 1024 KB

1 Giga Byte (GB)= 1024 MB

1 Tera Byte(TB)= 1024 GB

1 Peta Byte(PB) = 1024 TB

1 Exa Byte(EB) = 1024 PB

1 Zetta Byte(ZB) = 1024 EB

1Yotta Byte(YB)= 1024 ZB

1 Bronto Byte(BB) = 1024 YB

1Nona Byte(NB)=1024 BB

1 Dogga Byte(DB)=1024 NB

1 Corydon Byte(CB)=1024DB

目前Google,Apple,Amazon,Microsoft,BAT、新美大、小米、滴滴、平安等公司的数据已经积累到EB级别(1024PB)甚至是ZB级别

变量的使用及其CPU内存原理

读完本章节你将收获以下几点

  1. 理解变量的作用
  2. 变量的命名规范
  3. 变量的使用以及编译器的处理
  4. 变量在内存中的存储
  5. 数据在内存中的排列方式
  6. 变量赋值的注意事项
  7. 变量赋值的CPU原理
  8. 变量交换的三种方式

变量的作用

程序运行时会被加载到内存,而内存就是编址存储指令和数据的房间。

变量故名思议就是变化的值,生活中无处不在的变量,例如股市的上升下降,天气温度的变化,人民币升值贬值,游戏中人物的生命值等等。而变量的本质就是容器,这个容器用于存储计算机根据不同的业务逻辑执行对应运算后的结果,程序员通过操作变量就可以方便操作内存中的数据。

变量的命名规范

变量的名字必须是一个合法的标识符即可,但是为了增加程序的可读性以及便于后期的维护,还需要遵守以下规范

  1. 变量可以由字母(大小写)+数字+下划线组成,建议命名知意,例如age,count,max_value等等
  2. 变量不能以数字开头,不能是C语言的关键字(例如void ,struct ,reigster,int等等),在使用VisualStudio2017开发C程序时,蓝色的都是关键字。
  3. 变量名区分大小写,因此name和NAME是完全不同的变量
  4. 编译器的差别:VC的变量名、函数名可以支持中文,而GCC编译器是不支持中文的,不过不建议使用中文的变量名或者函数名,对于老版本的编译器(GCC)如果没有开启C++11支持或者VC2010之前的版本,变量必须在函数调用之前定义。

如下应用程序所示,展示了C语言的变量命名规则。

/*

    变量的命名案例
    @author tony [email protected]
    @date 2017/10/31 17:18
    @website www.ittimeline.net
*/
int main() {


    /*合法的变量标识符*/
    int num;
    const int age;
    int _num;

    int emp_name; // linux系统命名风格

    int EMP_NAME; //Windows系统命名风格
    return 0;

}

变量的使用

C语言强制要求在使用变量前,必须先声明,并完成初始化赋值后,再使用变量。

变量的声明非常简单,程序案例如下所示,只需要指定变量的数据类型变量名称即可,数据类型可以是C语言支持的数据类型,例如后期学习的基本数据类型,数组,结构体等等,变量的命名遵守下面的规范即可。如果需要声明多个变量,它们之间需要使用逗号(,)隔开,然后以分号(;)结尾,如下应用案例所示。

#include <stdio.h>
/*
   变量的声明
   @author tony ittimeline.net
   @date 2017/11/11 15:03
   @website www.ittimeline.net
*/
int  main() {

    int age=29;
    int one, two, three; //声明3个整数变量,注册到系统创建的变量表中

    //printf("one=%d\ttwo=%d\tthree=%d\n",one,two,three); //VisualStudio2017中编译错误,变量必须正确的初始化后才能使用
    return 0;
}

编译器如何处理程序中的变量

通过声明变量,编译器可以建立变量符号表,维护变量的名字、类型信息,如下表格所示描述了编译器维护变量的信息

变量地址 变量类型 变量名
007FF9DC int age

这样如果使用了未声明的变量,则直接在编译期间发生错误,指定变量的类型可以指示系统分配多少内存空间,同时指示系统如何存储空间中的值,明确了该值的取值范围(例如short的表示范围为-32768-32767之间),以及不同数据类型可以执行不同的操作,例如整数可以求余数(%)。

变量初始化的原因

在VisualStudio2015中,变量如果不完成初始化赋值的化会被随机分配一个垃圾值。如果使用这个垃圾值会造成程序逻辑错误。

在VisualStudio2017中,变量声明后应该立即完成初始化,否则会发生编译错误。

一个良好的使用变量的方式就是声明后立刻赋初始值 例如 int age =29;

变量在内存中如何存储

当在程序中声明了一个变量并给其赋值时,编译器会开辟一段内存用来存储变量的地址以及对应的变量值,如下表格所示。

内存地址 内存空间(存储数据或者指令)
007FF9DC 29

可以使用C语言的printf函数通过传递字符串参数 “%p”来实现查看变量的内存地址,程序案例如下所示。

#include <stdio.h>
/*
    通过printf函数查看变量的内存地址
    @author tony [email protected]
    @date 2017/10/31 16:07
    @website www.ittimeline.net
*/
int main() {

    int num = 10;

    printf("整数变量num的地址为%p\n",&num);//查看变量num的内存地址
    //修改整数变量num的值
    num = 12;
    printf("整数变量num修改之后的值为%d\n",num);
    getchar();
    return 0;

}

同时可以通过VisualStudio2017提供的强大的调试功能,观察变量在内存中的存储,如下图所示,通过下断点后运行程序,借助printf()函数获取num变量的内存地址,然后根据这个地址就可以获取对应的变量值,变量地址和变量值是一一对应的。

而如果修改变量的值,以num=12为例, 也就意味着12这个整数值会覆盖之前的整数值10,同一时刻,变量中只能存放一份值,后者会把前者覆盖。
变量在内存中的存储示例

数据在内存中的排列方式

首先明确一点,计算机的最底层都是以二进制的方式存储数据,那么二进制数据在内存中是如何排列的呢?

这里先将计算机分成手机、电脑和服务器(Unix)两类

手机和电脑为了节约内存的存储,考虑存储单位优化,通常是低位在低字节,而服务器(以Unix为例子)为了寻址速度的提升(先找数据类型单元,在进行数据匹配动作),通常是低位在高字节。

这里采用VisualStudio2017的调试功能,查看数据在电脑上的内存排列方式。
低位排在低字节

变量赋值的注意事项

变量的赋值使用“=”来实现的,它的含义就是把一个字面量值(例如10,3.14等等)赋值给已经声明过了的变量。

变量都是存储在内存中,操作变量实际上就是操作内存。而C语言只能操作内存,汇编语言既能操作内存,又可以操作寄存器。

#include <stdio.h>
/*
    赋值的特点
    1 C语言赋值的对象必须是声明的变量,汇编语言可以是寄存器
    2 变量都是存储在内存中,操作变量就是操作内存
    @author tony [email protected]
    @date 2017/10/31 16:42
    @website www.ittimeline.net
*/
int main() {


    int init = 3; //初始化 第一次赋值操作,只能给变量进行赋值操作,变量处于内存中
//    init + 1 = 4; //init+1处于寄存器中,C语言没有操作权限,汇编语言具有操权限。

    int height = 10;
    printf("height变量的内存地址为%p\n",&height);

    height = height+ 1;
    height = height+ 2;

    getchar();
    return 0;

}

如下图所示,结合VisualStudio2017的调试功能查看寄存器执行加法运算的过程
变量在寄存器中的运算

变量赋值的CPU内存原理

内存中只能存储变量,而变量的赋值(赋值也是一种运算,后期学习运算符时会深入了解)以及各种表达式的运算都是由CPU的寄存器完成的。接下来使用一段汇编语言的代码实现变量的赋值以及变量的加法运算,以便读者更深入的了解寄存器在变量赋值、算术运算时发挥的作用。

#include <stdio.h>
#include <stdlib.h>
/*
    变量赋值的原理
    @author tony [email protected]
    @date 2017/11/12 15:48
    @website www.ittimeline.net
*/
int  main() {

    int num;
    printf("num的变量地址是%p\n", &num);
    num = 10; //赋值操作是在CPU寄存器内部完成的

    //printf("获取常量10的地址 %p",&10); //10这个常量在被操作的时候,其实是存储在寄存器中 由于C语言不具备操作寄存器的权限,因此取地址无法进行操作


    /*使用汇编语言实现寄存器的赋值*/
    __asm {

        mov eax, 20 //将代码区符号表当中的常量移植到寄存器eax中
        mov num, eax //在寄存器中完成赋值操作,用于将寄存器当中的常量赋值给内存当中的常量
    }

    printf("寄存器赋值完成以后整数变量num的值为%d\n", num);

    /*使用汇编语言实现变量的加法运算*/

    __asm {
        mov eax, num //将内存当中变量num的值读取到CPU的寄存器eax中
        add eax, 5//将寄存器当中读取的数据从代码区符号表当中移植移植到寄存器的数据进行加法操作
        mov num, eax//将寄存器中进行加法操作之后的最终数据赋值给内存中的变量num

    }

    printf("寄存器完成变量相加后整数变量num的值为%d\n", num);

    system("pause");

    return 0;


}

变量的交换的三种方式

基于中间变量实现两个变量交换数据的实现

该算法的特点是增加了空间,节省了计算时间(三次赋值实现)

#include <stdio.h>
#include <stdlib.h>
/*
    变量的交换
    基于中间变量实现交换
    增加了空间,节省了计算时间
    @author tony [email protected]
    @date 2017/11/12 19:50
    @website www.ittimeline.net
*/
void varriable_swap_with_tmp(int left,int right) {

    printf("varriable swap with tmp \n");

    printf(" swap before left =%d\tright=%d\n",left,right);
    int tmp = left;
     left = right;
     right = tmp;

    printf(" swap after left =%d\tright=%d\n", left, right);
    system("pause");

}

基于算术运算实现两个变量的数据交换实现

该算法的特点是增加了计算时间(三次赋值,三次算术运算),节省了空间

使用算术运算时还需要注意数据溢出的问题

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

/*
    使用算术运算实现变量的交换(注意数据溢出)
    节约了空间,浪费时间
    @author tony [email protected]
    @date 2017/11/12 19:55
    @website www.ittimeline.net
*/
void varriable_swap_with_alg(int left,int right) {

    printf("varriable swap with alg \n");
    printf(" swap before left =%d\tright=%d\n", left, right);

    left = left + right;
    right = left - right;
    left = left - right;
    printf(" swap after left =%d\tright=%d\n", left, right);
    system("pause");

}

变量交换的最佳实现

通过亦或运算符,既不需要中间变量实现数据交换,又不需要考虑数据溢出的问题

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

/*
    使用亦或实现变量的交换
    @author tony [email protected]
    @date 2017/11/12 19:55
    @website www.ittimeline.net
*/
void varriable_swap_with_xor(int left,int right) {
    printf("varriable swap with xor \n");
    printf(" swap before left =%d\tright=%d\n", left, right);
    left = left^right;
    right = left^right;
    left = left^right;
    printf(" swap after left =%d\tright=%d\n", left, right);
    system("pause");


}

常量

读完本章节你将收获以下几点

  1. 理解常量的概念
  2. 常量的两种使用方式
  3. 常量的应用场景

理解常量的概念

常量就是一旦初始化后不能直接修改的变量。

而生活中的常量也是无处不在,例如每周都有7天,7就是一个整数常量。

常量使用的两种方式

C语言中的常量可以使用const修饰的变量和#define CONST_NAME CONST_VALUE两种方式来表示常量,_

使用const定义常量不能直接修改,但是可以借助C语言的指针获取内存地址实现修改const常量,const常量存储在内存,const常量的应用案例如下程序所示。

#include <stdio.h>
/*

    const使用
    const修饰的变量为常量,不能直接修改,但是可以通过指针修改
    @author tony [email protected]
    @date 2017/10/31 17:49
    @website www.ittimeline.net
*/
int main() {


    const int num = 99;
    printf("num = %d\n",num); //num =99 不能直接修改
    printf("num的地址是%p\n",&num);
    //* 用于根据地址取出指针指向变量的内容
    //(int *) 常量地址类型转换为变量地址类型
    *(int *)(&num) = 98;
    printf("修改之后num = %d\n", num);
    getchar();
    return 0;


}

而使用#define定义的常量则无法直接修改,也无法通过指针来修改,因为#define定义的常量位于CPU内部的寄存器,采用#define定义常量的应用案例如下所示

#define PERSON_OF_NAME "tony"
 //定义字符串常量 

/*
    #define用于声明宏常量,格式为 #define NAME value
    #define声明的常量是真正意义上的常量,一次赋值就永远无法修改
    @author tony [email protected]
    @date 2017/10/31 17:58
    @website www.ittimeline.net
*/
int main() {

    //表达式必须是可以修改的左值
    //PERSON_OF_NAME = "Tom";// 对宏常量进行再次赋值 直接报错
    getchar();
    return 0;

}

除此以外,#define还可以用作代码混淆的功能,实现案例如下所示

首先定义头文件define.h,内容如下

#define 返回整数 int
#define 主方法 main
#define 参数列表 ()
#define 左大括号 {
#define 调用计算器 system("calc");
#define 暂停 system("pause");
#define 又大括号 }

定义源文件define.c,内容如下

#include "define.h"

返回整数 主方法 参数列表 左大括号
调用计算器
暂停
又大括号

然后运行define.c,程序的运行结果显示可以调用windows平台的计算器,然后显示按任意键后退出。

常量的应用场景

由于常量值不可变特性,因此通常是定义在方法之外,由其他方法通过常量名字直接调用,而使用常量的好处就是意义明确,可以做到批量修改,当多个方法都使用了常量时,如果修改了常量的值,那么其他调用常量的方法都是获取的修改后的常量值。

常用进制及其转换

读完本章节你将收获以下几点

  1. 了解常用的进制类型以及应用场景
  2. 常用进制的转换以及编程实现

了解常用的进制类型

在计算机内存中,都是以二进制(01001)的补码形式来存储数据的,而在生活中以十进制方式计算的数据居多,例如账户余额,薪水等等。而计算的内存地址通常都是使用十六进制展示的,Linux系统的权限系统采用八进制的数据运算。相同进制类型数据进行运算时会遵守加法:逢R进1;减法:借1当R,其中R就表示进制

如下表格是它们的组成、示例和使用场景:

进制名称 组成 数值示例 典型使用场景
二进制 0,1 0101 内存数据存储
八进制 0-7之间的8个整数 012(以0开头) linux权限
十进制 0-9之间的10个整数 12 整数
十六进制 0-9,a-f之间的10个数字加6个字母 12f 数据的内存地址

如下应用案例就是八进制、十六进制和十进制的变量使用,需要注意的是C语言中的整数默认就采用十进制来表示,而且c语言没有提供二进制的数据表现形式。

#include <stdio.h>

/*

    整数常见的几种进制类型
    @author tony [email protected]
    @date 2017/10/31 20:27
    @website www.ittimeline.net
*/

int main() {

    //八进制:0开头 0-7之间的八个数字表示
    int oct_val = 017; //15
    //十六禁止:0x开头0-9,a-f之间的十个数字加上6个字母
    int hex_val = 0x12;//18
    //C语言不支持声明2进制的变量,这里是十进制的值
    int binary_val = 101001;

    printf("oct_val = %d\t hex_val =%d,binary_val=%d\n",oct_val,hex_val,binary_val);

    system("pause");

    return 0;
}

进制之间的转换

在某些场景下(例如面试)需要完成常用进制之间的数据转换

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

根据小学数学的逻辑可以知道2^3=8,2^4=16, 它们三者之间可以这样换算

二进制转八进制:在转换时,从右向左,每三位一组(不足三位用0补齐),转换成八进制。

二进制和八进制的对应关系如下表:

二进制 八进制
000 0
001 1
010 2
011 3
100 4
101 5
110 6
111 7

例如 1010转换为八进制的结果为12

二进制和十六进制的对应关系如下表:

二进制 十六进制
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 a
1011 b
1100 c
1101 d
1110 e
1111 f

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

例如 1010转换为八进制的结果为a

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

二进制、八进制、十六进制转换成十进制都是采用按权相加的形式计算的。

先看一个整数计算的案例:

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

因此可以采用按权相加的计算方式将二进制、八进制、十六进制转换为十进制

二进制转换为十进制:

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

八进制转换为十进制

27=2*8^1+7*8^0=16+7=23

十六进制转换为十进制

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

十进制转二进制

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

计算过程如下:

22/2 余 0
11/2 余 1
5 /2 余 1
2/2 余 0
1/2 余 1
0 商数为0

如下所示,采用编程实现十进制转二进制案例

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
     0-32767之间的十进制转换成二进制:除以2取余,直到商数为0,逆序排列
     @author tony [email protected]
     @date 2017/11/12 21:23
     @website www.ittimeline.net
*/
int main() {
    int numbers[16] = {0}; //初始化一个16位整数数组,用于保存二进制整数
    printf("请输入一个需要转换成二进制的十进制正整数,取值范围在0-32767之间\n");

    int value = 0; //待转换的十进制整数
    scanf("%d",&value); //读取用户输入的整数 
    for (int i = 0; i < 15; i++) { //十六位二进制数,最高符号位为正整数

        int quotient = value/2; //商数
        int remainder = value%2; //余数
        value = quotient; //将除以2之后的商数赋值给value
        numbers[i] = remainder;//将余数存储在数组中

    }

    printf("十进制%d对应的二进制整数为",value);
    //逆序输出二进制
    for (int i = 15; i >= 0;i--) {
        printf("%d ",numbers[i]);
    }

    printf("\n");
    system("pause");
    return 0;
}

使用C语言的库函数实现进制的转换案例:

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

/*

    使用C语言的库函数_itoa实现常用的进制转换
    @author tony [email protected]
    @date 2017/11/26 13:10
    @website www.ittimeline.net
*/
int main() {

    printf("请输入需要转换二进制的十进制整数\n");
    int num = 0;
    scanf("%d", &num);
    char str[64] = { 0 };
    _itoa(num, str, 2);

    printf("%d转换为二进制的结果是%s\n", num, str);


    _itoa(num,str,10);
    printf("%d转换为十进制的结果是%s\n", num, str);


    _itoa(num, str, 8);
    printf("%d转换为八进制的结果是%s\n", num, str);


    _itoa(num, str, 16);
    printf("%d转换为十六进制的结果是%s\n", num, str);

    system("pause");

    return 0;
}

猜你喜欢

转载自blog.csdn.net/ittechnologyhome/article/details/79915887