A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

© tingyuyisheng 中级黑马   /  2015-7-20 21:09  /  1590 人查看  /  10 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

学习目标

1.【掌握】include预处理指令

2.【掌握】多文件开发

3.【了解】认识进制

4.【掌握】进制之间的互相转换

5.【掌握】原码,反码,补码

6.【掌握】位运算

7.【掌握】int类型的修饰符

一、include预处理指令

其实我们早就有接触文件包含这个指令了, 就是#include,它可以将一个文件的全部内容拷贝另一个文件中。

使用语法:

第一种:#include <文件名>

直接到C语言库函数头文件所在的目录中寻找文件

第二种:#include "文件名"

系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找

使用注意:

#include指令允许嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.h,b.h 包含 a.h。下面是错误的用法:

使用#include指令可能导致多次包含同一个头文件,降低编译效率,比如下面的情况:

在one.h中声明了一个one函数;在two.h中包含了one.h,顺便声明了一个two函数。(这里就不写函数的实现了,也就是函数的定义)

假如我想在main.c中使用one和two两个函数,而且有时候我们并不一定知道two.h中包含了one.h,所以可能会这样做:

编译预处理之后main.c的代码是这样的:


[color=rgb(170, 170, 170) !important]1

[color=rgb(170, 170, 170) !important]2

[color=rgb(170, 170, 170) !important]3

[color=rgb(170, 170, 170) !important]4

[color=rgb(170, 170, 170) !important]5

[color=rgb(170, 170, 170) !important]6


[color=rgb(128, 0, 128) !important]void[color=rgb(0, 111, 224) !important] [color=teal !important]one[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(128, 0, 128) !important]void[color=rgb(0, 111, 224) !important] [color=teal !important]one[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(128, 0, 128) !important]void[color=rgb(0, 111, 224) !important] [color=teal !important]two[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(128, 0, 128) !important]int[color=rgb(0, 111, 224) !important] [color=teal !important]main[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]    return[color=rgb(0, 111, 224) !important] [color=rgb(0, 153, 153) !important]0[color=rgb(51, 51, 51) !important];
[color=rgb(51, 51, 51) !important]}



10 个回复

倒序浏览
第1行是由#include "one.h"导致的,第2、3行是由#include "two.h"导致的(因为two.h里面包含了one.h)。可以看出来,one函数被声明了2遍,根本就没有必要,这样会降低编译效率。

为了解决这种重复包含同一个头文件的问题,一般我们会这样写头文件内容:

QQ20150703-1

大致解释一下意思,就拿one.h为例:当我们第一次#include "one.h"时,因为没有定义_ONE_H_,所以第9行的条件成立,接着在第10行定义了_ONE_H_这个宏,然后在13行声明one函数,最后在15行结束条件编译。当第二次#include "one.h",因为之前已经定义过_ONE_H_这个宏,所以第9行的条件不成立,直接跳到第15行的#endif,结束条件编译。就是这么简单的3句代码,防止了one.h的内容被重复包含。

这样子的话,main.c中的:


1
2
#include "one.h"
#include "two.h"
就变成了:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// #include "one.h"
#ifndef _ONE_H_
#define _ONE_H_

void one();

#endif

// #include "two.h"
#ifndef _TWO_H_
#define _TWO_H_

// #include "one.h"
#ifndef _ONE_H_
#define _ONE_H_

void one();

#endif

void two();

#endif
第2~第7行是#include "one.h"导致的,第10~第23行是#include "two.h"导致的。编译预处理之后就变为了:


1
2
void one();
void two();
这才是我们想要的结果
回复 使用道具 举报
二、多文件开发
大家都知道我们的C程序是由1个1个的函数组成的,当我们的程序很大的时候,将这些函数代码写在写在同1个文件之中是绝对不科学。函数太多,不方便管理,并且不利于团队开发。

我们的程序实际上都是分为1个1个的模块的,无论多大的程序都是由1个1个的小功能组成的。模块就是功能相同或者相似的一些函数。在实际开发中,不同的人负责开发不同的功能模块,要使用模块中的功能的话,直接调用就可以了。

如何写模块?

写模块的人,一般情况下要写两个文件。.c文件  .h文件. header 头文件。

.h文件之中,写上函数的声明。

.c文件之中,写上函数的实现。

想要调用模块之中的函数,只需要包含这个模块的头文件就可以了。比如我们使用printf函数需要包含stdio.h头文件一样,只要包含了函数的声明,我们就能直接使用函数了。

例如:

QQ20150703-2 QQ20150703-3 QQ20150703-4

我们还能给函数分组,例如:

QQ20150704-1

右键,选择New Group可以创建组,进行源文件分组管理。放在组里的源文件其实他的路径是不会改变的:

51FC2DB3-56EA-49FA-A51A-85A907EBF9E5

三、认识进制
什么是进制?

进制是记数的一种方式,侧重点在于记数的时候,是逢多少进一。比如我们日常生活中用的十进制,逢10进1。C语言中也有进制,C语言能识别的进制有二进制,十进制,八进制,十六进制。多少多少进制就是逢多少进1。

二进制:

逢二进一,每1位用0和1表示。

在C语言的代码中,如果要写1个二进制的数,那么就必须要在这个二进制的数的前面加1个0b的前缀。

C语言没有提供1个个是控制符来将1个整形变量中的数据以二进制的形式输出。

八进制:

逢八进一,每1位 0、1、2、3、4、5、6、7中的任意1位来表示。

在C语言之中,如果要写1个八进制的数,那么就必须要在这个八进制的数的前面加1个前缀0。

