# C语言

# Hello world!

#include <stdio.h>
 
int main()
{
/*输出Hello world!*/
    printf("Hello world! \n");
    return 0;
}

实例解析:

  • 所有的 C 语言程序都需要包含 main() 函数。,main()表示主函数 ,代码从main() 函数开始执行,一个程序有且只能有一个main()函数的存在。
  • /* ... */ 用于注释说明,它本身并不执行,如果没有也不影响程序执行。但为了之后看代码方便,建议养成写注释的习惯。// 也可以用于注释说明,在//后的所以内容计算机都不理睬,但仅限这一行,如果换行就不算注释,如果注释多或者需要换行可以用/* ... */ 你只需要把注释写在里面就行

示例:

/*这里
是
注释*/

//这是注释
  • stdio.h 是一个文件 (标准输入输出头文件) 文件后缀为.h,h代表单词head(头) 所以也叫头文件。
  • #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf()函数时,如果没有找到stdio.h头文件,会发生编译错误。这是因为<stdio.h>是一个C 标准库,printf()运行需要它的支持。
  • printf() 用于格式化输出。printf() 函数在 "stdio.h" 头文件中声明。
  • int 表示数字格式,返回一个数字
  • return 用于表示结束函数。在这里return 0;的意思是返回一个整数0,因为它是int(整数)类型,所以只能返回整数。这里的0相当于一个状态,我们通过有没有返回0来判断函数是否成功执行,如果返回0则表示无错误,其他值代表有某种错误。每一个函数理论上都是有执行成功与执行异常的, 每一个函数应该带返回值的,这样方便给调用者更多的信息来判断程序的状态。当然如果没有这行代码程序也可以执行,但如果你把一些代码写在了return 0;之后,那么就会出现执行只执行reture 0; 之前的代码,不执行reture 0;之后的代码的情况。
  • 在代码printf("Hello world! \n");中\n表示换行
  • main()前面是函数类型; void main()是无类型函数,不需要返回值; int main()是整型类型函数;需要数值返回值0,即return 0
  • 在C语言中每行代码后面都要用;结束,不然计算机不知道你结束了,会出错

C语言对格式没有太高要求,就算你把代码写成这样,也依然可以正常执行

#include <stdio.h>
int main(){printf("Hello world! \n");return 0;}

注意:C语言时从头到尾一行一行执行的,在同一行代码里也是从前往后执行的

# 输入与输出

# 输出整数

#include <stdio.h>
int main()
{
    printf("%d",12);
    return 0;
}

%d表示输出后面的整数,例如运行以上代码输出:

12

我们还可以在printf()里做运算,例如

#include <stdio.h>
int main()
{
    printf("%d",1+2);
    return 0;
}

运行后,输出:

3

# 输出浮点型数据

#include <stdio.h>
int main()
{
    printf("%f",1+2.1);
    return 0;
}

在这里我们使printf()输出的数据为浮点数,运行后输出:

3.100000

注意:输入不同类型的数据时要先说明数据类型,例如整数就要说明是%d浮点数就要说明是%f ,如果输出的数据与说明的数据类型不一致,例如要输出小数1.2,但数据类型是%d时,输出的内容就不会是浮点数,而是一个与此无关错误数字,来表示出错了。

# scanf 输入

如果你需要在程序中输入内容,那你就要用到scanf()函数

运行下面这个程序,如果我们通过scanf()函数向程序里输入2,那么程序就会通过printf()函数输出运算后的结果3

#include <stdio.h>
int main()
{
    int a = 0;//初始化a,这是必要的
    scanf("%d", &a);
    printf("%d", 1 + a);
    return 0;
}

注意:在scanf()函数中要读取的数据前一定要加& 例如 scanf("%d", &price);

再看下面这段程序

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 1;
    scanf("%d %d", &a, &b);
    printf("%d %d",a,b);
    return 0;
}

首先这是一个正确的程序,第一个占位符%d对应a 第二个%d对应b ,这样我们就可以在程序中输入两个值,例如“1 2”这样用空格把两个值分开后输入

我们还可以先输入一个值,回车确认,再输入一个值,回车确认。

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 1;
    scanf("%d %d ", &a, &b);
    printf("%d %d",a,b);
    return 0;
}

如果在scanf()的最后一个占位符%d后多加一个空格,那么当你输入两个值后,按下回车,程序并没有结束,计算机还在等待你输入那个空格的,这时候你需要再输入任意一个字符来填补空格的位置,程序才能结束运行,由于之前已经把两个值都输入了,所以至于空格的位置输入什么都不会影响输出的结果

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 1;
    scanf("%d, %d", &a, &b);
    printf("%d %d",a,b);
    return 0;
}

如果我们像这样scanf("%d, %d", &a, &b);在两个占位符%d中间加一个, 那么当输入“3 4”时输出的却是”3 1“因为, 导致了程序错误,只能输入第一个值,因为它没被影响,但,后面的第二个值不能正常的输入,因为程序中代码int b = 1; 给b赋值为1,所以默认输出初始值1

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 1;
    scanf("abcd%d %d", &a, &b);
    printf("%d %d",a,b);
    return 0;
}

如果在scanf()函数中加入一些其他的字符,例如abcd 那么在输入的时候也要在按照相同的顺序加入字符abcd 。如果输入”abcd 3 4“ 终端如下:

abcd 3 4
3 4
PS D:\C>

正常运行

如果不输入abcd 直接输入“2 3”则终端如下:

2 3
0 1
PS D:\C>

这时输出的不是“2 3”而是“0 1”这是因为这里出现了错误,在scanf()函数中我们输入的内容必须是函数中存在的所有内容,例如在scanf("abcd%d %d", &a, &b); 中 我们输入的内容除了两个占位符%d所对应的a和b外,还要有abcd 这些其他的字符程序才能正确运行,而且必须按照顺序排列,这样才能对应字符,输入abcd 2 3可以,输入2 3 abcd不行

# 整数的格式化输出

占位符以及其对应的数据类型:

占位符 数据类型
%d int
%u unsigned
%ld long long
%lu unsigned long long

看一下这一段代码

#include <stdio.h>

int main()
{
    char c = -1;
    int i = -1;

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

    return 0;
}

按之前的经验,运行后输出的应该也是-1,但实际上输出的却是

c=4294967295 i=4294967295

为什么会这样呢?因为我们输出使用的是%u,所以值以unsigned类型输出在unsigned范围里没有-1,所有就像之前所讲的那样他会输出unsigned范围内最大的值4294967295

再试一试这个代码

int main()
{
    char c = -300;
    int i = -300;

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

    return 0;
}

输出的值为:

c=4294967252 i=4294966996

同样输入的是-300,为什么输出的值不同呢?那是因为我们在初始化的时候输入的值ci的数据类型不同i的数据类型是int,值的范围是2147483648 ~ 2147483647,c的数据类型是char范围是-128 ~ 127,-300并不在这个范围内

**首先,这样超出数据类型范围的程序是错误的,**但如果超过了这个范围也能输出,但并不是乱输出,而是有一定规律的输出,但输出的方式会很奇怪(不重要)

# 输入其他进制

#include <stdio.h>

int main()
{
    char c = 012; //八进制
    int i = 0x12; //十六进制

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

    return 0;
}

输出:

c=10 i=18

我们在输入其他进制的值时在C语言中计算机会自动转化为十进制输出,例如八进制的012输出为10,十六进制的0x12输出为18

# 如果想要输出其他进制的值怎么办?

占位符 数据类型
%o 八进制整数
%x 十六进制整数

我们可以使用这样的占位符来改变输出的数据类型,例如这些代码

#include <stdio.h>

int main()
{
    char c = 012;
    int i = 0x12;

    printf("c=%o i=%x\n", c, i);

    return 0;
}

运行后,输出:

c=12 i=12

这里并不会显示其他进制的值前的那些符号例如八进制的0和十六进制的0x

如果代码是这样

#include <stdio.h>

int main(){

    int i = 0x1A;

    printf("c=%o i=%x\n", c, i);

    return 0;
}

输出:

i=1a

这里输出的a为小写,因为%x是小写的x,如果改为大写的%X则会输出

i=1A
  • 这里的8进制和16进制只是把数字表达为字符串与计算机内部如何表达数学无关

# 格式化输入输出

# printf

%[flags][width][.prec][length]type
flag 含义 width/prec 含义
- 左对齐 number 输出的最小字符数
+ 在字符前添加+ * 取第一个参数为输出的最小字符数
(空格) 如果是正数在值前加一个空格 .number 取小数点后几位
0 用0填充空位 .* 取第一个参数为取小数点后的位数

左对齐

printf("%9d\n", 123);
printf("%-9d\n", 123);

输出结果:

      123
123

%9d表示数据输出占9个字符的空间,但是这是靠右对齐的,所以空格在字符的前面,如果再加上-,就会变成向左对齐的,空格就不在字符前面了

在输出值前显示+

printf("%d\n", -123);
printf("%d\n", +123);

输出结果:

-123
123

使用%d输出时可以输出负号,但没办法输出+,这时就要使用%+d

printf("%+d\n", 123);
printf("%+d\n", +123);

输出结果:

+123
+123

不论字符前是否有+,输出后都会在字符前添加+


  • 也可以输出负号
printf("%+d\n", -123);

输出结果:

-123
  • 这些符号也可以混合使用

printf("%+-9d\n",123);

输出结果:

+123

在正数前留空

printf("% d\n", 123);

输出结果:

 123

在输出的整数前留下一个空格

用0填充空格

printf("%09d\n", 123);

输出结果:

000000123

总共输出了9个字符,除了有效字符外剩余的位置用0填充

注意:占位符中0要在9前面,如果写在后面则表示的是输出占90个字符空间

取小数点后几位

printf("%.2f\n", 12.3);

%.2f表示取小数点后2位

输出结果:

12.30
  • 如果输出值的小数点位数超出了占位符所设定的位数时,会采取四舍五入的方式

自定义取小数点后几位

int i;
scanf("%d", &i);
printf("%.*f\n", i, 12.3);

输入3,输出结果为

12.300

占位符中的*表示把第一个参数填充到*的位置,这里的第一个参数是变量i,输入3后变量i的值就为3,然后把变量i填充到*的位置,占位符为%.3f,表示取小数点后三位

# 类型修饰符
类型修饰符 含义
hh 单个字节
h short
l long
ll long long
L long double

# scanf

%[flag]type
flag 含义
* 跳过
number 最大字符数
hh char
h short
l long或double
ll long long
L long double

跳过

int num;
scanf("%d%d", &num);
printf("%d\n", num);

输入了两次,分别为1和2,输出结果为

2

跳过了第一次输入的值,输出了第二次输入的值

# [...]

$GPRMC,080655.00,A,4546.40891,N,12639.65641,E,1.045,328.42,170809,,,A*60

这是一个GPS模块产生的数据,我们要处理这个数据就要使用[...]

scanf("%*[^,】,%【^,】,%【^,】,%【^,】,%【^,】,%【^,】,%【^,】,%【^,】,%【^,】,%【^,】", sTime,sAV,sLati,&sNW,sLong,&sEW,sSpeed,sAngle,sDate);

%[^,]表示在,之前的内容,%*[^,]表示跳过,之前的内容

# printf和scanf的返回值

  • printf和scanf都有返回值
  • scanf返回读取变量的个数
  • printf返回输出的字符数量

# 变量

如下,这个些代码可以运算100-1并输出结果99,如果我要计算100-2时我就必须重新修改代码

#include <stdio.h>
int main()
{
    printf("%d",100-1);
    return 0;
}

如何在不修改代码的情况下做到呢?那我们就要用到变量。示例如下

#include <stdio.h>

int main()
{
    int price;//定义一个变量price,类型为int(整数类型)
    printf("请输入金额(元):");
    //scanf是一个用于输入的函数,可以在终端里输入值就是它的功劳
    scanf("%d", &price);//输入的值等于price,相当于price=你输入的值,占位符是%d类型
    int change = 100 - price;//定义一个变量,这个变量等于100-你输入的值
    printf("找您%d元。\n", change);//输出内容
    return 0;
}

注意:

1.代码int price; 中这个price变量定义的是int类型(整数类型)所以要输入的时候只能输入整数

2.在scanf()函数中要读取的数据前一定要加& 例如 scanf("%d", &price);

# 赋值

如下,这就是赋值。给price赋予一个值0,price的类型为int

int price = 0;

“=” 是运算符,表示把右边的值赋给左边的变量,由于这是给变量赋予了一个值所以也叫做初始化

上面的这行代码就表示给变量price 设定一个初始值为0

在数学中a=b和b=a是等价的,但在程序设计中a=b和b=a是完全不同,在C语言中a=b就是把b交给a来代表,b=a是把a交给b代来表。

#include <stdio.h>
int main()
{
 int i;
 int j;
 j = i+10;
 printf("%d\n"j)
    return 0;
}

再看上面这些代码,我可以告诉你它是错误的,你知道它错在哪了吗?思考一下

这些代码运行后后输出一些奇怪的数字,因为这里的变量i并没有初始化,代码并不理解j=i+10;是什么意思,我想,你也不理解。

所以我们需要修改一下它,就像这样

#include <stdio.h>
int main()
{
 int i = 0;
 int j;
 j = i+10;
 printf("%d\n"j)
    return 0;
}

这样计算机就知道j=0+10 于是就会输出结果10

# 变量的默认

在之前的代码上稍加改动,给变量price赋予一个值为0

#include <stdio.h>

int main()
{
    int price = 0;//定义一个变量price,类型为int(整数类型)
    printf("请输入金额(元):");
    //scanf是一个用于输入的函数,可以在终端里输入值就是它的功劳
    scanf("%d", &price);//输入的值等于price,相当于price=你输入的值,占位符是%d类型
    int change = 100 - price;//定义一个变量,这个变量等于100-你输入的值
    printf("找您%d元。\n", change);//输出内容
    return 0;
}

我们运行一下它,然后输入一些内容。我们不输入数字,输入一些字母例如hi

结果它既然正常运行并输出 找您100元。,为什么会这样呢?因为我们在代码中对变量price初始化了一个值0,如果输入一堆字母,例如hi,你以为计算机看到的是这样的int change = 100 - hi; 其实它压根就不会鸟你,它会认为是你写错了,然后无视你的输入,用之前变量初始化的值进行计算

int change = 100 - 0;所以输出的结果为100

# 常量

#include <stdio.h>

int main()
{
    int price = 0;
    printf("请输入金额(元):");
    scanf("%d", &price);
    int change = 100 - price;
    printf("找您%d元。\n", change);
    return 0;
}

在这样一段程序中price 是一个变量,因为它的值是可以变化的,我们通过 scanf() 函数可以使price等于我们输入的值。反之,固定不变的量就是常量

代码中的100也是不能改变的,但他是直接写在程序里的所以我们把它叫作直接量

接下来我们把直接量用常量代替,代码如下

#include <stdio.h>

int main()
{
	const int AMOUNT = 100;
	int price = 0;
	printf("请输入金额(元):");
	scanf("%d", &price);
	int change = AMOUNT - price;
	printf("找您%d元。\n", change);
	return 0;
}

# 为什么要使用常量呢?

1.我们可以通过常量的名称来表示这个值,这样我们就知道这个值是干什么的了,如果只是一个100,我们看代码是就有可能不知道这个100是干嘛的,如果换成常量名称我们只需要看名称就可以知道这个值是干嘛的

2.在我们要修改常量的的时刻可以通过名称很快的找到对应的值,不再需要在代码中到处找这个值

# const

const 是一个修饰符,如果把它加在一个变量前面则表示这个值就不能再变了,所以他就成了一个常量 ,例如const int AMOUNT = 100;

如果你定义了一个常量后再去给它赋值则会出现错误,代码如下

#include <stdio.h>

int main()
{
	const int AMOUNT = 100;
	AMOUNT = 90;
	return 0;
}

这里常量名称AMOUNT大写是仅仅是为了强调他是一个不能变的常量,换成小写也不影响代码运行

# 数据类型

首先先了解一下C语言中数学运算基本符号

+代表加 -代表减 *代表乘 /代表除

然后看下面这个程序

#include <stdio.h>

int main()
{
    printf("%d",12/4);
    return 0;
}

输出:

3

好的,没什么有问题

再运行一下这个程序

#include <stdio.h>

int main()
{
    printf("%d",13/4);
    return 0;
}

输出:

3

输出怎么还是3?13÷4不是应该等于3.25吗?

我们再继续尝试一下,看一看问题出在哪里?

#include <stdio.h>

int main()
{
    printf("%d",15/4);
    return 0;
}

输出:

3

怎么还是3?因为我们在输出的时候用的占位是%d,这个占位符表示输出的值是整数类型,所以这几次运行输出的值都是3,而且在C语言中,输出整数的方式简单粗暴,它并不采样四舍五入的方式,而是直接把小数点后的所有数都去除,所以不论是3.25还是3.75统统都当作3输出

# 浮点数

在C语言中带小数点的数叫做浮点数,它与不带小数点的数是两种完全不同类型的数,例如10和10.0

虽然都是十,但在C语言中它们完全不同,处理它们的方式也不同

举一个具体的例子:

#include <stdio.h>

int main()
{
    printf("%f",15/4);
    return 0;
}

因为要输出浮点数所以 printf()换成数据类型为浮点数的占位符%f

运行后输出结果竟然为

0.000000

我们再尝试另一种方式

#include <stdio.h>

int main()
{
    printf("%f",15.0/4);
    return 0;
}

运行后输出:

3.750000

这次正常了,我只是把15改成15.0就发生了如此巨大的变化。虽然都是十五,但通过两次运行结果的对比,也能说明浮点数与整数的不同

这也说明要使用占位符%f时,数中一定要有浮点,如果是一个算式,那么算式中一定要有一个数是浮点数

浮点数之所以叫做浮点数是因为浮点数的小数点位置是浮动的,它的小数点位置可以出现在一个数的第几位是可以变的,还有一种数叫做定点数,它小数点的位置是固定的。

# double

#include <stdio.h>

int main()
{
    double foot;
    double inch;
    scanf("%lf %lf", &foot, &inch);
    printf("%f", (foot + inch / 12) * 0.3048);
    return 0;
}

在这些代码中double 表示双精度浮点数,和int一样都是定义数据类型的,只是int定义的类型是整数,而double定义的是浮点数,对于double类型的数输入时要用占位符%lf,输出时用%f

如果double要初始化,那么应该写作double x = 0.0;

# sizeof 给出类型或变量的所占字节数

sizeof()是一个运算符可以给出某个类型或变量的在内存中所占据的字节数,这个符号是静态的并不参与算数运算

#include <stdio.h>

int main()
{
    int a;
    printf("int类型占 %ld 字节\n",sizeof(int));
    printf("变量a占 %ld 字节\n",sizeof(a));
   
    return 0;
}

输出:

int类型占 4 字节
变量a占 4 字节
  • 字节(Byte)是一种单位,用于计量存储容量,1字节=8比特(二进制的位)
  • 各种类型的存储大小与系统位数有关,不同位数中类型的存储大小是不同的
  • 在一个计算机中一般都有CPU(处理器)和RAM(存储器)它们通过总线相连,在CPU中有个寄存器,一般我们说的32位64位就是他的位数,如果是32位则表示寄存器可以32比特的数据,在CPU到RAM间每次传输的数据也是32比特
  • CPU位数 = CPU中寄存器的位数 = CPU能够一次并行处理的数据宽度 = 数据总线宽度
  • 一般int在计算机中占一个寄存器的大小,或者说int想要表达的就是一个寄存器的大小,所有在不同位数的计算机上int的大小也会有不同
  • 在计算机之中一切都是二进制 1字节是8比特,表达范围是0~255用二进制表示就是00000000~11111111

我们如何在二进制中表达负号呢?

00000000是0,00000001是1

00000001+11111111=100000000 在二进制中1+1=10所有要进一位,结果就多了一位,如果计算机中这个数是8比特就会丢掉多出来的一位,所以00000001+11111111=00000000 **→ 1+(-1)=0 ,**在补码的情况下11111111表示负号,在当作纯二进制时表示255

补码:在计算机中补码和原码可以加出一个溢出的0

# 整数类型

类型 值范围
char -128 ~ 127
unsigned char 0 ~ 255
signed char -128 ~ 127
int -2147483648 ~ 2147483647
unsigned int 0 ~ 4294967295
short -32768 ~ 32767
unsigned short 0 ~ 65535
long -2147483648~2147483647
unsigned long 0 ~ 4294967295

举个例子,看一下值范围的差别

#include <stdio.h>

int main()
{
    char a = 127;
    char b = 128;
    char c = 255;
    char d = 25;
    printf("a=%d b=%d c=%d d=%d", a, b, c, d);

    return 0;
}

输出

a=127 b=-128 c=-1 d=25
  • 整数的计算是按照纯二进制的方式计算的 就像这样
0111 1111 + 1011 1100 = 0001 0011 1011

运行一下这个代码

#include <stdio.h>

int main()
{
    unsigned char a = 255;
    a = a + 1;
    printf("%d", a);

    return 0;
}

输出

0

为什么255+1是0,因为unsigned char的范围是0~255,这个范围就像一个圆超过255就又会到0,如果给a+2,则输出的就是1,如果是a = a + 257;则又会输出0

# 整数的输入输出

类型 占位符
int %d
unsigned %u
long long %ld
unsigned long long %lu

int >short>char 所有范围比int小的都用%d输出

# char

运行一种这段代码

#include <stdio.h>

int main()
{
    char a;
    char b;
    a = 1;
    b = '1';
    if (a == b){
        printf("相等\n");
    }else{
        printf("不相等\n");
    }

    return 0;
}

输出结果

不相等

同样是1为什么不相等呢?因为1表示数字1她是一个值,而'1'不是一个值,而是字符,因为它加了''

再运行这段代码

#include <stdio.h>

int main()
{
    char a;
    char b;
    a = 1;
    b = '1';
   printf("a=%d\n",a);
   printf("b=%d\n",b);

    return 0;
}

输出结果

a=1
b=49

这里的b的值并不为1,也验证了对于计算机来说加了'' 符号后计算机会把里面的内容当作字符处理,那这里为什么是49呢?因为在计算机中'1' 的ASCll码是49,所以计算机把输出的“1”当作了49来表达,每一个字符在计算机中都有一个值去表达它,这个值我们可以直接以整数的形式得到

参考: ASCII码

我们可以通过%c来表达字符

让我们设计一个程序试一下

#include <stdio.h>

int main()
{
    char a;
    scanf("%c", &a);

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

    return 0;
}

在这个程序中,输入1

输出结果

a=49
a=1

我们改一下代码

#include <stdio.h>

int main()
{
    int a;
    scanf("%d", &a);

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

    return 0;
}

然后输入49,则输出

a=49
a=1

还可以通过判断验证

#include <stdio.h>

int main()
{
    if ( 49 == '1')
    {
        printf("ok");
    }

    return 0;
}

输出结果

ok

# 浮点类型

浮点类型(float,double,long double)都是编码形式,这表示它在计算机中不能直接用来做运算

类型 字长 范围 有效数字
float 32 -3.4E+38 ~ 3.4E+38 7
double 64 -1.7E-308~1.7E+308 15

# 浮点数输入输出

类型 scanf printf
float %f %f,%e
double %lf %f,%e

# 科学计数

  • %e输出的是一个科学计数法的值

例如:

#include <stdio.h>

int main()
{
    double f = 1342.2345;
    printf("%e\n", f);

    return 0;
}

输出:

1.342235e+003

控制小数点后输出的位数

# 运算

运算符:是指+ - * / = 这种做运算的符号

算子:表达式中参加计算的东西,可以是常数或者变量,还可以是一个返回值

例如,在表达式 a = b + 2红色的为算子黑色运算符

# 算数运算符

运算符 说明
+
-
*
/
% 取余数
++ 整数值加1
- - 整数值减1

示例:计算时间差

#include <stdio.h>

int main()
{
//定义变量
    int hour1,minute1;
    int hour2,minute2;

//内容输入
    scanf("%d %d", &hour1, &minute1);
    scanf("%d %d", &hour2, &minute2);
//把小时转化为分钟方便计算
    int t1 = hour1 * 60 + minute1;
    int t2 = hour2 * 60 + minute2;
    int t = t2 - t1;

// t/60把分钟转化为小时,由于输出类型为整数所有即使有余数也只输出整数部分
// t%60表示剩余的分钟数
    printf("时间差为%d小时%d分", t/60, t%60);
    return 0;
}

如果两时间相差1小时10分钟,也可以说相差70分钟,用数学表示,70 ÷ 60 =1...10 整数1为相差的小时数,余数10为相差的分钟数 。在C语言中%表示取余,70%60表示对70除以60取余,结果为10,也就是我们所对应的分钟数。

# 优先级

在使用C语言运算时运算符的优先级其实与数学中的优先级一样,举几个例子感受一下:

例如:1+4/2=3 除法的优先级高于加法所以应该先计算4/2=2,然后再+1 最后计算结果为3,如果式子为(1+4)/2=2.5 因为加了括号所以表示先计算1+4,然后再除2

在C语言运算中有个概念叫单目,其实就是说表达式中只有一个算子的情况例如-2 这里只有一个算子2,如果式子是1+2 那就叫做双目运算,因为它有两个算子“1和2”

# 交换变量

如果a=5 b=6,我们如何使这两个变量的值进行交换呢?上代码:

#include <stdio.h>

int main()
{
    int a = 5;
    int b = 6;
    int c;

    c = a;
    a = b;
    b = c;

    printf("a=%d b=%d",a, b);
    return 0;
}