%o 会将整形变量中的数据以八进制的形式输出。

十进制:

逢十进一,每1位 0 1 2 3 4 5 6 7 8 9 中的任意一位,逢十进一。

在C语言之中直接写1个整数,默认就是十进制。

%d 是将整形变量中的数据以十进制的形式输出。

十六进制:

逢十六进以,每1位 0 1 2 3 4 5 6 7 8 9 a b c d e f 中的任意1位来表示。

如果我们要在C语言中写1个十六进制的数 那么就必须要在这个数的前面加1个前缀0x。

使用%x 将整形变量中的数据以十六进制的形式输出。

例如:


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, const char * argv[]){
   
    int num = 0x13adf0;//加0x表示十六进制
   
    printf("num = %x\n",num);//%x以十六进制形式打印
   
    return 0;
}
四、进制直接的互相转换
我们先来引入一个概念,当然,C语言中没有规定这些,是便于学习者进行按位运算而自己定义的概念。

数码:一个数的每一位数字,就叫做数码。

数位:数码在这个数中的位置,从右到左,从0开始增长。

基数:每一位数码最多可以由多少个数字来表示,多少进制就是多少基数。

位权 = 数码 * (基数的数位次方)

进制之间的转换:

十进制转二进制:除2取余,直到商为0,再余数倒序

十进制转八进制:除8取余,直到商为0,再余数倒序

十进制转十六进制:除16取余,直到商为0,再余数倒序

二进制转十进制:每一位的位权相加

八进制转十进制:每一位的位权相加

十六进制转十进制:每一位的位权相加

二进制转换八进制:3合1,低位到高位,每3位分成一组,高位不够补0,求出每一组的10进制,再相连

八进制转二进制:3拆1,将八进制的每1个数码,拆成1个三位的二进制,再将这些二进制连起来

二进制转十六进制:4合1,低位到高位,每四位分成1组,高位不够补0,求出每1组的10进制,再相连

十六进制转二进制:1拆4,将十六进制的每1个数码,拆成1个四位的二进制1再将这些二进制连起来

八进制转十六进制:八进制 -> 二进制 ->十六进制

打印二进制的函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

//传入一个整数,打印他的二进制
void printErJinZhi(int num){
    //定义一个临时变量temp,储存位移后的数据
    int temp = 0;
    //定义一个临时变量temp1,储存按位与后的二进制最低位数值
    int temp1 = 0;
    for (int i = 0; i < 32; i++) {
        //先位移,截取数据
        temp = num >> (31-i);
        //再与1按位与,因为任何数与1与都能得到那个任何数的二进制的最低位
        temp1 = temp & 1;
        //取出一位打印一位
        printf("%d",temp1);
    }
    printf("\n");
}

int main(int argc, const char * argv[]) {
   
    //调用函数打印出整数的二进制
    printErJinZhi(100);
   
    return 0;
}
自己随意写的,网上还有很多功能更多的进制转换函数,需要的自己去谷歌吧。

五、原码,反码,补码
声明1个变量,其实就是在内存之中申请指定字节数的空间,用来存储数据。无论任何数据在内存之中都是以其二进制的形式存储的,并且是以这个数据的二进制的补码的形式存储的。那什么是补码呢?原码、反码、补码 都是二进制,只不过是二进制的不同的表现形式。

强调:所有的数据都是以其二进制的补码的形式存储在内存之中的。

原码:

最高位用来表示符号位,0代表正,1代表负。其他叫数值位,数值位是这个数的绝对值的二进制位。


1
2
3
9的原码: 00000000 00000000 00000000 00001001

-3的原码:10000000 00000000 00000000 00000011
反码:

正数的反码就是其原码。负数的反码,是在其原码的基础之上,符号位不变,数值位取反。


1
2
3
4
5
6
7
9的原码: 00000000 00000000 00000000 00001001

9的反码: 00000000 00000000 00000000 00001001

-3的原码:10000000 00000000 00000000 00000011

-3的反码:11111111 11111111 11111111 11111100
补码:

正数的补码就是,其原码。负数的补码,是在其反码的基础之上加1。


1
2
3
4
5
6
7
8
9
10
11
9的原码: 00000000 00000000 00000000 00001001

9的反码: 00000000 00000000 00000000 00001001

9的补码: 00000000 00000000 00000000 00001001

-3的原码:10000000 00000000 00000000 00000011

-3的反码:11111111 11111111 11111111 11111100

-3的补码:11111111 11111111 11111111 11111101
为什么要用补码来储存数据,因为计算机之中只有加法,没有减法。为了更低成本的计算出结果,所以使用补码来存储数据。如下例子:3 + (-2) = 1


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
原码计算:
00000000 00000000 00000000 00000011
10000000 00000000 00000000 00000010 相减
------------------------------------
10000000 00000000 00000000 00000101  这个结果不对. 已经变成负数了.


反码计算:
00000000 00000000 00000000 00000011
11111111 11111111 11111111 11111101 相减
-------------------------------------
00000000 00000000 00000000 00000000   0 这也是错的.


补码计算:
00000000 00000000 00000000 00000011
11111111 11111111 11111111 11111110 相减
-------------------------------------
00000000 00000000 00000000 00000001   1   1结果是对.
回复 使用道具 举报
很详细!赞一个!
回复 使用道具 举报

怕版乱了。。。
回复 使用道具 举报
总结的很好啊啊
回复 使用道具 举报
顶一个!!!
回复 使用道具 举报

大神一起学习。。
回复 使用道具 举报
谢谢整理
回复 使用道具 举报
加油啊~~~~~~~~~
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马