这好比两杯水,我们要把两个杯子的水进行交换就需要在加一个空杯子,这个空杯子的就是我们的变量c 我们先把a的值给c,然后再把b的值给a ,最后把c的值给b,这时候a获得了b的值,b也通过c获得了a的值

# 递增递减运算符

include <stdio.h>

int main()
{
    int a = 10;

    printf("%d",++a);
    return 0;
}

输出:

11

++a表示给a又加了1

如果把++a换成a++呢?

#include <stdio.h>

int main()
{
    int a = 10;

    printf("%d\n",a++);
    printf("%d\n",a);
    return 0;
}

输出:

10
11

通过输出我们可以看出a++输出了10,与初始值相同,但输出的a却为11,这是因为a++++在后面,所以程序先输出了a然后再做运算+1

--也是同样的道理,只不过--不再是加1而是减1

# 复合赋值

运算符 示例
+= a+=b为a=a+b
-= a-=b为a=a-b
*= a*=b为a=a*b
/= a/=b为a=a/b

注意:a*=1+2表示a=a*(1+2),*=后的符号要为一个整体与a相乘,所以要加上括号,其他的复合赋值运算符也是同理

由于历史原因,C语言最开始是诞生于PDP-11这台机器,当时C语言要做一个底层的编程语言,所以在设计C语言时作者想用它表达出当时机器的所有指令,所以有了递增递减和复合赋值的运算符

# 判断

这是一个判断()括号中是要满足的条件如果满足条件就会执行{}内的内容

if ( a < 0 ) {
		a = 60 + 1;
}

如果我们需要不满足条件时执行一些程序怎么办呢?

if ( a < 0  ) {
		//满足条件执行这里
	} else {
		//否则执行这里
	}

没用的知识:在if和else后面可以不加{} 但如果不加{},则在条件或else后面只能写一句代码

if ( a < 0 ) 
a = 60 + 1;
else
a = 60-1;

# else if

如果要连续判断我们还可以这样写

if(a > 1){

}else if(a > 2){
 
}else if(a > 3){

}else {
  
}
  • 一个 if 后可跟零个或一个 else,else 必须在所有 else if 之后。
  • 一个 if 后可跟零个或多个 else if,else if 必须在 else 之前。
  • 一旦某个 else if 匹配成功,其他的 else if 或 else 将不会被测试。

示例:

#include <stdio.h>

int main()
{
	//	初始化
	int price = 0;
	int bill = 0;
	//	读入金额和票面
	printf("请输入金额:");
	scanf("%d", &price);
	printf("请输入票面:");
	scanf("%d", &bill);
	//	计算找零
	if ( bill >= price ) {
		printf("应该找您:%d\n", bill - price);
	} else {
		printf("你的钱不够\n");
	}

	return 0;
}

# 关系运算符

运算符 意义
== 相等
!= 不相等
> 大于
< 小于
>= 大于等于
<= 小于等于

如果用关系运算符做输出只有两个结果,0和1

#include <stdio.h>

int main()
{
    printf("%d\n", 5 > 3);
    printf("%d\n", 5 == 3);
    return 0;
}

输出:如果关系满足结果为1,如果不满足结果为0

1
0

# 优先级

#include <stdio.h>

int main()
{
    printf("%d\n", 7 >= 3 + 4);
    return 0;
}

关系运算符的优先级低于算数运算符(除=以外),例如以上这个例子,输出值为1,因为先计算了3+4=7,然后再判断7>=7,满足条件则输出1

#include <stdio.h>

int main()
{
    printf("%d\n",5 > 3 == 6 > 4);

    return 0;
}

这个例子输出值为1因为==的优先级低于>和<

如果出现像这种连续的同优先级的运算符时,按照从左到右的顺序开始,例如这样

#include <stdio.h>

int main()
{
    printf("%d\n",6 > 5 > 4);

    return 0;
}

首先从左边开始判断6>5满足所有输出1,但1>4不满足,所以最终输出的值为0

# 示例2

这是之前计算时间差的例子,我们学习了判断后就可以做一些改进

#include <stdio.h>

int main()
{
//定义变量
    int hour1,minute1;
    int hour2,minute2;

//内容输入
    scanf("%d %d", &hour1, &minute1);
    scanf("%d %d", &hour2, &minute2);
//把小时转化为分钟方便计算
    int t1 = hour1 * 60 + minute1;
    int t2 = hour2 * 60 + minute2;
    int t = t2 - t1;

// t/60把分钟转化为小时,由于输出类型为整数所有即使有余数也只输出整数部分
// t%60表示剩余的分钟数
    printf("时间差为%d小时%d分", t/60, t%60);
    return 0;
}

之前我们遇见了一个巨大的问题,当两个时间为1:40和2:10时 2-1=1小时,10-40=-30分钟,很显然这是错误的,我们上次使用的全部化为分钟然后取余数的方式来表达分钟部分,这次我们试一试用判断的方式去解决

#include <stdio.h>

int main()
{
  //定义需要的变量
	int hour1, minute1;
	int hour2, minute2;
    
  //内容输入
	scanf("%d %d", &hour1, &minute1);
	scanf("%d %d", &hour2, &minute2);

	int h = hour2 - hour1; //小时差
	int m = minute2 - minute1; //分钟差
  //如果分钟差小于0则执行括号里的程序
	if ( m <0 ) {
		m = 60 + m;
		h - 1;
	}
	
	printf("时间差是%d小时%d分。\n", h, m);
	
	return 0;
}wan

大概了解了这个解决方法后,我们需要解析一下我们到底怎么通过判断解决这个问题的

if ( m <0 ) {
		m = 60 + m;
		h - 1;
	}

首先我们先判断m也就是分钟差是否为负数,如果为负数那就说明我们在算分钟差的时候没有借位,在数学中当一个小数减去一个大数时,很显然它是不够的所以我们要像小时借一位1小时等于60分钟所以给minute2+60然后再减minute1,由于int m = minute2 - minute1; 已经在之前的代码里写好了所以60只能与分钟差m相加,之所以可以这样是因为minute2+60-minute1minute2-minute1+60是一样的。在判断中对应m = 60 + m; 由于借了一小时所以就要h - 1;

# 循环

# while循环

 while (x > 0)
    {
        n++;
        x /= 10;
    }

括号()中的表达式是条件,如果条件满足则会一直执行{}里的代码,直至不满足条件为止

ifwhile和很像,它们的区别在于if只判断一次,而while会反复判断条件是否满足,直至不满足时停止

试想一下,如何用C语言实现一个获得数字位数的功能

按照之前所学的,我们可以使用判断,例如一个整数小于等于999 但大于99则可以说这个整数是一个三位数,我们可以通过判断这个数的大小范围来获得这个数的位数

代码如下:

#include <stdio.h>

int main()
{
    int x;
    int n;

    scanf("%d",&x);

    if ( x > 999){
        n = 4;
    }else if (x > 99){
        n = 3;
    }else if ( x > 9){
        n = 2;
    }else{
        n =1;
    }

  printf("%d",n);

	return 0;
}

但这个只能判断4位数及以下的整数,那如果我要判断五位数以及更高的位数怎么办?例如1434554675876987756543,这时候使用这种判断的方法显然是麻烦的,有没有什么办法可以一劳永逸?

这时我们就可以使用while循环来解决这个问题,输入的整数如果满足大于0的条件则除以10并且次数n加一,只要满足大于零的条件,计算机将一直执行while里的代码,直至不满足此条件

#include <stdio.h>

int main()
{
    int x;
    int n = 0;

    scanf("%d", &x);

    while (x > 0){
        n++;
        x /= 10;
    }
    printf("%d", n);
    return 0;
}

这种方法虽然满足了判断任意位数的数,但当我们输入0时它输出的却是0,这是因为输入的值为零时不满足循环的条件,所以循环里的代码也不会执行,那么n输出的值就是初始化int n = 0;的0,我们可以做一些改进来解决这个问题,代码如下:

#include <stdio.h>

int main()
{
    int x;
    int n = 0;

    scanf("%d", &x);

    n++;
    x /= 10;
    while (x > 0){
        n++;
        x /= 10;
    }
    printf("%d", n);
    return 0;
}

这时刻你可能在想为什么不直接把while循环的条件改成x >= 0 这样判断的时候0也可以满足循环条件了,问题也解决了。但实际上这样是不行的,因为在代码x /= 10;x = x/10,我们知道0除以任何数都是0,这样条件就会被一直满足循环也就会一直进行下去,这就成了一个死循环了。不经如此,而且这种方式会导致你输入任何值时都会出现死循环,这是为什么呢?例如你输入的是200,经过三轮循环后会得到0.2但是最开始我们对变量x的类型设置为int类型,int只保留整数部分,所以输出的还是零,这样又进入了死循环

# do while循环

do
{
//循环体
}while();

这个循环先执行循环体(循环里代码)再进行while循环条件的判断,如果满足条件则会继续执行循环体代码,直至条件不再满足

括号()里是条件{}里的循环体

在之前的while循环中为了考虑输入0的情况我们需要在开始执行时就先运行一些次这个代码,所以我们就要在循环前再写一次,这样相同的代码就会重复写两次

  n++;
  x /= 10;

但现在我们就可以之间通过do while循环解决这个问题,例如:

#include <stdio.h>

int main()
{
    int x;
    int n = 0;

    scanf("%d", &x);

    do{
        n++;
        x /= 10;
    } while (x > 0);

    printf("%d", n);

    return 0;
}

现在我们要实现阶乘的计算,怎么做?我们可以使用while循环

#include <stdio.h>

int main()
{
    int n;
    int fact = 1;
    int i = 1;

    scanf("%d", &n);

   while (i <= n){
    fact *= i;
    i++;
   }

    printf("%d!=%d", n,fact);

    return 0;
}

当然也可以使用for循环

#include <stdio.h>

int main()
{
    int n;
    int fact = 1;
    int i = 1;

    scanf("%d", &n);

   for ( i = 1; i <= n; i++)
   {
    fact *= i;
   }

    printf("%d!=%d", n,fact);

    return 0;
}

# for循环

 for ( init; condition; increment )
{
   statement(s);
}

()里的三个分别是初始条件 循环条件 每一轮循环都要做的动作

# 循环嵌套

循环嵌套就是在一个循环里还有另一个循环,你可以一直这样嵌套下去,判断也可以嵌套,你可以在循环中嵌套循环,也可以在循环里嵌套判断,还可以在判断中嵌套循环或在判断中嵌套判断

for嵌套

for (x = 1; x <= num; x++)
    {
        for (i = 2; i < x; i++) { 
    
        }
    }

while嵌套

while (x > 1)
{
  
}

do while嵌套

do{

    do{

    }while (i < 2);
}while (x > 1);

也可以这样混着套

for (x = 1; x <= 10; x++){

     while (i < 2){
    
    }
 }

# 循环控制语句

# break 跳出循环

break就是用来跳出循环的,如果在一个程序中我们得到了我们要得到的结果,那么就没必要再等循环结束,我们可以直接跳出这个循环

while (i = 0; x > -1; i++)
{
    if (x < 1)
    {
        break;
    }
}
//break跳到这里

# continue 跳过这个一轮,进入下一轮循环

continuebreak不同,它并不是要跳出循环,而是跳过这一轮循环中continue之后的代码,直接进行下一轮

//continue跳到这里
while (i = 0; x > -1; i++)
{
    if (x < 1)
    {
        continue;
    }
}

# goto 跳到你指定的位置

在一个循环中,如果像这样使用break则只能跳出他所在的循环

for (one = 1; one < x * 10; one++)
{
    for (two = 1; two < x * 10 / 2; two++)
    {
        for (five = 1; five < x * 10 / 5; five++)
        {
            if (one + two * 2 + five * 5 == x * 10)
            {
                break;
            }
        }
     //break会跳到这里
    }
}

如果我想直接跳出所有循环该怎么办?或者,我想从第三个循环跳到第一个循环怎么办?

这时我们就可以使用goto语句,它可以让我跳转到代码的任何位置

先给你跳的地方命个名,这里就叫做 out,然后在要跳的地方写 out:,这时候只需要在开始跳转的位置写上goto out;即可

for (one = 1; one < x * 10; one++)
{
    for (two = 1; two < x * 10 / 2; two++)
    {
        for (five = 1; five < x * 10 / 5; five++)
        {
            if (one + two * 2 + five * 5 == x * 10)
            {
                goto out;
            }
        }
    }
}
out: //goto 跳到了这里

# 逃逸字符

如果我们要打印一串这样的字符

数字"1"和数字"2"

应该怎么办呢?如果是这样

#include <stdio.h>

int main()
{
    printf("数字"1"和数字"2"");

    return 0;
}

计算机会告诉你这是错的

为什么错?错在那里?

我们要输出一段字符,需要在printf()中用"" 把内容括起来以表示这些是要输出的字符,如果在这段文本再加入符号" 就会形成干扰,自然程序就会报错,这样无法打印的字符或者一些特殊字符就叫做逃逸字符,那么我们如何把" 当作字符去输出呢?

我们可以这样:

#include <stdio.h>

int main()
{
    printf("数字\"1\"和数字\"2\"");

    return 0;
}

# 转义字符

字符 意义
\b 回退一格
\t 到下一个表格位
\n 换行
\r 回车
" 双引号( " )
' 单引号( ' )
\ 反斜杠( \ )

先试一下回退

#include <stdio.h>

int main()
{
    printf("123456\b");

    return 0;
}

输出结果

123456

好像没什么变化

我们再在回退后面加字符

#include <stdio.h>

int main()
{
    printf("123456\ba");

    return 0;
}

输出结果

12345a

我们发现这里的最后一个字符被替换成了a

如果我们把\b放在字符中间会发生什么?

#include <stdio.h>

int main()
{
    printf("123\b456");

    return 0;
}

输出结果

12456

我们发现这里的3消失了取而代之的是\b后面的456

这些现象说明\n表示往后退一个字符,如果\n后有字符,那么回退一格后继续输出后面的字符,在输出后面字符的时候之前的那个回退后剩下的一个字符就会被之后输入的覆盖

然后再看看\t是干嘛的

#include <stdio.h>

int main()
{
    printf("123\t456\n");
    printf("12\t456\n");

    return 0;
}

输出结果

123   456
12    456

我们发现这里数字4是对齐,可以像表格一样排列,所以叫做制表位

而回车和换行在今天的计算机上看起来是一个动作,但它最早的起源是来自于打字机,对于打字机来说,回车和换行是两个动作,我们平常使用的\n换行在今天的计算机中它就相当于同时做了打字机的回车和换行两个动作

# 类型转换

# 自动类型转换

  • 当运算符两边出现不一样的类型时,会自动转换成范围较大的类型
  • char < short < int < long < long long
  • int < float < double
  • 当使用printf的时候,任何小于int的类型都会被转成int,float会被转成double,但是scanf不会
  • 如果要在scanf中输入short类型,需要%hd

# 强制类型转换

如果要把一种数据类型强制转换成另一种数据类型,我们可以通过

(类型)值 这样的形式转化,但要注意数据类型的范围问题

例如,你可以这样使用

#include <stdio.h>

int main()
{
    printf("%d\n",(short)32768);

    return 0;
}

也可以

#include <stdio.h>

int main()
{
    double a = 1.1;
    double b = 2.0;
    int i = (int)(a+b);
    printf("%d",i);

    return 0;
}
  • 注意:强制类型转换的优先级高于四则运算,先把值转换类型然后再进行运算。在这里(int)a+b(int)(a+b) 是不同的

# 逻辑运算

# 布尔类型(bool)

使用bool类型的时候要先导入头文件

#include <stdbool.h>

导入这个头文件后我们就可以使用bool和布尔值(true,false)

看一个示例:

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

int main()
{
    bool a = true;
    bool b = false;

    printf("a=%d\n", a);
    printf("b=%d\n", b);

    return 0;
}

输出:

a=1
b=0

虽然a和b都是布尔值,但输出的却是1和0,这里的true=1,false=0

# 逻辑运算符

运算符 描述 示例
|| a || b 如果a是ture,结果就是flase,如果是是flase,结果就是ture
&& a && b 如果a和b都是ture,结果就是ture,否则为flase
! !a a和b中有一个是ture,结果就是ture,否则为flase
  • 如果我们要表达x(4,6)时,如何用C语言写表达式呢?

首先不能像数学一样写成4<x<6,这个式子在C语言中表示,先看4是否小于x,根据是否对应不同的布尔值,如果为是则对应ture,输出值为1,如果为否则对应flase,输出值为0,然后再用这些输出值对比是否小于6

如果想要在c语言中正确表达x(4,6)可以使用逻辑运算符

写作x>4 && x<6

  • 那如何判断字母c是否为大写字母呢?

还记得在之前有提到过的ASCII码吗?在这个ASCII码的对照表中大写字母A~Z是连续排列的,并对应的十进制数65~90,现在我们只需要判断c的ASCII码的十进制数是否在这个范围里就可以知道c是不是大写字母了,当然在c语言中这些字母与ASCII码都是对应好的,你只需要写成c >= "A" && c <="Z" 就可以通过输出的布尔值来判断c是否为大写字母

  • !a <20 相当于 a≥20
  • x<0 || x>99表示x<0或者x>99

# 逻辑运算符优先级

! > && >||

在式子!a && (c>b) 中先算!a由于是逻辑运算所以输出的值不是0就是1 然后再算c>b

对于&&,左边式子是flase时就忽略右边

对于||,左边式子是true时就忽略右边式子

对于式子a == 6 && b==1 如果左边a==6,以及不满足了就不会再去看右边的b==1 ,这时要注意,如果右边有运算时,例如,在a == 6 && b+=1 中,如果左边的式子不满足就会忽略右边的式子,那么右边式子中的运算就不会进行。

# 示例

#include <stdio.h>

int main()
{
    int a = 6;
    int b = 1;
    int c = 3;

    if (a == 6 && b == 1 && c == 2)
    {
        printf("满足");
    }
    else
    {
        printf("不满足");
    }

    return 0;
}

# 条件表达式

这是C语言早期遗留下来的一种方式

运算符 说明
如果为真则…
否则…

count>20 ? count - 10 : count + 10;

这个式子在C语言中表示,如果满足条件count>20则执行count-10否则执行count+10

相当于:

if (count > 20){
count - 10;
}else{
count + 10;
}

# 函数

试想一下,我们要用C语言求从1开始加,一直加到10的值

这个并不难,简单写一个循环就可以解决

#include <stdio.h>

int main()
{
    void sum(){
    int a ;
    int b ;
    int i ;
    int sum =0 ;
    scanf("%d",&a);
    scanf("%d",&b);

    for ( i = a; i <= b; i++)
    {
        sum+=i;
    }
    printf("%d\n",sum);
}
    return 0;
}

a是开始的值,b是结束的值

如果我想同时得到1~10相加,20~30相加,30~40相加怎么办?

你可能在想,那就按照同样的方法多写几个循环就好了。

但是,这样非常麻烦,有没有什么可以一劳永逸的办法呢?

有!我们可以把这个连续求和的循环写成一个函数,这样当使用它的时候就可以直接调用,不用再重复写代码了

就像这样:

#include <stdio.h>

void sum(int a, int b)
{
    int i;
    int sum = 0;

    for (i = a; i <= b; i++)
    {
        sum += i;
    }
    printf("%d\n", sum);
}

int main()
{
    sum(1, 10);
    sum(20, 30);
    sum(30, 40);

    return 0;
}

输出结果:

55
275
385

这是不是方便了许多

# 什么是函数?

在计算机语言中,函数就是一段代码,它可以接收多个参数(也可以不接收),并返回一个值(也可以不返回),你可以把一些代码封装成一个函数,当你使用的它时候,只需要写出它的函数名称就可以代表函数里的代码去运行,调用时格式是这样:name(x); name代表函数名,x代表里面的参数

你也可以把参数输入在函数里,经过函数的处理后输出结果,就像上面的示例一样

对于这样一个函数

void sum(int a, int b)
{
    int i;
    int sum = 0;

    for (i = a; i <= b; i++)
    {
        sum += i;
    }
    printf("%d\n", sum);
}
  • 最开始的void sum(int a, int b) 是函数头,括号{ } 里的叫做函数体
  • 在函数头中void是返回类型,sum是函数名,在( )中是参数表,参数表中的参数需要用,隔开
  • void是一种不返回的返回类型
  • 在调用函数的时候,除了函数名称一定要加上() 不然计算机不会以外你在调用函数

# 函数返回值

#include <stdio.h>

int max(int a, int b)
{
    int ret;
    if (a > b)
    {
        ret = a;
    }
    else
    {
        ret = b;
    }

    return ret;
}

int main()
{

    int y = max(1, 2);
    printf("%d", y);

    return 0;
}

输出

2

在这样的一个示例中,向函数max中输入参数1和2,来对比谁大,最后把大的那个值赋值给变量ret,再通过return返回这个值

当然这仅仅是返回了值,并不会输出任何内容,如果要输入结果,可以先把值赋给一个变量,然后在输出这个变量的值,就像示例中那样。也可以

像这样直接输出printf("%d", max(1, 2));

在这里max函数返回类型是int,这个类型是必须要返回值的所有一定要有return ,如果要写不用返回值的函数就用void来做函数的返回类型

# 函数的先后关系

C的编译器是自上而下的顺序分析代码,所以要把调用函数的代码,放在要调用的函数后面,不然会出现错误。先要有函数,才能调用这个函数,所以先要让计算机看到这个函数,然后再去调用函数时计算机才知道你调用的是什么

当然,如果你非要把函数放在调用代码的后面,就必须在调用代码的前面写一个函数声明,告诉它有这样一个函数,等到代码执行到那个被调用的函数的时候再去执行之前这些调用的,就像这样

#include <stdio.h>

int max(int a, int b);//max函数的声明
int main()
{
    int y = max(1, 2);
    printf("%d", max(1, 2));

    return 0;
}
int max(int a, int b)
{
    int ret;
    if (a > b)
    {
        ret = a;
    }
    else
    {
        ret = b;
    }

    return ret;
}

# 函数原型

 int max(int a, int b);

在一个函数中这样的代码就是函数的原型,它和函数头的区别在于有;结尾并且没有{ }里的函数体

函数原型声明也可以写成这样(不写参数名,只写参数的数据类型)

int max(int, int);

# 传值

 int a,b;
  a=5;
  b=6;
  funtion(10,12)
  funtion(a,b)
  funtion(a,23)
  funtion(max(13,45),b)
  funtion(funtion(13,45),b)
  funtion(2+3,b)

一个函数中的参数可以是数字,也可以是变量,还可以是另一个函数或者自己的函数返回的值,或者是一个表达式的计算结果

# 本地变量

#include <stdio.h>

void swap(int a, int b)
{
    int t = a;
    a = b;
    b = t;
}
int main()
{
    int a = 5;
    int b = 6;

    swap(a, b);
    printf("a=%d b=%d\n", a, b);

    return 0;
}

在这样一个代码中swap函数用来把a和b的值交换

执行代码后,输出结果:

a=5 b=6

按道理应该把a和b的值交换了,但在这里,a还是5,b还是6

导致这种问题的产生的原因是函数间变量的隔离

每个函数有自己的变量空间,参数也在这个空间中。函数中的变量和该函数外的其他函数没关系

在这段代码中swap函数里面的变量a和b于函数外的变量a和b不是同一个变量,它们只是名称相同,没有任何关系

一个函数在运行中使用的变量是本地变量,是这个函数独有的

所有定义在函数内部的变量都叫做本地变量,参数也是本地变量

**变量的生存期:**变量什么时候出现,到什么时候消亡

**变量的作用域:**在代码的什么范围内可以访问这个变量,这个变量可以起作用

在一个函数中运行时,不能访问这个函数外的变量,因为那些变量不在这个函数的作用域中

  • 本地变量必须定义在块里,块就是的{ } 它可以是函数的块,也可以是语句的块,还可以把任意一个{ } 当作块

例如这个代码

#include <stdio.h>

int main()
{
    int a = 5;
    int b = 6;

 
    if (a < b)
    {
       int i = 10;
    }

    printf("%d\n", i);

    return 0;
}

在这些代码中变量iif的这个块里,但是如果运行这个代码时计算机会报错,因为这个if里的变量的生存期和作用域仅仅只在这个if

  • 但在块外的定义的变量在块内依然有效

但如果块里的变量的名称和块外的变量的名称重复时,代码就可以执行,不会报错,例如这样

#include <stdio.h>

int main()
{
    int a = 5;

    {
        int a = 0;
        printf("%d\n", a);
    }

    printf("%d\n", a);

    return 0;
}

输出:

0
5

在这个块里输出的值为0,覆盖了变量之前的值,但从块里出来后输出的值依然是变量最开始定义的值

  • 不能在同一个块里定义同名的变量,例如
#include <stdio.h>

int main()
{
    int a = 5;
    int a = 10;

    printf("%d\n", a);

    return 0;
}
  • 本地变量不会被默认初始化,但参数会初始化,当你在添参数的时候要有一个值来初始化参数

# 没有参数时

viod fun(void)

当函数的参数表中填写的是void时,明确告诉bianyiq这个函数不接受任何参数

viod fun()

如果函数的参数表中没有参数时,表示不确定是否有参数,可以有,也可以没有

# 逗号运算符

, 是运算符,但在函数中f(a,b) ,这里的逗号只是一种标点符号,用来隔开参数,当像这样f((a,b)) 时,这里的逗号就是运算符

# 函数嵌套

  • C语言不允许函数嵌套
void func()
{
   void func2()
   {
         
   }
}

虽然这样是非法的,但在实际操作中编译器依然可以进行编译,程序依然可以正常运行,但在调用函数时,不能调用嵌套在函数中的函数,如果要调用这个嵌套函数必须和函数在同一个块{}

void func()
{
   void test()
    {
         
    }
    test();//可以调用函数
}
test();//不能调用函数

# 可变参数

C语言中允许定义一个能根据具体的需求接受可变数量的参数函数

  • 参数int num 表示传递的参数总数
  • 参数写成省略号...,表示可变数量的参数,...要放在参数表的最后
  • 省略号前面可以设置其他参数
  • 使用时需要引入头文件stdarg.h,该文件提供了实现可变参数功能的函数和宏
 






 
 



int func(int num, ...)
{
   return 0;
}

int main()
{
   func(2, 2, 3);
   func(3, 2, 3, 4);
   return 0;
}

使用示例:

  • 在函数定义中创建一个va_list类型变量,va_list变量为一个参数列表,该类型是在stdarg.h头文件中定义的
  • 使用int参数和va_start宏来初始化
  • 使用va_arg宏和va_list变量来访问参数列表中的每个项
  • 使用宏va_end来清理赋予va_list变量的内存
#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)
{
 
    va_list valist; //可变参数列表
    double sum = 0.0;
    int i;
 
    va_start(valist, num); //为列表中的参数初始化
    for (i = 0; i < num; i++)//遍历赋给valist列表中的参数
    {
       sum += va_arg(valist, int); //把每个可变参数都加起来
    }
    va_end(valist); //清理valist列表中占用的内存
 
    return sum/num; //返回平均数
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

# 函数递归

  • 在C语言中,函数可以调用其自身
  • 但在使用递归时,需要定义一个从函数退出的条件,否则会进入死循环。
void recursion()
{
   recursion(); //函数调用自身
}
 
int main()
{
   recursion();
}

使用实例:

# 数组

如何用C语言实现计算输入的所有数字的平均数并输出所有大于平均数的数?

如果要实现这个功能我们就要记录输入的每一个数,然后等算完平均数后一个一个去对比,把大于平均数的值输出。

如果要把每一个数都记录下来,我们就要使用数组

示例: 算出平均数并输出大于平均数的数

例如,这就是一个数组

 int number[100];

这是一个名为number的数组,可以容纳100个int类型的数字

如果我们要查询这个数组中某一个位置上的值,可以通过索引查找,比如我要查询数组number的第5个值,就可以这样

int a = number[5];

我们还可以通过这样的方式去为数组的元素逐个初始化

int number[7] = {1,3,5,7,8,9,10};

也可以不写索引数,让编译器去给我填写,

int number[] = {1,3,5,7,8,9,10};

这种方式也叫作数组的集成初始化

写一个程序测试一下

#include <stdio.h>

int main()
{
    int a[9] = {2, 4, 6, 8, 5, 9, 11, 13, 23};
    {
        int i;
        for (i = 0; i < 9; i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

输出结果:

2       4       6       8       5       9       11      13      23

如果在这个示例的数组中不填写索引,就像这样

#include <stdio.h>

int main()
{
    int a[] = {2, 4, 6, 8, 5, 9, 11, 12};
    {
        int i;
        for (i = 0; i < 9; i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

当数组中的元素不够9个或者超过9个时就会出现问题

2       4       6       8       5       9       11      12      11998720

这里计算机自己“脑补”了缺少的那个值 11998720

为了解决这个问题,我们可以使for循环里判断的值(就是代码里的那个9)与数组的索引值相同,像这样

#include <stdio.h>

int main()
{
    int number=9;
    int a[number] = {2, 4, 6, 8, 5, 9, 11, 13, 23,14};
    {
        int i;
        for (i = 0; i < number; i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

但运行后发现,这是错误的,编译器告诉我们,大小可变的对象无法初始化,意思就是如果要初始化,数组索引里的值必须是一个确定的值

那怎么办呢?我们还可以使用sizeof() 例如

#include <stdio.h>

int main()
{
    int a[] = {1,2,3,4,5,6,7,8};
    {
        int i;
        for (i = 0; i < sizeof(a)/sizeof(a[0]); i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

这里的sizeof(a)是这个数组的所有元素占用的字节再除以每个元素占用的字节,这里以sizeof(a[0])为例

sizeof(a)/sizeof=数组总元素量=数组索引值

这样这个程序就是一个安全的程序

如果在数组有索引的情况下,数组内的元素不够索引里设定的值,就会把剩余的元素都赋值为0

#include <stdio.h>

int main()
{
    int a[9] = {1,
2};
    {
        int i;
        for (i = 0; i < 9; i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

输出结果

1       2       0       0       0       0       0       0       0

在C99中有一个特性可以给数组中指定的元素赋值

#include <stdio.h>

int main()
{
    int a[8] = {
        [0]=2,[2]=3,6,7
    };
    {
        int i;
        for (i = 0; i < 8; i++)
        {
            printf("%d\t", a[i]);
        }
    }

    return 0;
}

输出结果

2       0       3       6       7       0       0       0

# 数组的赋值

数组变量是特殊的,不能使用这种方式去赋值

int a[] = {2,3,4,5,6,7}
int b[] = a;

如果要把一个数组的值赋给另一个数组,可以采用遍历的方式

#include <stdio.h>

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int b[10];
    {
        int i;
        for (i = 0; i < sizeof(a)/sizeof(a[0]); i++)
        {
            b[i]=a[i];
            printf("%d\t", b[i]);
        }
    }

    return 0;
}

# 数组的特点

  • 数组中的元素的数量必须是整数
  • 在C99之前,数组的元素数量必须是编译时的字面值
  • 数组是一种容器,可以放一些东西
  • 一个数组中的所有元素具有相同的数据类型
  • 数组一旦创建就不能改变大小
  • 数组中的元素在内存中是连续依次排列的
  • 数组的每个单元都是数组类型的一个变量
  • 在数组中[]中的数字叫做索引,索引从0开始计数,例如设定一个数组int a [10],数组从0开始计数a[0],a[1],a[2],a[3]…一直到a[9],总共10个元素,如果这时有一个a[10]时,程序就会出现错误因为a[10]排在第11个
  • 如果数组元素的数量超过了设定的数量,数组就会越界,这样会造成程序崩溃
  • 可以存在长度为0的数组int a[0],但它没有任何用处

参考示例:

统计每一种整数输入的次数

在数组中查找数字是否存在

# 二维数组

这是一个二维数组,对于这个数组你可以把它想想成一个三行五列的矩阵

int a[3][5];

# 二维数组初始化

int a[2][5] = {
  {0, 1, 2, 3, 4},
  {2, 3, 4, 5, 6},
};
  • 列数必须给出,行数可以由编译器来数
  • 每行一个{},用,分隔

# 访问二维数组元素

int val = a[2][3];

# 通过遍历输出二维数组

#include <stdio.h>
 
int main ()
{
   /* 一个带有 5 行 2 列的数组 */
   int a[5][2] = { 
    {1,1}, 
    {1,2}, 
    {2,4}, 
    {3,6},
    {4,8},
    };
   int i, j;
 
   /* 输出数组中每个元素的值 */
   for ( i = 0; i < 5; i++ )
   {
      for ( j = 0; j < 2; j++ )
      {
         printf("a[%d][%d] = %d\n", i,j, a[i][j] );
      }
   }
   return 0;
}

# 指针

# 什么是指针?

  • 在计算机中内存可划分为若干存储单元,每个内存单元都对应着一个内存地址
  • 在C语言中内存地址是一种十六进制编码
  • 如果在程序中定义了一个变量,在编译程序时系统会自动给这个变量分配内存单元
  • 每个内存单元占一个字节
  • 指针是一种可以存放地址变量
  • 通过地址就能找到对应变量单元
  • 只能对变量取地址,如果不是变量就不能取地址

如下,在这段代码中,i是一个普通变量p是一个指针变量,通过运算符&获得了变量i内存地址,并把地址赋值给了指针p,这时指针p就指向了变量i

int i;
int *p = &i;

# 指针的写法

  • 以下这两种写法都可以,他们表示的意思相同
int* p;
int *p;
  • 当写作以下这种形式时,p是一个指针,而q只是一个普通的变量
int *p,q;

如果要p和q都表达为指针应该这样写

int *p,*q;

# 指针运算符

运算符 说明
& 返回变量的地址
* 指向一个变量

# 输出内存地址

scanf("%d", &x);&是一种运算符,用来获取变量的地址,%p是一种用于输出内存地址(指针的值)的占位符,它以十六进制整数的方式输出指针的值


 

int i = 0;
printf("%p\n", &i);

# 内存分配

  • 内存是自顶向下分配的
  • 内存按照顺序线性分配
int i;
int p;
printf("%p\n", &i);
printf("%p\n", &p);

输出结果:

000000000061FE1C
000000000061FE18

在十六进制中,C相当于十进制的12,12-8=4,所以这两个地址间相差4个字节,在这个例子中使用的变量是int类型,一个int类型的变量占4个字节,这说明变量i和变量p在内存中是按照顺序连续排列的。

变量i的地址比变量p的地址大,对应了堆栈的内存模型,自顶向下分配变量

# 指针参数

  • 如果要把指针当作参数传入函数,在参数表中参数要写作*p的形式
  • 向函数中传递参数时应该使用&i的形式传入
 



 

void f(int *p){
printf("%p\n", p);
}
int i =6;
f(&i);

# 指针赋值

*p=3;

在函数中通过指针赋值

void f(int *p){
*p = 26;
}
int i;
f(&i);
printf("i=%d\n", i);

输出结果:

i=26

在这个程序中,通过函数f把变量i的地址传给了函数参数表中的指针p,指针p获得了变量i的地址,通过地址就可以访问到变量i,给指针p赋值就是给指针中地址所对应的内存单元赋值,因为指针p指向的地址是变量i的地址,所以修改的内存单元就是变量i的内存单元

# 指针有什么用?

本地变量的例子中,由于块内的本地变量与块外的变量并不相通。所以不能通过函数swap去交换变量a和变量b的值

但是现在有了指针我们可以通过指针来解决这个问题,如下

#include <stdio.h>

void swap(int *pa, int *pb);

int main()
{
    int a = 5;
    int b = 6;

    swap(&a, &b);
    printf("a=%d,b=%d\n", a, b);
    return 0;
}

void swap(int *pa, int *pb)
{
    int t = *pa;
    *pa = *pb;
    *pb = t;
}

还有,比如说函数只能返回一个值,多个值就可以使用指针去处理

例如:输出数组中的最大值与最小值

常见错误

int *p;
*p = 12;

不能使用没有指向任何地方的指针,如果指针没有指向任何地方,在这时使用指针,就会出现异常。

纠正:

int i;
int *p = &i;
*p = 12;

# 指针与数组

  • 数组是一种特殊的指针
  • 在输出数组的内存地址时,&aa的地址与&a[0]的地址是相同的
  • 由于数组变量本身就表达地址,所以在输出地址时可以不使用运算符&
int a[10];

printf("%p\n", &a);
printf("%p\n", a);
printf("%p\n", &a[0]);
printf("%p\n", &a[1]);

输出结果:

000000000061FDF0
000000000061FDF0
000000000061FDF0
000000000061FDF4

这是一个int类型的数组,一个int类型的变量占4个字节,所以这个数组的单元都占4个字节,在数组中&a[1]&a[0]相差4个字节,说明在数组中内存地址是按照顺序连续排列的

  • 在函数的参数表中传入数组a[]作为参数时,会被当作指针处理
void fun(int a[]){
printf("%d\n", sizeof(a));  
}

int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%d\n", sizeof(a));
fun(a);

输出结果:

40
8

通过sizeof得到数组a占40个字节,但传入函数fun后却只占8个字节,因为传入函数的不是整个数组而是一个指针,这个指针指向数组a

  • *运算符可以用于指针,也可以用于数组
int a [] = {1,2,3,4,5,6,7,8,9};
printf("%d\n", *a);
  • 数组变量不能被赋值,因为int a []是一个常量指针,相当于int *const a
int a [] = 1; //不行
int *a = 1;   //可以

# 指针与const

const是一个修饰符,加在变量前表示该变量不能修改,指针也是一种变量,在指针中有两个东西,一个是指针本身,一个是指针所指的变量,指针本身可以是const,它所指的变量也const,它们的区别在于

  • 如果指针是const例如,int *const q = &i;则表示这个指针只能指向变量i不能再指向其他变量,如果这时我们让*q=26是可以的,因为*q指向的是变量ii不是const,但如果我们要用q++来做运算,是不行的。因为现在的qconst
  • 如果const int *p = &i,这时p所指向的变量i的值是可以变的,指针p也可以指向别的变量,但不能通过指针p去做赋值,例如*p=26;int const* p = &i等同const int *p = &i

数组变量本身就是一个const的指针,如果再给这个数组再加const,例如const int a[]={1,2,3,4,5,6};,这表示数组中的每个单元都是const int,只能通过初始化进行赋值

如果我们要把数组传入函数时,同时又不希望它数组被修改,那就可以使用这种方式int fun(const int a[])

# 指针运算

指针是怎么运算的?一个指针+1是加了1这个数字吗?我们来试试看

#include <stdio.h>

int main()
{
    char a = 0;
    char *p = &a;
    printf("P=%p\n", p);
    printf("P=%p\n", p + 1);

    return 0;
}

输出结果

P=000000000061FE0F
P=000000000061FE10

这里输出了两个十六进制的数,在十六进制中,F的下一个数是10,所以这两个十六进制数正好相差1

再换一种数据类型试一下

#include <stdio.h>

int main()
{
    int a = 0;
    int *p = &a;
    printf("p=%p\n", p);
    printf("p=%p\n", p + 1);

    return 0;
}

输出结果

p=000000000061FE14
p=000000000061FE18

在十六进制中4与8相差4,所以这两个十六进制数之间相差4个数

为什么换了一种数据类型它们之间的差距就会发生变化?因为它们加的并不是1而是sizeofsizeof(char)=1,sizeof(int)=4,因为第一次使用的是char类型,所以输出的两个十六进制的值相差1,而第二次换成了int类型,所以输出的两个十六进制数相差4

如果指针运算是通过地址去运算的,那么指针加1就表示到后一个地址,在数组中a[0]的后一个位置就是a[1],以此类推,我们可以通过这样的方式用指针获得数组中的单元

#include <stdio.h>

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9};
    int *p = a;
    printf("(p+1)=%d\n", *(p + 1));

    return 0;
}

输出结果

(p+1)=2

*p=a[0],所以*(p+1)=a[1],这里正好对应数组里的第2个单元,不仅是加法,还可以用减法,指针减1表示到前一个位置,或者其他的算数运算符例如(+,-,+=,-=,++,--)

# 指针相减

#include <stdio.h>

int main()
{
    int a[] = {1, 7, 5, 8, 4, 6, 7, 2, 5};
    int *p = &a[0];
    int *q = &a[5];
    printf("q-p=%d\n", q - p);
    printf("p=%p\n", p);
    printf("q=%p\n", q);

    return 0;
}

输出结果

q-p=5
p=000000000061FDE0
q=000000000061FDF4

通过计算,000000000061FDF4-000000000061FDE0=14,转换为十进制是20,这里使用的是int类型,通常一个int占4个字节(sizeof(int)=4),p和q之间相差20,20/4=5,p和q之间相差5个int的位置,这个值正好与输出的q-p的结果相吻合,所以在这里q-p输出的是两地址间的差值除以sizeof的结果,表示这两个值之间相差了多少个位置

# *p++

  • 取出p所指的数据,并且把p移到下一个位置
  • ++的优先级高于*
  • 在某些CPU上可以被直接翻译成一条汇编命令

# 使用*p++遍历数组

#include <stdio.h>

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,-1};
    int *p = &a[0];
    
while (*p != -1)
{
   printf("%d ", *p++);
}

    return 0;
}

# 指针比较

  • <,<=,==,>,>=,!= 都可以用于指针
  • 指针比较是比较内存中的地址
#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *p = &a[0];
    int *q = &a[1];
    printf("p=%p q=%p\n", p, q);
    if (*p < *q)
    {
        printf("ok");
    }

    return 0;
}

指针的类型

  • 不同类型的指针不能相互赋值
  • void* 表示不知道指向了什么的指针

# 动态内存分配

这是一个通过动态内存分配实现整数倒序的例子

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

int main()
{
    int number;
    int *a;
    int i;
    printf("输入数量:");
    scanf("%d", &number);
    a = (int*)malloc(number* sizeof(int));
    for ( i = 0; i < number; i++)
    {
     scanf("%d",&a[i]);
    }

    for ( i = number-1; i >=0; i--)
    {
      printf("%d",a[i]);
    }
    
    free(a);
    
    return 0;
}

这个例子通过使用函数malloc()实现内存分配,最后又通过free()实现回收内存

# malloc

使用malloc需要头文件<stdlib.h>的支持

#include <stdlib.h>

函数声明

void *malloc(size_t size)
  • 向malloc申请的内存空间大小以字节为单位
  • 一般返会结果的类型是void*

可以通过这样的方式转换为需要的类型

(int*)malloc(size_t size)

空间是有限的,如果空间用完了malloc会返回NULL

# free

为什么要使用free?因为内存空间是有限的,如果只用malloc申请内存,不使用free把内存还回来,就有可能会出现内存不够用的情况

  • 在使用free时,只能回收申请来的内存的首地址

情况1




 


  int i;
  void *p;
  p = malloc(1);
  p=&i;
  free(p);

情况2




 


  int i;
  void *p;
  p = malloc(1);
  p++;
  free(p);

以上两种情况都是不可以的

如果程序中并没有使用malloc申请内存但却用了free回收内存,程序依然可以运行,因为这相当于free(NULL);,在函数free中将什么都不执行

# 字符串

  • C语言的字符串是以字符数组的形式存在的

这是一个字符数组,但它并不是C语言的字符串,因为它不能用字符串的方式去做运算

char word[]={'h','e','l','l','o'}

当我们在数组最后增加一个为'\0'的单元时,它就变成了C语言的字符串,这时它可以用来做字符串的运算

char word[]={'h','e','l','l','o','\0'}
  • 字符串是一个以整数0结尾的一串字符
  • 0'\0'是一样的,但是和'0'不同,因为'0'是字符0,而字符串后面的是整数0
  • 空字符(NULL)在实际底层调用中就是0,\0是转义字符,意思是告诉编译器这不是字符0,而是空字符
  • 0标志字符串的结束,但它并不是字符串的一部分
  • 计算字符串长度时不包括这个0
  • 字符串以数组的形式存在,以数组或指针的形式访问

# 字符串的变量与常量

"括起来的东西叫做字符串的常量(字符串的字面量)

"hello"
  • 它会被编译器变成一个字符数组存放在某处,字符数组的长度是字符长度+1
  • 两个相邻的字符串常量会被自动连接起来
printf("这是字符串" "这也是段字符串");

输出结果:

这是字符串这也是段字符串

如果在使用printf输出时,要输出的内容太多,就需要换行,这时需要在这行字符的结尾加上\就可以输出下一行的内容,如果不加\就会出错

  • 但注意如果下一行内容前有空格,这时会将空格一并输出
printf("这是字符串\
这也是段字符串");

输出结果:

这是字符串这也是段字符串

# 指针表达字符串的变量

这是一个叫str的指针指向了一个字符数组,数组中的内容是hello

char *str = "hello";

如果两个字符串输出的内容一样时,它们都会指向同一个地址





 
 

 
 




#include <stdio.h>

int main()
{
char *s ="hello";
char *s2 ="hello";

printf("s =%p\n",s);
printf("s2=%p\n",s2);

return 0;
}

输出结果:

s =0000000000404000
s2=0000000000404000

这两个字符串指向了同一个地址,但这个地址很小,因为它存放在程序的代码段,代码段里面的代码不可以在运行的时候被修改,在编译时就已经确定,如果修改,就会出错。所以可以把它当作常量。如果想要字符串能被修改,应该使用数组表达字符串

如果两字符串不同时,它们就不会指向同一个地址





 
 







#include <stdio.h>

int main()
{
char *s ="hello";
char *s2 ="hello,world";

printf("s =%p\n",s);
printf("s2=%p\n",s2);

return 0;
}

输出结果:

s =0000000000404000
s2=0000000000404006

现在这两个字符串的地址就不相同了,它们之间相差了6,因为第一个字符串里总共有五个字符,再加上字符串结尾默认生成的0总共占6个字节,所以字符串s2的地址为404006

# 数组表达字符串

这是一个字符数组word,字符串是hello,字符串中的每一个字符都按照顺序依次放在数组的单元中

char word[] = "hello";

使用这种形式的字符串是可以修改的,因为它会存放在本地变量





 

 
 
 




#include <stdio.h>

int main()
{
char word[] = "hello";

printf("%c\n",word[0]);//修改前输出
word[0] = 'b',//修改为b
printf("%c\n",word[0]);//修改后输出

return 0;
}

输出结果:

h
b

定义字符串的长度大小

可以通过这样的方式定义字符串的大小,例如这个字符数组,它的大小为10字节

char line[10] = "hello";

注意:hello是5个字符,但实际上占了6字节,因为编译器会自动在结尾生成一个0,标志字符串的结束

# 字符串赋值

先定义一个指针t指向字符串abc,然后再用指针t赋值给指针s

 
 
 








char *t ="abc";
char *s;
s=t;

printf("*t=%p\n", t);
printf("*s=%p\n", s);

printf("t=%c\n", *t);
printf("s=%c\n", *s);

输出结果:

*t=0000000000404000
*s=0000000000404000
t=a
s=a

赋值后并没有产生新的字符串,而是使s指向了t所指的地址(它们指向了同一个地址),通过指向相同地址的方式实现了字符串的赋值

在这个示例中,输出的并不是字符串abc而是字符串中的第一个字符a.这是因为*s相当于s[0],它们表示的都是字符数组的第一个单元

# 字符串的输入输出

  • 通过scanfprintf输入输出整个字符串时,需要使用占位符%s,而不是%c
  • %c只能输出字符数组中第一个数组单元的字符,%s可以输出整个整个字符数组中的所有字符

字符串输入

scanf("%s", string);

字符串输出

printf("%s", string);

输入字符串时不能包含空格,scanf读到空格就会就会结束当前字符串的读取,如果之后还有字符串要读,就会开始读取下一个字符串的,如果没有,空格后的内容不会被读到

char string[10];
char string2[10];
scanf("%s%s", string, string2);
printf("%s %s", string,string2);

输出结果:

> 1 2 3
1 2

输入1 2 3 ,输出1 2 ,每个字符串间用空格隔开,因为这里scanf只读取两个字符串,所以这里输入的3不会被读到

scanf安全问题:

char string[10];
scanf("%s", string);

在这样一个程序中,字符数组中只能容纳10个字符,但在scanf中可以无限输入任意数量的字符,如果输入的字符数量超过字符数组的容纳范围就会出错,为了解决这个问题,可以通过占位符去限制输入字符的数量,例如:

scanf("%7s", string);
  • 在占位符%s中添加数字,表示最多读取字符的数量。例如%7s,表示最多读取7个字符(小于等于7)

演示

char string[10];
scanf("%7s", string);
printf("%s", string);

输入:

12345678

输出:

1234567
  • 如果输入的字符超过了输入限制,就会把多余的字符输入到下一个字符串中
char string[10];
char string2[10];
scanf("%7s", string);
scanf("%7s", string2);
printf("%s %s", string,string2);

输入:

12345678

输出:

1234567 8

总共输入了8个字符,但scanf限制只能输入7个字符,所以前7个字符放在了字符数组string中,剩下的字符放在了紧跟其后的字符数组string2

# putchar

函数声明

int putchar(int c);
  • 在标准输出中写入一个字符
  • 返回写入的字符数量,返回类型是int
  • 如果失败就会返回EOF(-1)表示文件结束

# getchar

函数声明

int getchar(void);
  • 从标准输出中读入一个字符
  • 返回读到都字符,返回类型是int

示例

int a;
while ((a = getchar()) != EOF){
putchar(a);
}

在这个程序中,输入什么内容,程序就会输出什么内容,这是因为,在键盘中输入的内容会在shell中进行行编辑,在按下回车前,这些内容都在shell中,并没有交给程序,当按下回车后,getchar会一个字符一个字符的读入并且putchar会一个个字符一个字符的写入

使用快捷键Ctrl+C,程序终止;使用Ctrl+Z(在unix中用Ctrl+D),程序结束

# 字符串函数

使用字符串函数时需要引入标准库string.h

#include <string.h>

# strlen

函数声明

size_t strlen(const char *s);
  • 函数中的字符串是常量,不能修改
  • 计算字符串长度时不包括空字符\0
  • size_t表示无符号整数类型

返回值

该函数返回字符串的长度

使用示例

char string[] = "hello";
printf("%lu\n", strlen(string));

# strcmp

函数声明

int strcmp(const char *s1, const char *s2)
  • 这个函数用于字符串的比较
  • 函数中的字符串是常量,不能修改
  • 两个字符串按照ASCII值大小自左向右逐个字符比较,直到出现不同的字符或遇见\0为止

返回值

  • 返回值小于 0,则表示 str1 小于 str2。
  • 返回值大于 0,则表示 str1 大于 str2。
  • 返回值等于 0,则表示 str1 等于 str2。

使用示例

char str1[] = "Hello";
char str2[] = "hello";
printf("%d\n", strcmp(str1, str2));

# strncmp

strncmp函数在strcmp函数的基础上增加了对于比较数量的控制

strncmp(str1, str2, 4);

函数参数表中的4表示最多比较前4个字符

# strcpy

函数声明

char *strcpy(char *dest, const char *src)
  • 该函数可以把src中的字符串拷贝到dest指向的空间中
  • srcdest在内存中不能重叠

参数

  • dest : 指向用于存储复制内容的目标数组
  • src : 要复制的字符串

返回值

该函数返回一个指向dest字符串的指针

使用示例

char str1[100]="abc";
char str2[100];

strcpy(str2,str1);
printf("%s", str2);

输出结果:

abc

还可以直接通过strcpy函数修改字符串

strcpy (str,"hello,world");

# strncpy

strncpy函数在strcpy函数的基础上,增加了对于字符复制数量的控制

strncpy(str2, str1, 10);

函数参数表中的10表示最多复制10个字符,如果要复制的字符数不满足10个时,剩余的空间用空字节填充

# strcat

函数声明

char *strcat(char *str1, const char *str2)
  • 该函数可以把字符串str2追加到字符串str1的后面,组成一个字符串
  • str1必须有足够的空间

返回值

该函数返回一个指向字符串s1的指针

使用示例

char str1[50]="abc";
char str2[50]="def";
   
strcat(str1,str2);
printf("%s",str1);

输出结果:

abcdef

# strncat

strncatstrcat的基础上增加了对于追加字符数量的限制

strncat(str1, str2, 15);

函数参数表中的15表示最多追加15个字符

# strchr

函数声明

char *strchr(const char *str, int c)

参数

  • str :要被检索的字符串
  • c :在字符串str中寻找的字符

返回值

在字符串str从左向右寻找字符c第一次出现的位置,并返回这个位置的指针(内存地址),如果未找到该字符则返回NULL

使用示例

const char str[] = "hello,world";
const char c = 'w';
char *ret;

ret = strchr(str, c);

printf("%c\n", c);
printf("%s\n", ret);

输出结果:

w
world

如果要寻找字符串中字符c第二次出现的位置,可以再使用一次strchr







 
 

const char str[] = "hello,world";
const char c = 'l';
char *ret;
char *p;

ret = strchr(str, c);
p = strchr(ret+1, 'l');
printf("%s\n", p);

输出结果

lo,world

# strrchr

函数声明

char *strrchr(const char *str, int c)

在字符串str中搜索最后一次出现字符c的位置

参数

  • str :要被检索的字符串
  • c :在字符串str中寻找的字符

返回值

在字符串str从左向右寻找字符c最后一次出现的位置,并返回这个位置的指针(内存地址),如果未找到该字符则返回NULL

使用示例

const char str[] = "hello,world";
const char c = 'l';
char *ret;

ret = strrchr(str, c);
printf("%c\n", c);
printf("%s\n", ret);

输出结果:

l
ld

# strstr

函数声明

char *strstr(const char *string, const char *str)

参数

string 被检索的字符串 str 在字符串trisng中寻找的字符串

返回值

该函数返回在string中第一次出现字符串str的指针,如果未找到字符串str则返回null

使用示例

const char string[20] = "hello,world";
const char str[20] = "world";
char *ret;

ret = strstr(string, str);
printf("%s\n", ret);

输出结果:

world

# strcasestr

函数strcasestr和函数strstr基本相同,区别在于strcasestr在寻找的字符串时不区分字母大小写

# 枚举

语法

enum name {a,b,c,...,n};
  • {}中的值从0到n依次递增
  • {}中的元素是int类型

示例:

enum name {a,b,c,d,e,f,g};
enum name day = c;
printf("%d",day);

输出结果:

2

在枚举中默认a=0,b=1,c=2,d=3,e=4...依次递增

指定枚举量

可以在定义枚举类型时改变枚举元素的值

enum name {a,b,c,d=8,e,f,g};
enum name one = e;
enum name twe = b;
printf("%d\n",one);
printf("%d\n",twe);

输出结果:

9
1

这里指定枚举元素d=8,之后的e,f,g就依次为9,10,11,但d之前的枚举元素不受影响,a,b,c依次是0,1,2

枚举还可以这样

  • 省略枚举名称,同时定义枚举类型与枚举变量
enum {a,b,c,d,e,f,g} 
one = a, 
two=b,
three=c;
  • 通过one+1表示下一个枚举元素
enum name {a,b,c,d,e,f,g} one;
printf("%d",one+1);

one默认表示第一个枚举元素aone+1则表示第二个枚举元素b

  • 遍历枚举元素
enum name {a,b,c,d,e,f} one;
for (one = a; one <= f; one++) {
printf("%d ", one);
}

遍历的前提是枚举必须是连续的,以下这种枚举不能遍历,遍历时会忽略枚举元素的赋值

enum {a,b,c=4,d,e,f,g};
  • 直接使用枚举中的元素
enum {a,b,c,d,e,f};
printf("%d %d %d %d %d %d", a, b, c, d, e, f);
  • 在switch中使用枚举
enum { red=1, green, blue }love;
printf("选择你喜欢的水果:(1.苹果 2.橘子 3.香蕉): ");
scanf("%d", &love);
 
switch (love)
{
case red:
printf("你选择了苹果");
     break;
case green:
printf("你选择了橘子");
    break;
case blue:
printf("你选择了香蕉");
    break;
default:
printf("没有这个选项");
}

# 结构

# 结构的语句格式

struct date { 
int a;
char b;
double  c;
} var;
  • date是结构名称,var是结构的变量名,{}中是结构的成员
  • 函数内部声明的结构类型只能在函数内部使用
  • 结构体可以用一个变量表达多个数据
  • 声明了结构,就有了一种自己定义的数据类型struct date

# 结构的形式

形式1: 先声明结构类型,再定义结构变量

//声明结构类型
struct date { 
int a;
int b;
};
struct date d1, d2;//定义结构变量

形式2: 同时定义结构类型和结构变量

  • 结构可以不写名字
  • d1和d2是这个结构形式的变量
struct date { 
int a;
int b;
} d1, d2;

# 结构的初始化

方法1: 按照顺序初始化

struct date var = {1,2,3};

方法2: 指定数据项初始化

struct date var = {.a=1, .b=2, .c=3};

方法3: 声明结构类型并初始化

struct date{
int a;
int b;
int c;
} var = {1,2,3};

# 结构成员

  • 结构中的每一项都可以是不同类型的数据
  • 在结构中使用.访问结构中的成员,例如date.a,表示访问结构date中的a

# 结构运算

  • 要访问整个结构时,可以直接用结构变量的名字
struct date { 
int a;
int b;
int c;
}var;

var =(struct date){1,3,5};
printf("%d %d %d", var.a, var.b, var.c);

通过结构变量给结构中的项初始化

  • 结构允许赋值,取地址,向函数传递参数

结构变量赋值

var.a = 15;

结构变量var给结构变量vari赋值

//声明一个结构类型struct date
struct date { 
int a;
int b;
int c;
} var;

var =(struct date){1,3,5};//结构初始化
struct date vari;//定义变量,类型为struct date
vari = var;//把变量var赋值给vari
printf("%d %d %d", vari.a, vari.b, vari.c);

# 指向结构的指针

指向结构变量

struct date *p = &var;
  • var是结构变量名,struct date是指针类型
  • 获取结构变量地址时要使用运算符&

指向结构变量中的成员

(*p).a = 12;

或者用->表示指针所指的结构变量中的成员

p->a =12;

# 结构作为函数参数

int fun (struct date a);
  • 整个结构可以作为参数的值传入函数
  • 函数可以返回一个结构
  • 如果要把结构传入函数,通常用传递指针的方式

示例:

struct date { 
int a;
int b;
int c;
} var = {1,3,5};

void fun( struct date var ){
printf("%d\n", var.a);
}
fun(var);

# 结构数组

struct date{
int a;
int b;
int c;
}class[3];
  • struct date是结构体类型名,class[3]是结构体数组

使用示例:

  • 结构数组的元素间用,隔开
  • 结构数组元素{}中的三个值分别对应结构声明中的成员a,b,c
struct date { 
int a;
int b;
int c;
};

struct date class[10]={
  {1,2,3}, //class[0]的值
  {4,5,6}  //class[1]的值
};
printf("%d",class[1].b);

输出结果:

5
  • 结构体数组在定义的同时也可以初始化
  • 当数组中全部元素都赋值时,可以不写数组长度
struct date { 
int a;
int b;
int c;
} class[]={
  {1,2,3}, 
  {4,5,6}
};

给结构数组中的元素赋值

class[1].a = 9;

获取结构数组元素的值

int x = class[1].a;

# 自定义数据类型

  • 在C语言中可以使用typedef来定义一个新的数据类型名称
typedef int abc;

这样就使abc成为了int类型的别名,现在可以通过这个新名字去定义变量

 
 




typedef int abc;
abc x = 1;

printf("%d\n", x);
printf("%d\n", sizeof(x));

输出结果:

1
4

这个自定义的abc类型和int类型一样也是4个字节,它们在性质上是一样的,不过是换了一个名字去使用int

typedef也可以用于结构,通过typedef可以简化结构类型的名称

typedef struct date { 
int a;
int b;
int c;
} name;
name x; //定义一个name类型的变量,相当于struct date x
x.a=1; //给结构中的成员a赋值
printf("%d\n", x.a);

结构数组使用typedef

typedef struct date { 
int a;
int b;
int c;
} class;

class x[10]={
  {1,2,3}, 
  {4,5,6} 
};
printf("%d",x[1].b);

# 联合

union date
{
  int a;
  int b;
  int c;
} date;
  • union中的每个成员共同使用同一块内存,但使用时只能储存其中一个成员的数据,各个成员的数据不能共存
  • 所占内存大小等于最大的成员大小,内存大小为4(或者8)的整数倍,如果最大的成员大小不为4的整数倍时,内存大小会自动增大为4的整数倍(32位占4的整数倍,64位占8的整数倍)
union date
{
  int i;
  float f;
  char str[20];
} date;
printf("%d", sizeof(date));

# 全局变量

  • 定义在函数外的叫全局变量,定义在函数内的叫本地变量
  • 全局变量具有全局的生存期和作用域
  • 全局变量可以在任何函数中使用,本地变量只能在包含该变量的函数中使用
int a = 1;

void fun(){
printf("%d\n", a);
} 

int main(){
printf("%d\n", a);
fun();
return 0;
}

输出结果:

1
1

不论在main函数中还是在fun函数中,都可以使用变量a

  • 没有做初始化的全局变量,值为0;如果该变量是指针则会得到null
int a;
int main()
{
   printf("%d\n", a);
   return 0;
}

输出结果:

0
  • 只能用确定的值初始化全局变量
  • 如果函数内存在与全局变量同名的变量时,优先使用函数内的本地变量
int a = 1;

void fun(){
   printf("fun %d\n", a);
}

int main(){
   int a = 3;
   printf("main %d\n", a);
   fun();
   return 0;
}

输出结果:

main 3
fun 1

在函数main中本地变量a覆盖了全局变量a输出值为3,离开函数main后在函数fun中继续使用全局变量a输出值为1

不仅是全局变量,在块中也是一样

int a = 5;
   {
      int a = 3;
      printf("%d\n", a);
   }

输出结果:

3

# 静态本地变量

  • 在本地变量前加上static就成为了静态本地变量
  • 静态本地变量是特殊的全局变量
  • 静态本地变量具有全局的生存期(生存期与程序的运行期相同)
  • 静态本地变量只作用于该函数内
void fun()
{
   static int a = 3;
   printf("a=%d\n", a);
   a += 1;
   printf("a+1=%d\n", a);
}

int main()
{
   fun();
   fun();
   fun();
   return 0;
}

输出结果

a=3
a+1=4
a=4
a+1=5
a=5
a+1=6 
  • 静态本地变量只初始化一次
  • 当离开函数时,静态本地变量的生存期不会随着函数的离开而结束,该变量会一直存在并保持函数离开时的值,等待下次进入函数时,继续使用上次离开时的值,
  • 而本地变量的生存期会随着函数的离开而结束,下次再进入函数时重新初始化

这是使用本地变量的结果

a=3
a+1=4
a=3
a+1=4
a=3
a+1=4
  • 静态本地变量和全局变量位于相同的内存区域
int a = 0;

int main()
{
   static int b = 0;
   int c = 0;

   printf("%p\n", &a);
   printf("%p\n", &b);
   printf("%p\n", &c);

   return 0;
}

输出结果:

0000000000407030
0000000000407034
000000000061FE1C

全局变量和静态本地变量相差4个字节,正好是一个int的位置,在示例中的变量使用的就是int类型,所以全局变量和静态本地变量在内存中是紧挨着的

# 编译预处理

  • 编译预处理指令以#开头

#

  • #define用来定义一个宏
  • #define <名称> <值>,例如#define pi 3.14
  • 因为预处理不是C语言的语句,所以结尾不写;
  • 宏可以没有值
#define pi 3.14

int main()
{
   printf("%f\n", 2*pi);
   return 0;
}

输出结果:

6.280000

#define会让程序在运行前先进行预处理,这时代码中的pi被替换成了3.14,然后再编译程序。在程序中pi并不是变量,它只是普通的文本,预处理只是进行了文本的替换

#define可以替换代码语句中的任何内容,就像这样

#define a int
#define b main
#define c ()
#define d {
#define e printf
#define f (
#define g "%d\n"
#define h ,2);
#define i return 0;
#define j }

a b c d e f g h i j

输出结果:

2

# 预处理运算符

# 宏延续运算符

如果宏太长,一个单行容纳不下,则使用宏延续运算符\

 
 






#define a printf("%d\n", 1);\
          printf("%d\n", 2);

int main(void){
   a 
   return 0;
}

输出结果:

1
2

# 字符串常量化运算符

  • 在宏中可以使用参数表的形式,例如#define x(a, b)
  • 运算符#可以把宏的参数转化成字符串常量,如下
  • 宏中的参数只能是常量
 


 



#define x(a, b) printf(#a" are "#b"?")
 
int main(void){
   x(how, you);
   return 0;
}

错误的宏

#define a(x) (x*3.14)
#define a(x) (X)*3.14

正确的宏

#define a(x) ((X)*3.14)

# 标记粘贴运算符

 


 
 



#define x(n) printf ("%d", abc##n)
 
int main(void){
  int abc34 = 12;
  x(34);
  return 0;
}

预处理过程:

先把abc##n替换成abc34,再把x(34);替换成printf ("%d", abc34);,相当于

int abc34 = 12;
printf ("%d", abc34);

# 参数化宏

在宏中使用条件表达式做判断

#define MAX(x,y) ((x) > (y) ? (x) : (y))
 
int main(void)
{
   printf("Max between 20 and 10 is %d\n", MAX(10, 20));  
   return 0;
}

# 预定义宏

描述
__DATE__ 执行该行代码的日期
__TIME__ 执行该行代码的时间
__FILE__ 当前文件路径
__LINE__ 该行代码所在的行号
__STDC__ 当编译器以ANSI标准编译时为1

演示

printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );

输出结果:

File :D:\C\test.c
Date :Nov 12 2022
Time :14:57:06
Line :8
ANSI :1

# 程序结构

# 引用外部文件

这种方式用于引用系统头文件时,编译前会在C的标准库中搜索名为 file 的文件

#include <file>

这种方式用于引用用户头文件,编译前会在当前文件的目录中搜索名为 file 的文件

#include "file"

也可以直接引用指定路径的文件

#include "./file/a.h"

除了引用头文件,还可以引用C的源文件

#include "./function/b.c"

使用示例:

文件a.c中调用文件b.c的函数

文件a.c中的代码


 
 



 


#include <stdio.h>
#include "./function/b.c"
extern void printb(void);

int main(){                                                        
 printb();
 return 0;
}
  • extern用于声明文件外部的函数(在代码第三行)

文件b.c中的代码

Svoid printb(void)
{
 printf("%s\n","abc");
}

输出结果:

abc

# #include的原理

#include程序编译前会把自己所在的那行代码替换成它所对应的文件中的代码,然后再进行编译

  • 头文件里只有函数原型,而函数的代码在.lib或.a格式的文件中

# 声明

在C语言中,当要使用其他源文件中的全局变量时,需要声明这个变量,例如

extern int a;

使用示例:

文件b.c中的代码

int a = 10;

文件a.c中的代码


 
 


 



#include <stdio.h>
#include "./function/b.c"
extern int a;

int main(){                                                        
 printf("%d", a);
 return 0;
}

输出结果:

10
  • 声明是不产生代码的,定义是产生代码的
  • 一般在头文件中只写声明

# 标准头文件结构

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这样会出现错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下

#ifndef __HAED_FILE__
#define __HAED_FILE__

#endif

代码表示,如果宏__HAED_FILE__未被定义,就定义宏__HAED_FILE__

#endif表示结束,#ifndef表示如果未被定义时,#define表示定义宏

# 文件

# 打开文件

FILE *fopen( const char *filename, const char *mode );
  • filename 是文件名称
  • mode 是文件访问模式
  • 该函数返回一个FILE指针

# 关闭文件

 int fclose( FILE *fp );
  • 关闭文件fclose()函数返回0
  • 如果关闭文件时发生错误,函数返回EOF(EOF是定义在stdio.h中的常量)

使用示例:

FILE *fp = fopen("a.txt", "r"); //打开文件,fp指向目标文件
  if (fp) //如果打开则...
{
  int a;
  fscanf(fp, "%d\n", &a); //把文件内容交个变量a
  printf("%d\n", a); //输出文件内容
  fclose(fp); //关闭文件
}
  else 
{
  printf("无法打开文件\n");
}

注意: 这样的方式只能输出文件中的值不能输出字符串

# 写入文件

使用fputs在文件中写入字符串

int fputs(const char *str, FILE *stream);
  • 函数fputs可以向指定的文件写入字符串
  • str 表示字符串
  • stream 是指向文件的指针

使用示例:

int main()
{
FILE *fp = fopen("a.txt", "w");
fputs("这段字符串被写入文件", fp);
fclose(fp);
}

使用fprintf在文件中写入数据

int fprintf(FILE *stream, const char *format, ...)
  • stream 是指向目标文件的指针
  • format 是要写入文件中的字符串
  • fprintf可以在文件中写入多种类型的数据
  • fprintf可以使用占位符定义写入文件的数据类型

使用示例:


 


FILE *fp = fopen("a.txt", "w");
fprintf(fp,"hello,world");
fclose(fp);

通过占位符定义写入文件的数据类型


 


FILE *fp = fopen("a.txt", "w");
fprintf(fp,"%s","hello,world");
fclose(fp);

使用fprintf就像使用printf一样

fprintf(fp,"%s,%d","hello,world",1970);

# 读取文件

使用fgets读取字符串

char *fgets(char *str, int n, FILE *stream);
  • str 是一个指向字符数组的指针,该数组存储了要读取的字符串
  • n 表示最多读取字符的数量(包括最后的空字符)
  • stream 是指向目标文件的指针
  • fgets函数用来从文件中读入字符串
  • fgets从文件中读取一行字符,并把它存储在str指向的字符串内。
  • 一次只读取一行,当读取到换行符结束

使用示例:

FILE *fp = fopen("a.txt", "r");
char buff[255];
fgets(buff, 255, fp);
printf("%s\n", buff );
fclose(fp);

使用fscanf读取的数据

int fscanf(FILE *stream, const char *format, ...)
  • stream 是指向目标文件的指针
  • format 是要读取的字符串
  • fscanf可以读取多种类型的数据
  • fscanf可以使用占位符定义读取的数据类型

使用示例:

FILE *fp = fopen("a.txt", "r");
char buff[255];
fscanf(fp, "%s", buff);
printf("%s\n", buff );
fclose(fp);

# 文件操作模式

二进制 文本 描述
rb r 文件只读,读取的文件必须存在
wb w 文件只写,如果文件不存在则新键文件,然后写入;如果文件存在则清空重写
rb+ r+ 文件可读可写,读取的文件必须存在
wb+ w+ 文件可读可写,如果文件不存在则新键文件,然后写入;如果文件存在则清空重写
ab a 在文件中追加内容,如果文件不存在则新键文件,然后写入
ab+ a+ 文件可读可写,如果文件不存在则新键文件,写入时追加内容
  • 文件操作模式符号后添加x,表示只新键文件不打开文件

# 二进制文件

  • 所有的文件最终都是二进制的

# 读取二进制文件

fread()函数声明

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr 用于暂存内容的变量
  • size 读这是要读取的每个元素的大小,以字节为单位
  • nmemb 读入元素的个数
  • stream 指向目标文件的指针

使用示例



 



int a;
FILE *fp = fopen("a.bin", "rb");
fread(&a, sizeof(int), 1, fp);
printf("%x\n", a);
fclose(fp);

指针fp指向目标文件,通过函数fread读取二进制文件并把值交给变量a,然后通过printf以十六进制格式输出

# 写入二进制文件

fwrite()函数声明

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr 用于暂存内容的变量
  • size 读这是要读取的每个元素的大小,以字节为单位
  • nmemb 读入元素的个数
  • stream 指向目标文件的指针

使用示例



 


int a = 0xff00;
FILE *fp = fopen("a.bin", "wb");
fwrite(&a, sizeof(int), 1, fp);
fclose(fp);

指针fp指向目标文件,通过函数fwrite把变量a中的内容写入二进制文件

# 位运算

# 位运算符

运算符 含义 运算符 含义
& 按位与 ^ 按位异或
| 按位或 << 左移
~ 按位取反 >> 右移
p q p | q p & q p | q p ^ q
0 0 0 0 0 0
0 1 0 1 0 1
1 1 1 1 1 1
1 0 1 0 0 1

# 按位与&

以下两个二进制数分别对应十六进制的5A和8C。在两个数的相同的位数上,如果都是1结果则为1,否则为0

0101 1010 ——> 5A
1000 1100 ——> 8C
----------------
0000 1000 ——> 08

示例:

unsigned int a=0x5A;
unsigned int b=0x8C;
printf("%hhx",a&b);
  • 使二进制数某一位变为0

如果要使二进制数1011 1010的第一位变成0,可以用一个第一位是0,其他位是1的二进制数与它进行按位与的运算。运算结果除了第一位变成了0,其他位都没有变

1011 1010
0111 1111
----------
0011 1010  <—— 结果

# 按位或|

以下两个二进制数分别对应十六进制的AA和54。在两个数的相同的位数上,只要有一个是1结果就为1,否则为0

1010 1010 ——> AA
0101 0100 ——> 54
----------------
1111 1110 ——> FE

示例:

unsigned int a = 0xAA;
unsigned int b = 0x54;
printf("%hhx", a | b);
  • 使二进制数某一位变为1

如果要使二进制数0101 0100的第一位变成1,可以用一个第一位是1,其他位是0的二进制数与它进行按位或的运算。运算结果除了第一位变成了1,其他位都没有变

0101 0100
1000 0000
----------
1101 0100 <—— 结果
  • 使两个数拼起来
1010 1011 0000 0000 ——> AB00
0000 0000 1100 1101 ——> 00CD
-----------------------------
1010 1011 1100 1101 ——> ABCD

# 按位取反~

按位取反可以使二进制数中的0变为1,1变为0

1010 1011 ——> AB
-----------------
0101 0100 ——> 54

示例:

unsigned char a = 0xAB;
printf("%hhx\n", (char)~a);

# 按位异或^

在两个二进制数相同的位数上,如果这两个位相等结果为0,不相等结果为1

1011 0100
0110 1001
----------
1101 1101
  • 判断两数是否相等

通过按位异或做运算可以判断两数是否相等,如果相等时结果为0

1010 1011 ——> AB
1010 1011 ——> AB
-----------------
0000 0000 ——> 0

# 移位运算

# 左移<<

左移可以使二进制数所有的位左移x个位置

i << x

演示:

   1001 1011 ——> 9B
--------------------
10 0110 1100 ——> 26C

假如i << 2,二进制数中所有的位都会向左移两个位置,右边空出来的位置补两个00

  • 这里的10 0110 1100相当于0010 0110 1100

示例:

unsigned int a = 0x9B;
printf("%hhx", a<<2);

# 右移>>

左移可以使二进制数所有的位右移x个位置

i >> x

演示:

1001 1011 ——> 9B
--------------------
0010 0110 11 ——> 26

假如i >> 2,二进制数中所有的位都会向右侧移两个位置,左边空出来的位置补两个00

  • 这里的0010 0110 11相当于0010 0110,右移时会忽略最后的11

示例:

unsigned int a = 0x9B;
printf("%hhx", a>>2);

# 位段

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”

struct name
{
  type [member_name] : size ;
  type [member_name] : size ;
  type [member_name] : size ;
};
  • name 结构名
  • type 位段数据类型,只能为intunsigned intsigned int三种类型
  • member_name 位域的名称
  • size 该位段的大小,单位为比特(位)
struct name
{
  unsigned int a : 8;
  unsigned int b : 6;
  unsigned int c : 18;
};

使用示例:

struct
{
  unsigned int a : 8;
} var;
  var.a = 4;
  printf("%d\n", var.a);

使用位段的变量时不能超出结构中设定的大小

struct
{
  unsigned int a : 3; //3个比特
} var;

  var.a = 3; //二进制为0011 ——> 11,占2个比特
  printf("%d\n", var.a);

  var.a = 6; //二进制为0110 ——> 110,占3个比特
  printf("%d\n", var.a);

  var.a = 9; //二进制为1001,占4个比特,超过了设定的3比特
  printf("%d\n", var.a);

输出结果

3
6
1

9的二进制为1001,占4个比特,超过了设定的3比特,编译会忽略掉最前面的那一位,变成0001,所以当变量为9时输出的却是1