学习C语言(完整版)

目录

    • 1.第一个程序
    • 2. 变量
    • 3. 常量和宏定义
    • 4. 数据类型
    • 5. 字符和字符串
    • 6. 算术运算符
    • 7. 关系运算符和逻辑运算符
    • 8. if语句
    • 9. switch语句和分支嵌套
    • 10. while语句和do while语句
    • 11.for语句和循环嵌套
    • 12.break语句和continue语句
    • 补充
    • 13.数组
    • 14.字符串处理函数
    • 15. 二维数组
    • 16. 指针
    • 17.指针和数组
      • 指针数组和数组指针
      • 指针和二维数组
    • 18.void指针和NULL指针
      • 指向指针的指针
    • 19.常量和指针
    • 20.函数
      • 参数和指针
      • 指针函数和函数指针
      • 局部变量和全局变量
    • 22.作用域与链接属性
    • 23.生存期和存储类型
    • 24.递归
    • 25. 动态内存管理
      • C语言的内存布局
    • 26.高级宏定义
      • 预处理器
      • 内联函数
      • "#" 和 "##"
      • 可变参数
    • 27.结构体
      • 结构体数组和结构体指针
      • 传递结构体变量和结构体指针
    • 28.typedef
    • 29.共用体
    • 30.枚举类型
    • 31.命令行参数
    • 32.头文件
    • 33.文件读写
    • 经典例题

1.第一个程序

#include 

int main()
{
	printf("hello world!\n");
	pri\              
ntf("hello world!\n");
	return 0;
}
// "\"的作用是:如果有字符串或语句很长,你想分两行写,这时候需要用到这个反斜杠,意思是对前段字符串或语句的延续,但是第二行字符串或语句之前不应该有缩进量。
// \n :表示换行

1)所有的 C 语言程序都需要包含 main() 函数。 代码从 main() 函数开始执行。
2)/* … */ 用于注释说明。
3)printf() 用于格式化输出到屏幕。printf() 函数在 “stdio.h” 头文件中声明。
4)stdio.h 是一个头文件 (标准输入输出头文件) , #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。
5)return 0; 语句用于表示退出程序。
附:

printf("hello world!\n");  // C
std::cout << "hello world!\n";  //C++

2. 变量

我们把要让 CPU 处理的数据都放在内存中,但如果你没有给他安排一个位置,而是随意存放,那么你在后边需要再次用到这个数据的时候,就再也找不到它了。所以变量的意义就是确定目标并提供存放的空间。
学习C语言(完整版)_第1张图片
变量命名需要遵守规范:
1.C语言变量名只能是英文字母(A-Z,a-z)和数字(0-9)或者下划线(_)组成,其他特殊字母不行。下横线通常用于连接一个比较长的变量名,比如i_love_fishC
2.第一个字母必须是由英文字母或者下划线开头,也就是不能用数字开头。
3.变量名区分大小写。因为C语言是大小写敏感的编程语言,也就是大写的FISHC跟小写的fishc会被认为是不同的两个名字。在传统的命名习惯中,我们用小写字母来命名变量,用大写字母来表示符号常量名。
4.不能使用关键字来命名变量。
关键字
学习C语言(完整版)_第2张图片
常用的数据类型:
学习C语言(完整版)_第3张图片

#include 

int main()
{
//数据类型和变量(a b c d)
	int a;
	char b;
	float c;
	double d;

	a = 520;
	b = 'h';   //字符应该是有单引号的,字符串是有双引号的
	c = 3.14;   //单精度浮点型
	d = 3.141592653;   //双精度浮点型

	printf("我的生日是%d\n",a);
	printf("%cello world!\n",b);   //c表示是字符
	printf("圆周率是:%.2f\n", c);  //.2表示小数点后面有两位
	printf("精确到小数点后9位的圆周率是:%11.9f\n",d);  //11.9表示小数点后面有9位,且11表示那串数据占的尺寸
	return 0;
}

3. 常量和宏定义

C 语言中常见的常量

整型常量:520, 1314, 123
实型常量:3.14, 5.12, 8.97
字符常量:普通字符:‘L’, ‘o’, ‘v’, ‘e’ 转义字符:’\n’, ‘\t’, ‘\b’
字符串常量:“FishC”
符号常量:使用之前必须先定义

符号常量的定义格式是

#define 标识符 常量

其中这个 #define 是一条预处理命令(预处理命令都以"#"开头),我们也称为宏定义命令。它的功能就是把程序中所有出现的标识符都替换为随后的常量。

#include 

#define URL  "https://blog.csdn.net/weixin_48678164/article/details/106887891"   //字符串的话要加双引号
#define NAME "博客"     //符号常量为了与普通变量区分开来使用大写
#define BOSS "xliu"
#define YEAR 2020
#define MONTH 06
#define DAY 24

int main()
{
	printf("%s创立于%d年%d月%d日\n",NAME,YEAR,MONTH,DAY);  //s表示是字符串
	printf("%s是%s创立的\n",NAME,BOSS);
	printf("%s的域名是%s\n",NAME,URL);
	return 0;
}

4. 数据类型

在 C 语言里,所谓的数据类型就是坑的大小。我们说变量就是在内存里边挖一个坑,然后给这个坑命名。那么数据类型指的就是这个坑的尺寸。
学习C语言(完整版)_第4张图片
sizeof 运算符

sizeof 用于获得数据类型或表达式的长度,它有三种使用方式:

sizeof(type_name); //sizeof(类型);
sizeof(object); //sizeof(对象);
sizeof object; //sizeof 对象;

#include 

int main()
{
	int i;
	char j;
	float k;

	i = 520;
	j = 'c';
	k = 3.14;

	printf("size of int is %d\n",sizeof(int));  //sizeof+类型,所以要加括号
	printf("size of i is %d\n",sizeof(i));  //sizeof+对象,所以可以不用加括号
	printf("size of char is %d\n",sizeof(char));  //
	printf("size of j is %d\n",sizeof j);  //
	printf("size of float is %d\n",sizeof(float));  //
	printf("size of k is %d\n",sizeof k);  //

	return 0;
}

运行结果是:
学习C语言(完整版)_第5张图片

#include 


int main()
{

	printf("size of int is %d\n",sizeof(int));  
	printf("size of short int is %d\n",sizeof(short));
	printf("size of long int is %d\n",sizeof(long));
	printf("size of long long int is %d\n",sizeof(long long));
	printf("size of char is %d\n",sizeof(char));
	printf("size of float is %d\n",sizeof(float));
	printf("size of double is %d\n",sizeof(double));
	printf("size of long double is %d\n",sizeof(long double));
	printf("size of _Bool is %d\n", sizeof(_Bool)); //其结果是1

	return 0;
}

学习C语言(完整版)_第6张图片
signed 和 unsigned
还有一对类型限定符是 signed 和 unsigned,它们用于限定 char 类型和任何整型变量的取值范围。

signed 表示该变量是带符号位的,而 unsigned 表示该变量是不带符号位的。带符号位的变量可以表示负数,而不带符号位的变量只能表示正数,它的存储空间也就相应扩大一倍。默认所有的整型变量都是 signed 的,也就是带符号位的。

因此加上 signed 和 unsigned 限定符,四种整型就变成了八种:

[signed] short [int]
unsigned short [int]
[signed] int
unsigned [int]
[signed] long [int]
unsigned long [int]
[signed] long long [int]
unsigned long long [int]

#include 

int main()
{
	short i;  //也可以改为int,这里写short可以省略掉int
	unsigned j;//不可以写成 unsigned int,也可以写成 unsigned short

	i = -1;
	j = -1;

	printf("%d\n",i);  
	printf("%u\n",j);   //u代表的是无符号位的

	return 0;
}

在这里插入图片描述
运行结果不是想要的,这是可以修改一下 j=1
附:
学习C语言(完整版)_第7张图片

#include 
#include  //因为这里用到了pow函数,pow(x, y) 用于求 x 的 y 次幂

int main()
{
	int result = pow(2, 32)-1; //int 是4个字节 也就是32个比特位

	printf("result =%d\n",result);  

	return 0;
}
//运行结果是result =-2147483648,与计算值不符

原因是:默认所有的整型变量都是 signed 的,也就是带符号位的。因此第一位是不能存放的,所以只有31个比特位。修改如下**(也可以把32改成31)**

存放 signed 类型的存储单元中,左边第一位表示符号位。如果该位为 0,表示该整数是一个正数;如果该位为 1,表示该整数是一个负数。
一个 32 位的整型变量,除去左边第一位符号位,剩下表示值的只有 31 个比特位。

#include 
#include 

int main()
{
	unsigned int result = pow(2, 32)-1;

	printf("result =%u\n",result);  

	return 0;
}
//运行结果是result =4294967295

学习C语言(完整版)_第8张图片

5. 字符和字符串

1.字符类型事实上是一个特殊的整数类型
字符类型事实上是一个特殊的整型,因此它也有取值范围,signed char 的取值范围是 -128 ~ 127;unsigned char 的取值范围是 0 ~ 255。

2.字符类型与普通整数类型的不同之处
C 标准规定普通整数类型默认使用 signed 修饰符,但没有规定 char 的默认修饰符。因此,使用 signed 或 unsigned 修饰符,是由编译系统自行决定。

3.存放在字符类型中的变量,都可以被解释为 ASCII 字符表中的对应字符
标准 ASCII 字符表使用7位二进制数来表示所有的大写和小写字母,数字 0 到 9、标点符号, 以及在美式英语中使用的特殊控制字符。

其中,ASCII 字符表上的数字 0 ~ 31 以及 127(共 33 个)分配给了控制字符,用于控制像打印机等一些外围设备。这些是看不到的。数字 32 ~ 126 分配给了能在键盘上找到的字符,这些是所见即所得的。
ASCII字符表:https://fishc.com.cn/thread-67427-1-1.html

#include 

int main()
{
	char a = 'C';

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

	return 0;
}
//最后运行结果是C=67
#include 


int main()
{
	char a = 70,b=105,c=114;  //这里也可以修改成 int,其结果是一样的

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

	return 0;
}
//最后的运行结果是Fir

由于字符类型事实上是一个特殊的整型,因此它也有取值范围,signed char 的取值范围是 -128 ~ 127;unsigned char 的取值范围是 0 ~ 255。然后可能会出现如下情况:

#include 


int main()
{
	char height = 175; //把char改为int,输出结果就是175

	printf("xliu的身高是%d\n",height);  

	return 0;
}
//最后的运行结果是xliu的身高是-81,显然这是错误的,修改如下。
#include 


int main()
{
	unsigned char height = 175;

	printf("xliu的身高是%u\n",height);  

	return 0;
}
//最后的运行结果是xliu的身高是175

字符串
声明字符串的语法:char 变量名[数量];
对其进行赋值,事实上就是对这一块空间里边的每一个字符变量进行赋值。我们通过索引号来获得每个字符变量的空间。

变量名[索引号] = 字符;
学习C语言(完整版)_第9张图片

#include 

int main()
{
	char a[6] = { 'x','l','i','u','\0' };

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

	return 0;
}
//运行结果是xliu

学习C语言(完整版)_第10张图片

#include 


int main()
{
	char a[] ="xliu";

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

	return 0;
}
//最后运行结果是xliu

6. 算术运算符

C 语言通过提供大量的运算符来支持我们对数据进行处理,前边我们将一个值存放到变量中,使用的是赋值运算符,就是等于号(=),上节课对字符串中的某个字符进行索引,使用方括号([])作为下标运算符实现。
学习C语言(完整版)_第11张图片
大家可能看到有些运算符后边写双目,有些写单目,那么什么是目呢?

我们把被运算符作用的运算对象称之为操作数,比如 1 + 2,那么 1 和 2 就是被加法运算符(+)作用的两个操作数。我们说一个运算符是双目运算符还是单目运算符,就是看它有多少个操作数。
运算符的优先级和结合性
http://bbs.fishc.com/thread-67664-1-1.html

#include 
#include 

int main()
{
	int i, j, k;

	i = 1 + 2;
	j = 1 + 2 * 3;
	k = i + j + -1 + pow(2, 3);  //使用pow函数时要加上 include,因为pow函数是在这个库中实现的
  //-1的优先级是更高的,结合性是从右到左
	//printf("%d%d%d\n",i,j,k);  
	printf("i=%d\n",i);  
	printf("j=%d\n",j);  
	printf("k=%d\n",k);  

	return 0;
}

运行结果:
在这里插入图片描述
类型转换

#include 

int main()
{
	
	printf("整型输出是%d\n",1+2.0);  
	printf("浮点型输出是%f\n",1+2.0);  
	 

	return 0;
}

在这里插入图片描述
当一个运算符的几个操作数类型不同时,编译器需要将它们转换为共同的某种数据类型,才能进行运算。通常情况下,编译器会将占用坑位比较小的操作数,先转换为与坑位较大的操作数的相同类型,然后再进行运算。这样做的目的就是为了确保计算的精确度。

除了编译器帮你自动转换不同类型的操作数之外,C 语言还允许你强制转换操作数的数据类型。做法就是在操作数的前边用小括号将目标数据类型括起来。

#include 

int main()
{
	
	printf("整型输出是%d\n", 1 +(int)2.0);  //假如2.0修改为1.8,结果是整型输出是2
	printf("整型输出是%d\n", 1 +(int)(1+1.8));
	printf("浮点型输出是%f\n",1+2.0);  
	 

	return 0;
}
//运行结果是整型输出是3  整型输出是3  浮点型输出是3.000000

7. 关系运算符和逻辑运算符

关系运算符
在 C 语言中,使用关系运算符来比较两个数的大小关系。
学习C语言(完整版)_第12张图片注:“=”表示赋值
关系运算符都是双目运算符,其结合性均为左到右。另外,关系运算符的优先级低于算术运算符,高于赋值运算符。

#include 

int main()
{
	int a = 5, b = 3;
	printf("%d\n", 1<2);
	printf("%d\n", a>b);
	printf("%d\n", a<=b+1);
	printf("%d\n",'a'+'b'<='c' );
	printf("%d\n", (a=3)>(b=5) ); 
	 

	return 0;
}
//运行结果是 1 1 0 0 0

逻辑运算符
学习C语言(完整版)_第13张图片

#include 

int main()
{//关系运算符优先级高于逻辑运算符,但是!除外
	int a = 5, b = 3;
	printf("%d\n", 3 > 1 && 1< 2);   
	printf("%d\n", 3 +1||2 == 0);
	printf("%d\n", !(a+b));
	printf("%d\n", !0 +1<1 ||!(3+4));
	printf("%d\n",'a'-'b'&& 'c' );
	
	return 0;
}
//运行结果是 1 1 0 0 1
//非0的都是真,即是“1”

注:关系表达式和逻辑表达式得到的值都是一个逻辑值,也就是表示真的 1 和表示假的 0。但是用于判断一个值是否为真时,以 0 表示假,以任何非 0 的数表示真。一个是编译系统告诉我们的结果,一个是我们让编译系统去判断的,两者方向不同。
短路求值

#include 

int main()
{
	int a = 3, b = 3;

	(a = 0) && (b = 1);  //“与”运算符,只要前面是假,结果就是假
	printf("a=%d,b=%d\n", a,b); 
	(a = 1) ||(b = 1);   //“或”运算符,只要前面是真,结果就是真
	printf("a=%d,b=%d\n", a, b);
	
	 

	return 0;
}

运行结果是:

a=0,b=3
a=1,b=3

8. if语句

那么光有关系表达式和逻辑表达式还不足以实现分支结构,还需要学习一个新的语句——if 语句。

if 语句的实现有好几种形式,逐一给大家介绍一下。
第一种

…… // 其它语句
if (表达式) //当返回逻辑值为真就执行下面的语句块
{
    …… // 逻辑值为真所执行的语句、程序块
}
…… // 其它语句
#include 

int main()
{
	int i;

	printf("你今年多少岁啊:\n"); 
	scanf_s("%d", &i);

	if (i >= 18)
	{
		printf("出门左拐\n");
	}

	return 0;
}

运行结果是

你今年多少岁啊:
18
出门左拐

第二种

…… // 其它语句
if (表达式)
{
…… // 表达式的逻辑值为真所执行的语句、程序块
}
else
{
…… // 表达式的逻辑值为假所执行的语句、程序块
}
…… // 其它语句

#include 

int main()
{
	int i;

	printf("你今年多少岁啊:\n"); 
	scanf_s("%d", &i);

	if (i >= 18)
	{
		printf("出门左拐\n");
	}
	else
	{
		printf("出门右拐\n");
	}
	return 0;
}

运行结果是:

你今年多少岁啊:
16
出门右拐

第三种

…… // 其它语句
if (表达式1)
{
…… // 表达式 1 的逻辑值为真所执行的语句、程序块
}
else if (表达式2)
{
…… // 表达式 2 的逻辑值为真所执行的语句、程序块
}
else if (表达式3)
{
…… // 表达式 3 的逻辑值为真所执行的语句、程序块
}
.
.
.
else if (表达式n)
{
…… // 表达式 n 的逻辑值为真所执行的语句、程序块
}
else
{
…… // 上面所有表达式的逻辑值均为假所执行的语句、程序块
}
…… // 其它语句

#include 

int main()
{
	int i;

	printf("请输入分数:\n"); 
	scanf_s("%d", &i);

	if (i >= 90)
	{
		printf("A\n");
	}
	else if (i>=80 && i<90)
	{
		printf("B\n");
	}
	else if (i >= 70 && i < 80)
	{
		printf("C\n");
	}
	else if (i>=60 && i<70)
	{
		printf("D\n");
	}
	else
	{
		printf("E\n");
	}
	return 0;
}

9. switch语句和分支嵌套

switch语句

…… // 其它语句
switch (表达式)
{
case 常量表达式1: 语句或程序块
case 常量表达式2: 语句或程序块
……
case 常量表达式n:语句或程序块
default: 语句或程序块
}
…… // 其它语句

1)这里每个 case 后边的常量是匹配 switch 后边表达式的值
2)case 后边必须跟一个常量值,而不能是一个范围
3)如果所有的 case 均没有匹配的,那么执行 default 的内容
4)default 是可选的,如果没有 default,并且所有的 case 均不匹配,那么 switch 语句不执行任何动作

switch 语句中的 case 和 default 事实上都是“标签”,用来标志一个位置而已。当 switch 跳到某个位置之后,就会一直往下执行,所以我们这里还需要配合一个 break 语句,让代码在适当的位置跳出 switch。
学习C语言(完整版)_第14张图片

#include 

int main()
{
	char a;

	printf("请输入你的成绩:\n"); 
	scanf_s("%c", &a);

	switch (a)
	{
	case 'A':printf("你的成绩是在90分以上\n"); break;
	case 'B':printf("你的成绩是在80分到90分之间\n"); break;
	case 'C':printf("你的成绩是在70分到80分之间\n"); break;
	case 'D':printf("你的成绩是在60分到70分之间\n"); break;
	case 'E':printf("你的成绩是在60分以下\n"); break;
	default:printf("请输入正确的成绩\n"); break;
	}
	return 0;
}

在这里插入图片描述
假如没有break语句,会出现这样的结果:
学习C语言(完整版)_第15张图片
分支结构的嵌套
如果在一个 if 语句中包含另一个 if 语句,我们就称之为 if 语句的嵌套,也叫分支结构的嵌套。
学习C语言(完整版)_第16张图片
学习C语言(完整版)_第17张图片
按照流程图来写代码:

#include 

int main()
{
	int a,b;

	printf("请输入两个数:\n"); 
	scanf_s("%d%d", &a,&b);

	if (a != b)
	{
		if (a > b)
		{
			printf("%d > %d\n", a, b);
		}
		else
		{
			printf("%d < %d\n", a, b);
		}
	}
	else
	{
		printf("%d =%d\n", a, b);
	}
	return 0;
}


考虑下面的代码片段:

……
if (x == 0)
    if (y == 0)
        error();
else
    z = x + y;
……

这段代码中编程者的本意是应该有两种主要情况,x 等于 0 以及 x 不等于 0。对于 x 等于 0 的情形,除非 y 也等于 0(此时调用 error 函数),否则程序不作任何处理;对于 x 不等于 0 的情形,程序将 x 与 y 之和赋值给 z。

然而,这段代码实际上所做的却与编程者的意图相去甚远。

原因在于 C 语言中有这样的规则,else 始终与同一对括号内最近的未匹配的 if 结合。如果我们按照上面这段程序实际上被执行的逻辑来调整代码缩进,大致是这个样子:

……
if (x == 0)
    if (y == 0)
        error();
    else
        z = x + y;
……

也就是说,如果 x 不等于 0,程序将不会做任何处理。如果要得到原来的例子中由代码缩进体现的编程者本意的结果,应该这样写:

……
if (x == 0)
{
    if (y == 0)
    {
        error();
    }
}
else
{
    z = x + y;
}
……

现在,else 与第一个 if 结合,即使它离第二个 if 更近也是如此,因为此时第二个 if 已经被括号“封装”起来了。
举例:

#include 

int main()
{
	char isfree,israin;

	printf("是否有空:(Y/N)\n"); 
	scanf_s("%c", &isfree);

	 //getchar();

	printf("是否下雨:(Y/N)\n");
	scanf_s("%c", &israin);

	if (isfree == 'Y') {
		if (israin == 'Y') {
			printf("记得带伞\n");
		}
	}
	else {
		printf("没空\n");
	}
	return 0;
}

运行结果是:
在这里插入图片描述
注:为啥会出现这样的情况,因为在运行时输入了回车,回车也是占一个字符的,因此我用 getchar() 过滤掉回车,就出现这样的结果。
把注释去掉就行了。

#include 

int main()
{
	char hasbf;

	printf("是否有bf:(Y/N)\n"); 
	scanf_s("%c", &hasbf);

	if (hasbf == 'Y')  //注意这里不能写“=”,否则就会出错
	{ 
			printf("祝福\n");
	}
	else 
	{
		printf("那就.....\n");
	}
	return 0;
}

等于号带来的问题
在 C 语言中使用等号(=)作为赋值运算,使用连续两个等号(==)作为比较运算。一般而言,赋值运算相对于比较运算出现得更频繁,因此字符较少的 = 就被赋予了更常用的含义——赋值操作。此外,在 C 语言中赋值符号被作为一种操作符对待,因而重复进行赋值操作(如 a = b = c)可以很容易地书写,并且赋值操作还可以被嵌入到更大的表达式中。

但是,这种使用上的便利性可能导致一个潜在的问题:当程序员本意是在作比较运算时,却可能无意中误写成赋值运算。

比如下例,该语句本意似乎是要检查 x 是否等于 y :

if (x = y)
    break;

而实际上是将 y 的值赋给了 x ,然后检查该值是否为零。

10. while语句和do while语句

while语句

while (表达式)
    循环体

执行过程流程图:
学习C语言(完整版)_第18张图片
举例1:
学习C语言(完整版)_第19张图片

#include 

int main()
{
	int i = 1,sum = 0;

	while (i <= 100) {
		sum = sum + i;
		i = i + 1;
	}
	printf("结果是:%d\n",sum);
	return 0;
}
//运行结果是5050

举例2:
学习C语言(完整版)_第20张图片

#include 

int main()
{
	int count= 0;

	printf("请输入英文字符:\n");

	while (getchar() != '\n') {
		count=count+1;
	}
	printf("字符个数是:%d\n",count);
	return 0;
}

运行结果是:
在这里插入图片描述
getchar() :https://fishc.com.cn/forum.php?mod=viewthread&tid=68661&extra=page%3D1%26filter%3Dtypeid%26typeid%3D583

#include 

int main()
{
	char a, b, c;

	a = getchar();
	b = getchar();
	c = getchar();

	putchar(a);
	putchar(b);
	putchar(c);
	putchar('\n');
/*也可以改成
#include 

int main()
{
	
	putchar(getchar());
	putchar(getchar());
	putchar(getchar());
	putchar('\n');

	return 0;
}*/
	return 0;
}
#include 

int main()
{
	char a, b, c;

	a = getchar();
	getchar();
	b = getchar();
	getchar();
	c = getchar();

	putchar(a);
	putchar(' ');
	putchar(b);
	putchar(' ');
	putchar(c);
	putchar('\n');
	
	return 0;
}

do while语句

do
    循环体
while (表达式);

学习C语言(完整版)_第21张图片
while 是先判断表达式,如果表达式结果为真,才执行循环体里边的内容;

而 do…while 则相反,不管三七二十一,先执行循环体的内容再判断表达式是否为真。

注意:do…while 语句在 while 后边一定要用分号(;)表示语句结束
比较:
学习C语言(完整版)_第22张图片
学习C语言(完整版)_第23张图片
学习C语言(完整版)_第24张图片

11.for语句和循环嵌套

循环的基本结构:
学习C语言(完整版)_第25张图片
在这里插入图片描述
for 语句:对于 while 语句,这些动作是分散在三个不同的地方。那如果能够把它们都集中到一块,那么对于后期无论是调试也好修改也罢,无疑就便捷了许多。没错,当年 C 语言作者也是跟我们想到一块去了,所以 for 语句就这么应运而生。

for (表达式1; 表达式2; 表达式3)
        循环体

三个表达式用分号隔开,其中:
1)表达式1是循环初始化表达式
2)表达式2是循环条件表达式
3)表达式3是循环调整表达式

这样一来,for 语句将初始化计数器、循环条件判断、更新计数器三个动作组织到了在一起,那么以后如果要修改循环的次数,每次递进的跨度,或者循环结束条件,只需要在 for 语句后边的小括号内统一修改即可。
举例:

#include 

int main()
{
	int count;

	for (count = 0; count < 10; count++)
	{
		printf("love\n");
	}
	return 0;
}
//运行结果是10个love

比较一下:
学习C语言(完整版)_第26张图片
练习
学习C语言(完整版)_第27张图片

#include 

int main()
{
	int i, sum;
	_Bool flag = 1; //也可以替换为 int  //_Bool :用来判断是与不是        

	printf("请输入一个整数:\n");
	scanf_s("%d", &sum);

	for (i = 2; i < sum / 2; i++)
	{
		if (sum % i == 0) {
			flag = 0; //将flag设置为0   //再多加一个变量来判断是不是素数
		}
		
	}
	if (flag)  //也可以加上等于1
	{
		printf("%d是一个素数\n",sum);
	}
	else {
		printf("%d不是一个素数\n",sum);
	}
	return 0;
}

灵活的for语句
学习C语言(完整版)_第28张图片
举例:

#include 

int main()
{
	int count=0;

	for (; count < 10; count++)
	{
		printf("love\n");
	}
	return 0;
}

也可以这样修改:

#include 

int main()
{
	int count = 0;

	for (; count < 10; )
	{
		printf("love\n");
		count++;
	}

	return 0;
}
#include 

int main()
{
	int i,j;

	for (i=0,j=10; i<j; i++,j--)  //也可以在表达式中定义变量,即加个int
	{
		printf("%d\n",i);
	}
	return 0;
}
//运行结果是0 1 2 3 4

循环嵌套
对于嵌套的循环结构,执行顺序是从内到外:先执行内层循环,再执行外层循环。

#include 

int main()
{
	int i,j;

	for (i=0; i<3; i++) 
	{
		for (j = 0; j < 3; j++)
		{
			printf("i=%d,j=%d\n", i,j);
		}
		
	}
	return 0;
}

运行结果是:
学习C语言(完整版)_第29张图片
练习:
学习C语言(完整版)_第30张图片

#include 

int main()
{
	int i,j;

	for (i=1; i<=9; i++) 
	{
		for (j = 1; j <= i; j++)
		{
			printf("%d * %d = %-2d  ", i, j, i * j);  //如果在这里加了换行符,九九乘法表出现的格式就不一样了,另外使用”-2d“是因为到后面的结果值会越来越大
		}
		putchar('\n');  //因为九九乘法表需要换行的,所以还要加上putchar
	}
	return 0;
}

学习C语言(完整版)_第31张图片
putchar函数:https://fishc.com.cn/forum.php?mod=viewthread&tid=68879&extra=page%3D1%26filter%3Dtypeid%26typeid%3D583

#include 

int main()
{
	char a = 'B', b = 'O', c = 'Y';//改为int a = 66, b = 79, c = 89也是可以的;

	putchar(a);//向显示器输出字符B
	putchar(b);
	putchar(c);
	putchar('\n');//向显示器输出换行符

	return 0;
}

12.break语句和continue语句

break 语句
那么在循环体中,如果我们想要让程序在中途跳出循环,那么我们同样可以使用 break 语句来实现。

执行 break 语句,直接跳出循环体。

有一点需要注意的是,对于嵌套循环来说,break 语句只负责跳出所在的那一层循环,要跳出外层循环则需要再布置一个 break 语句才行。

#include 

int main()
{
	long long i, sum;
	_Bool flag = 1; //也可以替换为 int  //_Bool :用来判断是与不是        

	printf("请输入一个整数:\n");
	scanf_s("%lld", &sum);

	for (i = 2; i < sum / 2; i++)
	{
		if (sum % i == 0) {
			flag = 0; //将flag设置为0   //再多加一个变量来判断是不是素数
			break;  //减少循环的次数
		}

	}
	if (flag)  //也可以加上等于1
	{
		printf("%d是一个素数\n", sum);
	}
	else {
		printf("%d不是一个素数\n", sum);
	}
	printf("i=%lld\n", i);
	return 0;
}

在这里插入图片描述
如果把break给注释掉,会出现这样的结果:
在这里插入图片描述
举例:

#include 

int main()
{
	int i, j;

	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			if (j == 4)
			{
				break;
			}
		}
	}
	printf("i=%d,j=%d\n",i,j);
	return 0;
}

在这里插入图片描述
比较一下:

#include 

int main()
{
	int i, j;

	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			if (j == 4)
			{
				break;
			}
		}
		if (j == 4)
		{
			break;
		}
	}
	printf("i=%d,j=%d\n",i,j);
	return 0;
}

在这里插入图片描述
continue语句
当满足某个条件的时候,跳过本轮循环的内容,直接开始下一轮循环。这时候我们应该使用 continue 语句。

当执行到 continue 语句的时候,循环体的剩余部分将被忽略,直接进入下一轮循环。

对于嵌套循环来说,continue 语句跟 break 语句是一样的:它们都只能作用于一层循环体。

#include 

int main()
{
	char ch;  //因为getchar返回的是int

	while ((ch = getchar()) != '\n')  //
	{
		if (ch == 'o')
		{
			continue; //相当于跳过‘o’
		}
		putchar(ch); //printf("%c", ch);
		
	}
	putchar('\n'); //printf("\n");//这里相当于起到了换行的作用
	
	return 0;
}

在这里插入图片描述
for 语句和 while 语句执行过程的区别

for 语句和 while 语句执行过程是有区别的,它们的区别在于出现 continue 语句时。

在 for 语句中,continue 语句跳过循环的剩余部分,直接回到调整部分。

在 while 语句中,调整部分是循环体的一部分,因此 continue 语句会把它也跳过。

#include 

int main()
{
	int i = 0;

	while (i<10)  
	{
		if (i%2)
		{
			continue; 
		}
		i++; //这样就会陷入死循环,应该把它放入if前面
	}
	return 0;
}

比较一下for语句:

#include 

int main()
{
	int i;

	for (i = 0; i < 100; i++)
	{
		if (i % 2)
		{
			continue;
		}
		printf("%d ", i);
	}
	return 0;
}

补充

复合的赋值运算符
学习C语言(完整版)_第32张图片
自增自减运算符
学习C语言(完整版)_第33张图片
举例:

#include 

int main()
{
	int i=5,j;

	j = i++;
	printf("i=%d,j=%d\n", i, j);

	i = 5;
	
	j = ++i;
	printf("i=%d,j=%d\n", i, j);
	
	return 0;
}

在这里插入图片描述
逗号运算符
逗号表达式的语法是:表达式1,表达式2,表达式3,… ,表达式n

逗号表达式的运算过程为:从左往右逐个计算表达式;
逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
学习C语言(完整版)_第34张图片
条件运算符
有一个操作数的运算符称为单目运算符,有两个两个操作数称为双目运算符,然而 C 语言还有唯一的一个三目运算符,它的作用是提供一种简写的方式来表示 if-else 语句。

语法:exp1 ? exp2 : exp3;

exp1 是条件表达式,如果结果为真,返回 exp2,如果为假,返回 exp3。

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

可以直接写成:

max = a > b ? a : b;

13.数组

数组的定义
学习C语言(完整版)_第35张图片
学习C语言(完整版)_第36张图片
举例:
尝试用数组存放班里10位同学的数学成绩,并计算出平均数。

#include 
#define NUM 5

int main()
{
	int i, sum=0;
	int a[NUM];


	for (i = 0; i <NUM ; i++)
	{
		printf("请输入第%d位同学的成绩:\n",i+1);
		scanf_s("%d", &a[i]);
		sum += a[i];
	}
	printf("输出的平均分为:%.2f\n", (double)sum / NUM);

	return 0;
}

学习C语言(完整版)_第37张图片
学习C语言(完整版)_第38张图片

#include 


int main()
{
	int a[10] = { 0 };
	int i;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", a[i]);
	}

	return 0;
}

学习C语言(完整版)_第39张图片

#include 

int main()
{
	int a[] = { 1,2,3,4,5 };
	int i;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", a[i]);
	}

	return 0;
}

学习C语言(完整版)_第40张图片

14.字符串处理函数

字符数组:char str[] =" love";
获取字符串的长度:strlen

#include 
#include   //因为调用了strlen函数

int main()

{
	char str[] = "i love you";

	printf("sizeof str=%d\n", sizeof(str));
	printf("strlen str=%u\n", strlen(str)); //因为strlen函数(https://fishc.com.cn/thread-68741-1-1.html)返回值是无符号整型
	return 0;
}

在这里插入图片描述
原因是:sizeof包含到了反斜杠0
拷贝字符串:strcpy 函数和 strncpy 函数

#include 
#include 

int main()
{
    char str1[] = "Original String";
    char str2[] = "New String"; 
    char str3[100];

    strcpy_s(str1, 13,str2); //这里要多加一个目标函数长度,不然会报错,同下
    //字符串拷贝的时候会把反斜杠0也拷贝过去,要保证目标数组的长度大于原数组的长度
    strcpy_s(str3, 20,"Copy Successful");

    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);
    printf("str3: %s\n", str3);

    return 0;
}

在这里插入图片描述
在取消安全函数的限制之后:

#include 
#include 

int main()
{
    char str1[] = "Original String";
    char str2[] = "New String";
    char str3[100];

    strcpy(str1,str2);
    strcpy(str3,"Copy Successful");

    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);
    printf("str3: %s\n", str3);

    return 0;
}

strncpy函数的用法:

#include 
#include 

int main()
{
    char str1[16] = "Original String";
    char str2[11] = "New String";
    char str3[100];

    strncpy(str1,str2, 11);
    strncpy(str3,"Copy Successful", 16);

    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);
    printf("str3: %s\n", str3);

    return 0;
}

在这里插入图片描述

#include 
#include 

int main()
{
    char str1[] = "Original String";
    char str2[100];

    strncpy_s(str2,100,str1,5);

    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);

    return 0;
}

在这里插入图片描述

#include 
#include 

int main()
{
    char str1[] = "Original String";
    char str2[] = "I love you";

    strncpy(str2, str1, 5);//复制的字符不应多于str2中原有的字符个数

    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);

    return 0;
}

连接字符串:strcat 函数和 strncat 函数

#include 
#include 

int main()
{
    char str1[] = "i love";
    char str2[]=" you";

    strcat_s(str1,12,str2);

    printf("str1: %s\n", str1);

    return 0;
}

在这里插入图片描述

#include 
#include 

int main()
{
    char str1[100] = "i love";//空间要足够大
    char str2[] = " you";

    strcat(str1,str2);

    printf("str1: %s\n", str1);

    return 0;
}

比较字符串:strcmp 函数和 strncmp 函数

#include 
#include 

int main()
{
    char str1[10] = "FishC.com";
    char str2[20] = "FishC.com";

    if (!strcmp(str1, str2))
    {
        printf("两个字符串完全一致!\n");
    }
    else
    {
        printf("两个字符串不同!\n");
    }

    return 0;
}
//运行结果为两个字符串完全一致

学习C语言(完整版)_第41张图片
学习C语言(完整版)_第42张图片

#include 

int main()
{
	char str[100];

	printf("Enter a value :");
	gets(str);

	printf("\nYou entered: ");
	puts(str);
	return 0;
}

15. 二维数组

学习C语言(完整版)_第43张图片
二维数组的定义:
定义二维数组的方法跟一位数组相似,使用方括号指定每个维度的元素个数:

类型 数组名[常量表达式][常量表达式]

int a[6][6]; // 6*6,6行6列
char b[4][5]; // 4*5,4行5列
double c[6][3]; // 6*3,6行3列

二维数组的访问:

a[0][0]; // 访问a数组中第1行第1列的元素
b[1][3]; // 访问b数组中第2行第4列的元素
c[3][3]; // 访问c数组中第4行第4列的元素

跟访问一维数组相似,同样是使用下标访问数组中的元素。同样需要注意下标的取值范围,以防止数组的越界访问。
比如 int a[3][4],其“行下标”的取值范围是 0~2,“列下标”的取值范围是 0~3,超出任何一个下标的访问都会造成越界。

二维数组的初始化:

int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int a[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
};
#include 

int main()
{
    int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
    int i, j;

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 4; j++)
        {
            printf(" %d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

在这里插入图片描述
二维数组也可以仅对部分元素赋初值:

int a[3][4] = {{1}, {5}, {9}};
#include 

int main()
{
    int a[3][4] = { {1},{5},{9} };
    int i, j;

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 4; j++)
        {
            printf(" %d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

在这里插入图片描述
将二维数组初始化为0

int a[3][4] = {0};	

C99 同样增加了一种新特性:指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而未被赋值的元素自动初始化为 0:

int a[3][4] = {[0][0] = 1, [1][1] = 2, [2][2] = 3};

二维数组的初始化也能偷懒,让编译器根据元素的数量计算数组的长度。但只有第 1 维的元素个数可以不写,其他维度必须写上:

int a[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

将之前矩阵转置一下:

#include 

int main()
{
    int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
    int i, j;

    for (i = 0; i < 4; i++)  //把3改成4
    {
        for (j = 0; j < 3; j++)  //把4改成3
        {
            printf(" %d ", a[j][i]);  //i和j对调一下
        }
        printf("\n");
    }
    return 0;
}

在这里插入图片描述

#include 

int main()
{
    int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} }; int b[4][3];
    int i, j;

    for (i = 0; i < 3; i++)  //把3改成4
    {
        for (j = 0; j < 4; j++)  //把4改成3
        {
            printf(" %d ", a[i][j]);//i和j对调一下
            b[j][i] = a[i][j];
        }
        printf("\n");
    }
    printf("\n");

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf(" %d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}

16. 指针

指针和指针变量:
通常我们所说的指针,就是地址的意思。C 语言中有专门的指针变量用于存放指针,跟普通变量(存储的是数据)不同,指针变量存储的是一个地址

指针变量也有类型,它的类型就是存放的地址指向的数据类型。
学习C语言(完整版)_第44张图片

定义指针变量:

char *pa;
int *pb;

左侧的数据类型表示指针变量中存放的地址指向的内存单元的数据类型。

比如刚才的图中,指针变量 pa 中存放字符**变量 a 的地址,**所以 pa 应该定义为字符型指针;而指针变量 pb 中存放的是整型变量 f 的地址,所以 pb 就定义为整型指针。这点一定要注意,因为不同数据类型所占的内存空间不同,如果指定错误了,那么在访问指针变量指向的数据时就会出错。
取地址运算符和取值运算符
学习C语言(完整版)_第45张图片
这里要注意的是取值运算符跟定义指针用的都是星号(*),这属于符号的重用,在不同的地方有不同的意义:在定义时表示定义一个指针变量;在其他位置表示获取指针变量指向的变量的值。

直接通过变量名来访问变量的值,我们称之为直接访问;通过指针变量这样的形式来访问变量的值,我们称之为间接访问,所以取值运算符有时候也叫间接运算符。

#include 

int main()
{
    char a = 'F';
    int f = 123;

    char *pa = &a;
    int *pb = &f;//获取变量f的地址,然后放到对应的指针变量里面去

    printf("a=%c\n", *pa); //*pa 是取这个变量里面的地址指向的数据的值
    printf("f=%d\n", *pb); //*pb 是取这个变量里面的地址指向的数据的值
    return 0;
}
//运行结果是:a=F f=123
#include 

int main()
{
    char a = 'F';
    int f = 123;

    char *pa = &a;
    int *pb = &f;//获取变量f的地址,然后放到对应的指针变量里面去

    printf("a=%c\n", *pa); //*pa 是取这个变量里面的地址指向的数据的值
    printf("f=%d\n", *pb); //*pb 是取这个变量里面的地址指向的数据的值

    *pa = 'C';
    *pb += 1;

    printf("a=%c\n", *pa); 
    printf("f=%d\n", *pb);

    printf("size of=%d\n", sizeof(pa)); //输出结果是:size of=4
    printf("size of=%d\n", sizeof(pb));

    printf("%p\n", pa);//p是打印地址类型的数据
    printf("%p\n", pb);
    return 0;
}

学习C语言(完整版)_第46张图片

17.指针和数组

#include 

int main()
{
    int a;
    int* p = &a;

    printf("请输入一个整数:\n");
    scanf_s("%d", &a);
    printf("a=%d\n", a);

    printf("请在输入一个整数:\n");
    scanf_s("%d", p);//因为p已经存放的是a的地址,所以不用加取值操作符,加了的话就变成p的地址
    printf("a=%d\n", a);//也可以把a改成*p

    return 0;
}

学习C语言(完整版)_第47张图片

#include 

int main()
{
    char str[128];

    printf("请输入鱼c的域名:");
    scanf_s("%s", str);//这里也是不用加取地址操作符
    
    printf("鱼c的域名是%s\n", str);

    return 0;
}//运行出错

数组名是数组第一个元素的地址,也是数组的首地址。

#include 

int main()
{
    char str[128];

    printf("请输入鱼c的域名:");
    scanf_s("%s", str);//这里也是不用加取地址操作符
    //printf("鱼c的域名是%s\n", str);

    printf("str 的 地址是:%p\n",str);
    printf("str 的 地址是:%p\n",&str[0]); //这两个输出的地址类型数据是一样的,因为数组名是数组第一个元素的地址

    return 0;
}//运行出错

指向数组的指针

int a[] = {1, 2, 3, 4, 5};
int *p;
p = a; // 语句1
p = &a[0]; // 语句2

因为数组名即数组第一个元素的地址,所以语句 1 和语句 2 是等价的,都是将数组 a 的首地址存放到指针变量 p 中。
指针的运算
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第 n 个元素。比如 p+1 表示指向 p 指针指向的元素的下一个元素;p-1 则表示指向上一个元素。

需要郑重强调的是:p+1 并不是简单地将地址加 1,而是指向数组的下一个元素。

#include 

int main()
{
    char a[]="love";
    char* p = a;

    printf("*p=%c,*(p+1)=%c,*(p+2)=%c\n", *p, *(p + 1), *(p + 2));

    return 0;
}

在这里插入图片描述

#include 

int main()
{
    char a[]="love";
    char* p = a;

    //printf("*p=%c,*(p+1)=%c,*(p+2)=%c\n", *p, *(p + 1), *(p + 2));
    printf("*a=%c,*(a+1)=%c,*(a+2)=%c\n", *a, *(a + 1), *(a + 2));

  //把p改为a结果是一样的

    return 0;
}

在这里插入图片描述
学习C语言(完整版)_第48张图片

#include 

int main()
{
    char* str = "I love you";
    
    printf("%s\n", str);

    return 0;
}

指针数组和数组指针

指针和数组的区别:
指针是左值(什么是 lvalue,什么是 rvalue?);
而数组名只是一个地址常量,它不可以被修改,所以数组名不是左值。
错误示例:

#include 


int main()
{
	char str[] = "love";
	
	int count = 0;
	while (*str++ != '\0') //需要左值
	{
		count++;
	}
	printf("总共有%d个字符\n",count);
	return 0;
}

正确示例:

#include 


int main()
{
	char str[] = "love";
	char* target = str;
	int count = 0;
	while (*target++ != '\0') //先运算++
	{
		count++;
	}
	printf("总共有%d个字符\n",count);
	return 0;
}

学习C语言(完整版)_第49张图片
指针数组:
数组下标[]要比取值运算符*,优先级要高,所以先入为主,p1 被定义为具有 5 个元素的数组。那么数组元素的类型呢?是整型吗?显然不是,因为还有一个星号,所以它们应该是指向整型变量的指针。
结论:指针数组是一个数组,每个数组元素存放一个指针变量。

#include 


int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	int e = 5;
	int* p1[5] = { &a,&b, &c, & d, &e };
	int i;
	for (i = 0; i < 5; i++)
	{
		printf("%d\n", *p1[i]);
	}
	return 0;
}//运行结果是:12345
#include 

int main()
{
	char* p1[5] = {
          "让编程改变世界--鱼C工作室",
          "Just do it ..NIKE",
          "一切皆有可能--李宁",
          "永不止步--安踏",
          "One more thing.. .-苹果",
	};
	int i;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n",p1[i]);//不能加*,加了就变成求字符了,我们要求字符串,所以要给出字符串的地址
	}
	return 0;
}

数组指针
因为圆括号和数组下标位于同一个优先级队列,所以我们就要看先来后到的问题了。由于它们的结合性都是从左到右,所以 p2 先被定义为一个指针变量。那么它指向谁?还能有谁?后边还紧跟着一个具有 5 个元素的数组,p2 指向的就是它。由于指针变量的类型事实上就是它所指向的元素的类型,所以这个 int 就是定义数组元素的类型为整型。
结论:数组指针是一个指针,它指向的是一个数组。
错误示范:

#include 

int main()
{
	int temp[5] = { 1, 2, 3,4,5 };
	int(*p2)[5] = temp;
	int i;
	for (i = 0; i < 5; i++){
		printf("%d\n", *(p2 + i));
}
	return 0;
}

正确示范:

#include 

int main()
{
	int temp[5] = { 1, 2,3,4,5 };
	int* p = temp; //这个指针指向的是数组的第一个元素的地址,而不是指针
	int i;
	for (i = 0; i < 5; i++) {
		printf("%d\n", *(p + i));
	}
	return 0;
}

也可以替换成这样:

#include 

int main()
{
	int temp[5] = { 1, 2,3,4,5 };
	int* p = temp; //这个指针指向的是数组的第一个元素的地址,而不是指针
	
	for (; p<temp+5;p++) {
		printf("%d\n", *p);
	}
	return 0;
}
#include 

int main()
{
	int temp[5] = { 1, 2, 3,4,5 };
	int(*p2)[5] = &temp;//我们要取出的是这个数组的地址,而不是变量的地址
	int i;
	for (i = 0; i < 5; i++){
		printf("%d\n", *(*p2 + i));//代表元素p2[0][i]
}
	return 0;
}

指针和二维数组

学习C语言(完整版)_第50张图片学习C语言(完整版)_第51张图片

学习C语言(完整版)_第52张图片

#include 

int main()
{
	int array[2][3] = { {0, 1, 2},{3,4,5} };
	int(*p)[3] = array;

	printf("**(p + 1) : % d\n", **(p+1));
	printf("** (array+1): %d\n", **(array + 1));
	printf("array[1][0]: %d\n", array[1][0]);
	printf("*(*(p+1)+2); %d\n", *(*(p + 1) + 2));
	printf("*(*(array+1)+2): %d\n", *(*(array + 1) + 2));
	printf("array[1][2]: %d\n",array[1][2]) ;

	return 0;
}

学习C语言(完整版)_第53张图片

18.void指针和NULL指针

void 类型
void 即的字面意思是“无类型”,定义变量的时候,我们通过类型来决定该变量所占的内存空间。

比如在我们的系统中,char 类型占 1 个字节,int 类型占 4 个字节,double 类型占 8 个字节等。那 void 既然是无类型,我们就不应该用它来定义一个变量,如果你一定要这么做,那么程序就会给你报错!
void 指针
void 指针我们把它称之为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给 void 指针。
提示:
不要直接对 void 指针进行解引用,因为编译器不知道它所指向的数据类型
使用 void 指针一定要小心,由于 void 指针可以包罗万象的特性,间接使得不同类型的指针转换变为合法
任何类型的指针都可以赋值给 void 指针。

#include 

int main()
{
	int num = 1024;
	int* pi = &num;
	char* ps = "FishC";
	void* pv;

	pv = pi;
	printf("pi:%p,pv:%p\n", pi, pv);

	pv = ps;
	printf("ps:%p,pv:%p\n",ps, pv);

	return 0;
}

在这里插入图片描述
void 指针可以包罗万象的特性,间接使得不同类型的指针转换变为合法:

#include 

int main()
{
	int num =1024;
	int* pi = &num;
	char* ps = "FishC";
	void* pv;

	pv = pi;
	printf("pi:%p, pv:%p\n" ,pi, pv);
	printf("*pv:%d\n", *(int*)pv); //要加上int型指针,并在前面加上解引用
	
	pv = ps;
	printf("ps:%p,pv:%p\n", ps, pv);
	printf("*pv:%s\n",(char*) pv);//要加上char型指针,因为是字符串类型,不用加解引用

	return 0;
}

学习C语言(完整版)_第54张图片
NULL 指针

#define NULL ((void *)0)

当你还不清楚要将指针初始化为什么地址时,请将它初始化 NULL;在对指针进行解引用时,先检查该指针是否为 NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。

#include 

int main()
{
	int* pi;
	int* p1=NULL;

	printf("%d\n", *pi);
	printf("%d\n", *p1);


	return 0;
}

在这里插入图片描述
注:NULL 用于指针和对象,表示指向一个不被使用的地址。而 ‘\0’ 我们非常熟悉了,它表示字符串的结尾。所以两者的概念是不同的,为了更好地进行编程,请大家严肃地进行区分。

指向指针的指针

学习C语言(完整版)_第55张图片

#include 

int main()
{
	int num = 520;
	int* p = &num;
	int** pp = &p;

	printf("num :% d\n", num); 
	printf("*p: %d\n",*p);
	printf("**p: %d\n",**pp);
	printf("&p: %p,pp: %p\n",&p, pp);
	printf("&num: %p,p: %p,*pp: %p\n", &num, p, *pp);

	return 0;
}

学习C语言(完整版)_第56张图片
指针数组和指向指针的指针

#include 

int main()
{
	char* cBooks[] = {
      "《C程序设计语言〉",
      "《C专家编程》",
      "《C和指针》",
      "《C陷阱与缺陷》",
      "《C Primer PLus>>",
      "《带你学C带你飞》"};
      
      char** byFishC;
      char** jiayuLoves[4];
      int i;

      byFishC = &cBooks[5];//相当于char **pp=&p
      jiayuLoves[0] = &cBooks[0];
      jiayuLoves[1] = &cBooks[1];
      jiayuLoves[2] = &cBooks[2];
      jiayuLoves[3] = &cBooks[3];

      printf("FishC出版的图书有: %s\n",*byFishC);//字符串类型,所以不用加解引用
      printf("小甲鱼喜欢的图书有: \n");

      for (i = 0; i < 4; i++) {
          printf("%s\n",*jiayuLoves[i]);
      }
    
	return 0;
}

学习C语言(完整版)_第57张图片
数组指针和二维数组

#include 

int main()
{
	int array[10] = { 0, 1, 2 ,3,4,5,6,7,8,9 };
	int* p = array;
	int i;

	for (i = 0; i < 10; i++){
		printf("%d\n", * (p + i));
}
	return 0;
}

错误示范:

#include 

int main()
{
	int array[3][4] = {
		{0,1,2,3},
		{4,5,6,7},
		{8,9,10,11}
};
    int** p = array;
    int i, j;
    for (i = 0; i < 3; i++)
    {
	for (j = 0; j < 4; j++)
	{
		printf("%d ", * (*(p + i) + j));
	}
	printf("\n");
    }
	return 0;
}

正确示范:

#include 

int main()
{
	int array[3][4] = {
		{0,1,2,3},
		{4,5,6,7},
		{8,9,10,11}
};
    //int** p = array;
    int i, j;
    for (i = 0; i < 3; i++)
    {
	for (j = 0; j < 4; j++)
	{
		printf("%d ", * (*(array + i) + j));//这里把p改成array,
	}
	printf("\n");
    }
	return 0;
}
//p和array的地址是一样的,p+1是加了4个字节,而array是加了4个元素。编译系统不知道你指的是二维数组,只知道它是指向指针的指针

也可以替换成这样:

#include 

int main()
{
	int array[3][4] = {
		{0,1,2,3},
		{4,5,6,7},
		{8,9,10,11}
	};
	int* p = array;
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", *p++);//这里把p改成array,
		}
		printf("\n");
	}
	return 0;
}
#include 

int main()
{
	int array[3][4] = {
		{0,1,2,3},
		{4,5,6,7},
		{8,9,10,11}
	};
	int (*p)[4] = array; //这两个跨度是一样的,  p = array;
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}

	return 0;
}

分析:
p 是一个指向数组的指针,这个数组包含了 4 个元素,所以 p的跨度是 4 * sizeof(int),然后把二维数组第一个元素的地址赋值给 p。这时,p+1 恰好是二维数组一行的跨度。对 p+1 进行解引用,得到的就是二维数组第二行第一个元素的首地址。所以我们可以通过数组指针的方式来访问二维数组。

19.常量和指针

const 关键字:
在 C 语言中,有一种能力可以将变量变成具有常量一样的特性。这就是 —— const 关键字。

在它的修饰下,变量就会失去可修改的特性,也就是变成只读的属性。

const int price = 520;
const char a = ‘a’;
const float pi = 3.14;

int main()
{
	const float pi = 3.14;

	printf("%f\n", pi);

	pi = 3.1425;//运行错误,因为pi是不可以修改的值(必须是可以修改的左值)
	return 0;
}

指向常量的指针:
万能的指针当然也可以指向被 const 修饰过的变量,这就意味着不能通过指针来修改它所引用的值。

这时候,如果尝试修改指针引用的值,那么我们将被告知程序无法通过编译;但如果只是修改指针的指向,那么编译器并不会阻止你这么做。

#include 

int main()
{

    int num = 520;
    const int cnum = 880;
    const int* pc = &cnum;

    printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
    printf("*pc: %d, pc: %p\n", *pc,pc);

    pc = &num; 
    printf("num: %d, &num: % p\n", num, &num );
    printf("*pc: %d, pc : % p\n",*pc, pc);

    num= 1024;  // 尝试修改为 *pc,报错
    printf("*pc: %d,pc: %p\n", *pc,pc);

    return 0;
}

学习C语言(完整版)_第58张图片
总结:
指针可以修改为指向不同的常量
指针可以修改为指向不同的变量
可以通过解引用来读取指针指向的数据
不可以通过解引用修改指针指向的数据
常量指针:

#include 

int main()
{

    int num = 520;
    const int cnum = 880;
    int* const p = &num;

    *p = 1024; //指针指向的值可以被修改
    printf("*p: %d\n", *p);

    p = &cnum;  //指针自身不可以被修改
    printf("*p: %d\n", *p);

    return 0;

}

可以修改为这样:

#include 

int main()
{

    int num = 520;
    const int cnum = 880;
    int* const p = &num;

    *p = 1024; //指针指向的值可以被修改
    printf("*p: %d\n", *p);

    num=521; 
    printf("*p: %d\n", *p);

    return 0;
}

20.函数

#include 

void print_C(void);//函数声明

int main(void)
{
    print_C();//函数调用

    return 0;
}

void print_C(void) //函数定义
{
    printf(" ###### \n");
    printf("##    ##\n");
    printf("##      \n");
    printf("##      \n");
    printf("##      \n");
    printf("##    ##\n");
    printf(" ###### \n");
}

函数的定义
C语言要求函数必须“先定义,再调用”,定义函数的格式如下:
学习C语言(完整版)_第59张图片
1)类型名就是函数的返回值,如果这个函数不准备返回任何数据,那么需要写上 void(void 就是无类型,表示没有返回值)。
2)函数名就是函数的名字,一般我们根据函数实现的功能来命名,比如 print_C 就是“打印C”的意思,一目了然。
3)参数列表指定了参数的类型和名字,如果这个函数没有参数,那么这个位置直接写上小括号即可(())。
4)函数体就是指定函数的具体实现过程,是函数中最重要的部分。
函数的声明
声明函数的格式非常简单,只需要去掉函数定义中的函数体再加上分号(;)即可:

void print_C(void);

函数的参数和返回值
请参考以下两个例子:

a. 编写一个函数 sum,由用户输入参数 n,计算 1+2+3+…+(n-1)+n 的结果并返回。

#include 

int sum(int n);

int sum(int n)  //这里的n是形参
{
    int result = 0;

    do
    {
        result += n;
    } while (n-- > 0);

    return result;//返回值为整型,无返回值就void
}

int main(void)
{
    int n;

    printf("请输入n的值:");
    scanf("%d", &n);

    printf("1+2+3+...+(n-1)+n的结果是:%d\n", sum(n)); //这里的n是实参

    return 0;
}

b. 编写一个函数 max,接收两个整型参数,并返回它们中的较大的值。

#include 

int max(int, int); // 声明可以只写参数的类型,不写名字

int max(int x, int y)
{
    if (x > y)
        return x; // 程序一旦执行return语句,表明函数返回,后边的代码不会继续执行。
    else
        return y;
}

int main(void)
{
    int x, y, z;

    printf("请输入两个整数:");
    scanf("%d%d", &x, &y);

    z = max(x, y);

    printf("它们中较大的值是:%d\n", z);

    return 0;
}

c.编写一个函数sum,接收两个整型参数,返回两个数之和:

#include 

int sum(int, int); // 声明可以只写参数的类型,不写名字

int sum(int x, int y)
{
    return x + y;
}

int main(void)
{
    printf("两个数之和为:%d\n", sum(3, 5));

    return 0;
}

补充:如果函数不需要参数,建议定义时在函数名后边的小括号中写上 void,明确表示该函数无参数。

参数和指针

#include 

void swap(int x, int y);

void swap(int x, int y)
{
	int temp;
	printf("In swap, 互换前: x = %d, y = %d\n", x, y);

	temp = x;
	x = y;
	y = temp;

	printf("In swap, 互换后: x = %d, y = %d\n", x, y);
}

int main()
{
	int x = 3, y = 5;
	printf("In main, 互换前: X = %d, y = %d\n", x, y);
	swap(x, y);
	printf("In main, 互换后: X = %d, y = %d\n", x, y);

	return 0;
}

学习C语言(完整版)_第60张图片
参数的值最后没有改变,因为它们是在不同的作用域,不能访问到另一个作用域。在不同作用域,参数是不冲突的
使用了指针,结果就不一样了。

#include 

void swap(int *x,int *y);

void swap(int *x, int *y)
{
	int temp;
	printf("In swap, 互换前: x = %d, y = %d\n", *x, *y);

	temp = *x;
	*x = *y;
	*y = temp;

	printf("In swap, 互换后: x = %d, y = %d\n", *x, *y);
}

	int main()
	{
		int x = 3, y = 5;
		printf("In main, 互换前: X = %d, y = %d\n", x, y);
		swap(&x, &y);
		printf("In main, 互换后: X = %d, y = %d\n", x, y);

		return 0;
	}

学习C语言(完整版)_第61张图片
可变参数
实现可变参数,需要包含一个头文件叫:

这个头文件中有三个宏和一个类型是我们需要用到的,一个类型是 va_list,三个宏,一个是 va_start,一个是 va_arg,还有一个是 va_end。这里的 va就是 variable-argument(可变参数)的缩写。

#include 
#include 

int sum(int n, ...);

int sum(int n, ...) // 三个小点是占位符,表示参数个数不确定
{
    int i, sum = 0;
    va_list vap; // 定义参数列表

    va_start(vap, n); // 初始化参数列表,如果是 int sum(int gg, ...); 则这里应该是 va_start(vap, gg);
    for (i = 0; i < n; i++)
    {
        sum += va_arg(vap, int); // 获取参数值
    }
    va_end(vap); // 首尾工作,关闭参数列表

    return sum;
}

int main()
{
    int result;

    result = sum(3, 1, 2, 3);

    printf("result = %d\n", result);

    return 0;
}

指针函数和函数指针

指针函数:我们说函数的类型,事实上指的就是函数的返回值。根据需求,一个函数可以返回字符型、整型和浮点型这些类型的数据,当然,它还可以返回指针类型的数据。定义的时候只需要跟定义指针变量一样,在类型后边加一个星号即可。

所以,用指针变量作为函数的返回值就是指针函数。

#include 

char* getWord(char c);
char* getWord(char c) //返回字符串,定义为char类型的指针
{
    switch (c)
    {
    case'A': return "Apple";
    case'B': return "Banana";
    case'C': return "Cat";
    case'D': return "Dog";
    default: return "None";
    }
}
int main()
{
    char input;
    printf("请输入一个字母; ");
    scanf_s("%c", &input);

    printf("%s\n", getWord(input));

    return 0;
}

不要返回局部变量的指针:

#include 

char* getWord(char c);
char* getWord(char c)
{     //局部变量,运行出错
    char str1[] = "Apple";
    char str2[] = "Banana";
    char str3[] = "Cat";
    char str4[] = "Dog";
    char str5[] = "None";

    switch (c)
    {
    case'A': return "str1";
    case'B': return "str2";
    case'C': return "str3";
    case'D': return "str4";
    default: return "str5";
    }
}
int main()
{
    char input;
    printf("请输入一个字母; ");
    scanf_s("%c", &input);

    printf("%s\n", getWord(input));

    return 0;
}

在这里插入图片描述
函数指针
指针函数,它是个函数;函数指针,就应该是个指针。没错,从名字我们不难发现真相:它就是一个指向函数的指针。

指针函数 -> int *p();

函数指针 -> int (*p)();

注:本质上,函数表示法就是指针表示法,因为函数的名字经过求值会变成函数的地址。所以在定义了函数指针后,给它传递一个已经被定义的函数名,即可通过该指针进行调用。

#include 

int square(int num);
int square(int num)
{
	return num * num;
}
int main()
{
	int num;
	int (*fp)(int);
	printf("请输入一个整数: ");
	scanf_s("%d", &num);
	fp = square;//可以认为函数名就是地址,可用指针指向,加上取地址运算符
	printf("%d * %d = %d\n", num, num, (*fp)(num));
	return 0;
}

也可以这样写:

#include 

int square(int num);
int square(int num)
{
	return num * num;
}
int main()
{
	int num;
	int (*fp)(int);
	printf("请输入一个整数: ");
	scanf_s("%d", &num);
	fp = square(num);//可以认为函数名就是地址,可用指针指向,加上取地址运算符
	printf("%d * %d = %d\n", num, num, *fp);
	return 0;
}

局部变量和全局变量

局部变量
在开始讲解函数之前,我们所理解的变量就是在内存中开辟一个存储数据的位置,并给它起个名字。因为那时候只有一个 main 函数,所以那时我们对它的作用范围一无所知,觉得定义了变量就随时随地可以使用它……直到我们学习函数才发现,不同函数之间定义的变量,它们是无法相互进行访问的。

#include 

int main()
{
	int i = 520;
	printf("before,i=%d\n", i);

	for (int i = 0; i < 10; i++)
	{
		printf("i=%d\n", i);
	}
	printf("after,i=%d\n", i);
	return 0;
}

学习C语言(完整版)_第62张图片
注:
学习C语言(完整版)_第63张图片
全局变量
在函数里边定义的,我们叫局部变量;在函数外边定义的,我们叫外部变量,也叫全局变量。有时候,我们可能需要在多个函数中使用共同的一个变量,那么就会用到全局变量。因为全局变量可以被本程序中其他函数所共用的。

#include 

void a();
void b();
void c();

int count = 0; //定义的全局变量

void a()
{
	count++;
}
void b()
{
	count++;
}
void c()
{
	count++;
}

int main()
{
	a();
	b();
	c();
	b();
	printf("总共多少次:%d\n", count);

	return 0;
}

注:与局部变量不同,如果不对全局变量进行初始化,那么它会自动初始化为 0。
如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是再函数中屏蔽全局变量(也就是说在这个函数中,全局变量不起作用)。举例:

#include 

void func();
int a, b =520; //全局变量,a会自动初始化为0

void func()
{
	int a = 880, b = 120; //局部变量,会屏蔽掉全局变量
	printf("In func, a = %d, b = %d\n", a, b);
}
int main() 
{
	printf("In main, a = %d, b = %d\n", a, b);
	func();
	printf("In main, a = %d,b = %d\n", a, b);
	return 0;
}

在这里插入图片描述
如果一个全局变量在使用之后才被定义:
那么你可以在对该全局变量进行访问之前,使用 extern 关键字对该变量名进行修饰。这样就相当于告诉编译器:这个变量我在后边定义了,你先别急着报错。
在这里插入图片描述
举例:

#include 

void func();

void func()
{
    //extern count;//应该加上这个,这样就不会报错了
	count++;
}

int count=0;
int main() 
{
	
	func();
	printf("%d\n", count);

	return 0;
}

在这里插入图片描述

22.作用域与链接属性

作用域
上一讲我们是从变量的作用域角度将变量划分为局部变量和全局变量,这是从空间的角度来分析的。我们发现当变量被定义在程序的不同位置时,它的作用范围是不一样的。这个作用范围就是我们所说的作用域。

那么 C 语言编译器可以确认 4 种不同类型的作用域:代码块作用域、文件作用域、原型作用域和函数作用域。
代码块作用域:

#include 

int main()
{
	int i = 100;
	{
		int i = 110;

		{
			int i = 120;
			printf("i = %d\n", i);//i=120;
		}

		{
			printf("i = %d\n", i);//i=110;
			int i = 130;
			printf("i = %d\n", i); //i=130;
		}
		printf("i = %d\n", i);//i=110;
	}
	return 0;
}

学习C语言(完整版)_第64张图片
文件作用域

#include 

void func(void);//函数声明

int main(void)
{
	extern int count;//extern 声明一下,先别急着报错
	func();
	count++;

	printf("In main, count = %d\n", count);

	return 0;
}
int count;

void func(void)
{
	count++;
	printf("In func, count = %d\n", count);
}
//运行结果是in func 里是1; in main 里是2;

原型作用域:
原型作用域只适用于那些在函数原型中声明的参数名。我们知道函数在声明的时候可以不写参数的名字(但参数类型是必须要写上的),其实多尝试你还可以发现,函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然,这样做毫无意义)。允许你这么做,只是因为原型作用域起了作用。
学习C语言(完整版)_第65张图片
定义和声明
学习C语言(完整版)_第66张图片
链接属性
简单的来说,编译器将你的源文件变成可执行程序需要经过两个步骤:编译和链接。编译过程主要是将你写的源代码生成机器码格式的目标文件,而链接过程则是将相关的库文件添加进来(比如你在源文件中调用了 stdio 库的 printf 函数,那么在这个过程中,就把 printf 函数的代码添加进来),然后整合成一个可执行程序。
在 C 语言中,链接属性一共有三种:

external(外部的)-- 多个文件中声明的同名标识符表示同一个实体
internal(内部的)-- 单个文件中声明的同名标识符表示同一个实体
none(无)-- 声明的同名标识符被当作独立不同的实体(比如函数的局部变量,因为它们被当作独立不同的实体,所以不同函数间同名的局部变量并不会发生冲突)
学习C语言(完整版)_第67张图片
默认情况下,具备文件作用域的标识符拥有 external 属性。也就是说该标识符允许跨文件访问。对于 external 属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。

#include 

void a(void);
void b(void);
void c(void);

int count;//在前面加上static ,就变成internal

int main(void)
{
	a();
	b();
	c();
	b();
	printf("一共多少次%d次 \n", count);

	return 0;
}
extern int count;

void a(void) {
	count++;
}
//新建源文件a.c
extern int count;

void b(void) {
	count++;
}
//新建源文件b.c
extern int count;

void c(void) {
	count++;
}
//新建c文件c.c

使用 static 关键字可以使得原先拥有 external 属性的标识符变为 internal 属性。这里有两点需要注意:

使用 static 关键字修改链接属性,只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)
链接属性只能修改一次,也就是说一旦将标识符的链接属性变为 internal,就无法变回 external 了

23.生存期和存储类型

生存期:
C语言的变量拥有两种生存期,分别是静态存储期(static storage duration)和自动存储期(automatic storage duration)。

具有文件作用域的变量具有静态存储期(比如全局变量),函数名也拥有静态存储期。具有静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放;具有代码块作用域的变量一般情况下具有自动存储期(比如局部变量和形式参数),具有自动存储期的变量在代码块结束时将自动释放存储空间。
存储类型:
前边我们分别介绍了 C 语言变量的作用域、链接属性和生存期,总得来说,这些都是由变量的存储类型来定义的。变量的存储类型其实是指存储变量值的内存类型,C 语言提供了 5 种不同的存储类型,分别是:auto、register、static、extern 还有 typedef。
自动变量(auto):
学习C语言(完整版)_第68张图片
寄存器变量(register):
寄存器是存在于 CPU 的内部的,CPU 对寄存器的读取和存储可以说是几乎没有任何延迟。

将一个变量声明为寄存器变量,那么该变量就有可能被存放于CPU的寄存器中。为什么我这里说有可能呢?因为CPU的寄存器空间是十分有限,所以编译器并不会让你将所有声明为register的变量都放到寄存器中。事实上,有可能所有的register关键字都被忽略,因为编译器有自己的一套优化方法,会权衡哪些才是最常用的变量。在编译器看来,它觉得它比你更了解程序。而那些被忽略的register变量,它们会变成普通的自动变量。

所以寄存器变量自动变量在很多方面的是一样的,它们都拥有代码块作用域,自动存储期和空链接属性。
学习C语言(完整版)_第69张图片

#include 

int main()
{
	register int i = 520;//这里应该不能加register

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

	return 0;
}

在这里插入图片描述
静态局部变量(static):

#include 

void func(void);
void func(void)
{
	static int count = 0;//还存储着上一次的值,内存还没有释放
	printf("count = %d\n", count);
	count++;
}
int main(void)
{
	int i;
	for (i = 0; i < 10; i++)
	{
		func();
	}
	return 0;
}

学习C语言(完整版)_第70张图片
static 和 extern:
作用于文件作用域的 static 和 extern,我们上一节已经讲过了,static 关键字使得默认具有 external 链接属性的标识符变成 internal 链接属性,而 extern 关键字是用于告诉编译器这个变量或函数在别的地方已经定义过了,先去别的地方找找,不要急着报错。

#include 

extern int count;//这里加上了extern

void func(void)
{
	printf("count = %d\n", count);
}
extern void func(); //这里加上了extern
int count = 520;
int main()
{
    func();
}

24.递归

递归从原理上来说就是函数调用自身这么一个行为。
递归程序需要正确设置结束条件,否则递归程序会一直走下去,直到崩溃。

#include 

void recursion(void);
void recursion(void)
{
    static int count = 10; //如果没有约束条件,程序将一直运行下去
    printf("Hi!\n");
    if (--count)
    {
        recursion();
    }
}
int main(void)
{
    recursion();

    return 0;
}
//打印出来10个Hi

学习C语言(完整版)_第71张图片
第一种写法:

#include 

long fact(int num);
long fact(int num)
{
    long result;
    for (result = 1; num > 1; num--)
    {
        result *= num;
    }
    return result;
}
int main(void)
{
    int num;
    printf("请输入一个正整数: ");
    scanf_s("%d", &num);
    printf("%d的阶乘是: %d\n", num,fact(num));

    return 0;
}

第二种写法:(使用递归的方法)

#include 

long fact(int num);
long fact(int num)
{
    long result;
    if (num > 0)
    {
        result = num * fact(num - 1);
    }
    else
    {
        result = 1;
    }
    return result;
}
int main(void)
{
    int num;
    printf("请输入一个正整数: ");
    scanf_s("%d", &num);
    printf("%d的阶乘是: %d\n", num,fact(num));

    return 0;
}

学习C语言(完整版)_第72张图片

25. 动态内存管理

4个库函数:
malloc – 申请动态内存空间
free – 释放动态内存空间
calloc – 申请并初始化一系列内存空间
realloc – 重新分配内存空间
malloc
学习C语言(完整版)_第73张图片

#include 
#include 
int main()
{
	int* ptr;
	ptr = (int*)malloc(sizeof(int));
	if (ptr == NULL)
	{
		printf("分配内存失败! \n");
		exit(1); //exit和malloc函数都需要用到stdlib.h这个头文件
	}
	printf("请输入一个整数:");
	scanf_s("%d", ptr);

	printf("你输入的整数是: %d\n", *ptr);
	return 0;
}
//申请动态存储空间,然后把数据放进去

附:堆和栈的区别,比如局部变量是放在栈上面的,而动态内存是放在堆上面的。
free
学习C语言(完整版)_第74张图片
free函数和malloc函数是配套的

#include 
#include 
int main()
{
	int* ptr;
	ptr = (int*)malloc(sizeof(int));//为啥写sizeof而不写4个字节,因为不同的操作系统,所占字节不一定是4个字节
	if (ptr == NULL)
	{
		printf("分配内存失败! \n");
		exit(1); //exit和malloc函数都需要用到stdlib.h这个头文件
	}
	printf("请输入一个整数:");
	scanf_s("%d", ptr);

	printf("你输入的整数是: %d\n", *ptr);
	free(ptr);

	printf("你输入的整数是: %d\n", *ptr);
	return 0;
}

在这里插入图片描述
内存泄漏
1)隐式内存泄漏(即用完内存块没有及时使用free函数释放,也是不正常申请动态内存空间)
2)丢失内存块地址

malloc 可以申请一块任意尺寸的内存空间

malloc 不仅可以申请存储基本数据类型的空间,事实上它还可以申请一块任意尺寸的内存空间。对于后者,由于申请得到的空间是连续的,所以我们经常用数组来进行索引即可:

#include 
#include 

int main(void)
{
    int* ptr = NULL;
    int num, i;

    printf("请输入待录入整数的个数:");
    scanf_s("%d", &num);

    ptr = (int*)malloc(num * sizeof(int));

    for (i = 0; i < num; i++)
    {
        printf("请录入第%d个整数:", i + 1);
        scanf_s("%d", &ptr[i]);
    }

    printf("你录入的整数是:");
    for (i = 0; i < num; i++)
    {
        printf("%d ", ptr[i]);
    }

    putchar('\n');
    free(ptr);

    return 0;
}

初始化内存空间
由于 malloc 并不会帮你初始化申请的内存空间,所以你需要自己进行初始化。
当然你可以写一个循环来做这件事儿,但我不建议你这么做,因为标准库提供了更加高效的函数:memset。https://fishc.com.cn/thread-80241-1-1.html
学习C语言(完整版)_第75张图片

#include 
#include 
#include 

#define N 10

int main(void)
{
    int* ptr = NULL;
    int i;

    ptr = (int*)malloc(N * sizeof(int));
    if (ptr == NULL)
    {
        exit(1);
    }

    memset(ptr, 0, N * sizeof(int));

    for (i = 0; i < N; i++)
    {
        printf("%d ", ptr[i]);
    }

    putchar('\n');
    free(ptr);

    return 0;
}

calloc
学习C语言(完整版)_第76张图片
下面两种写法是等价的:
学习C语言(完整版)_第77张图片
realloc
realloc 函数修改 ptr 指向的内存空间大小为 size 字节
如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!
该函数将移动内存空间的数据并返回新的指针
如果 ptr 参数为 NULL,那么调用该函数就相当于调用 malloc(size)
如果 size 参数为 0,并且 ptr 参数不为 NULL,那么调用该函数就相当于调用 free(ptr)
除非 ptr 参数为 NULL,否则 ptr 的值必须由先前调用 malloc、calloc 或 realloc 函数返回

#include 
#include 

int main(void)
{
    int i, num;
    int count = 0;
    int* ptr = NULL; // 注意,这里必须初始化为NULL

    do
    {
        printf("请输入一个整数(输入-1表示结束):");
        scanf_s("%d", &num);
        count++;

        ptr = (int*)realloc(ptr, count * sizeof(int));
        if (ptr == NULL)
        {
            printf("内存空间不足!\n");
            exit(1);
        }

        ptr[count - 1] = num;
    } while (num != -1);

    printf("输入的整数分别是:");
    for (i = 0; i < count; i++)
    {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    free(ptr);

    return 0;
}

注:reaIloc是在原来的内存土后面如果迎足够空间的话就会在原来基础后面扩增,返回的还是原来的地址
如果原来基础后面不够的话,才会另外新申请一个空间,并将数据转移过去,返回新的地址

C语言的内存布局

学习C语言(完整版)_第78张图片
根据内存地址从低到高分别划分为:

代码段(Text segment)
数据段(Initialized data segment)
BSS段(Bss segment/Uninitialized data segment)
栈(Stack)
堆(Heap)

堆:
前边我们学习了动态内存管理函数,使用它们申请的内存空间就是分配在这个堆里边。
所以,是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。
当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上;当利用 free 等函数释放内存时,被释放的内存从堆中被剔除。
栈:
大家平时可能经常听到堆栈这个词,一般指的就是这个栈。
栈是函数执行的内存区域,通常和堆共享同一片区域。
堆和栈的对比:
学习C语言(完整版)_第79张图片

26.高级宏定义

不带参数的宏定义:

#define PI 3.14

这个宏定义的作用是把程序中出现的 PI 在预处理阶段全部替换成 3.14。
学习C语言(完整版)_第80张图片

#include 
#define PI 3.14 
int main(void)
{
	int r;
	float s;
	printf("请输入圆的半径: ");
	scanf_s("%d", &r);
	s = PI * r*r;
	printf("圆的面积是: %.2f\n", s);

	return 0;
}

带参数的宏定义:
C 语言允许宏定义带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数,这点和函数有些类似。

#define MAX(x, y) (((x) > (y)) ? (x) : (y))

这个宏定义的作用是求出 x 和 y 两个参数中比较大的那一个。

#include 
#define MAX(x, y) (((x) > (y)) ? (x) : (y))//这里必须要加上括号,否则结果会出错
int main(void)
{
	int x, y;
	printf("请输入两个整数; ");
	scanf_s("%d%d", &x, & y);
	printf("%d是较大的那个数:\n",MAX(x, y));
	return 0;
}

不使用宏定义(宏定义会有bug)如何来提高函数的效率呢。使用内联函数

预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
学习C语言(完整版)_第81张图片
预处理器实例

#include 
#include "myheader.h"

这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。

#undef  FILE_SIZE
#define FILE_SIZE 42

这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。

内联函数

#include 

inline int square(int x);//在这里要加上inline
inline int square(int x)
{
	return x * x;
}

int main(void)
{
	int i = 1;
	while (i <= 100)
	{
		printf("%d的平方是%d\n", i , square(i));
		i++;
	}
	return 0;
}

内联函数虽然节省了函数调用的时间消耗,但由于每一个函数出现的地方都要进行替换,因此增加了代码编译的时间。
另外,并不是所有的函数都能够变成内联函数。现在的编译器也很聪明,就算你不写 inline,它也会自动将一些函数优化成内联函数。

“#” 和 “##”

"# 和 ## 是两个预处理运算符。
在带参数的宏定义中,# 运算符后面应该跟一个参数,预处理器会把这个参数转换为一个字符串。
"## 运算符被称为记号连接运算符,可以使用它来连接多个参数

#include 

#define STR(s) # s
int main(void)
{
	printf("%s\n", STR(FISHC));//只有输出是字符串的形式才能被解析
	return 0;
}
//运行结果是FISHC
#include 
#define TOGETHER(x, y) x ## y
int main(void)
{
	printf("%d\n", TOGETHER(2, 50));
	return 0;
}
//运行结果是250

可变参数

#define SHOWLIST(…) printf(#VA_ARGS)

其中 … 表示使用可变参数,VA_ARGS 在预处理中被实际的参数集所替换。

#include 
#define SHOWLIST(...) printf(#__VA_ARGS__ )
int main(void)
{
	SHOWLIST(FishC,5203.14\n); 
	return 0;
}

可变参数是允许空参数的(如果可变参数是空参数,## 会将 format 后面的逗号“吃掉”,从而避免参数数量不一致的错误):

#include 

#define PRINT(format, ...) printf(#format, ##__VA_ARGS__)

int main(void)
{
        PRINT(num = %d\n, 520);
        PRINT(Hello FishC!\n);

        return 0;
}

27.结构体

学习C语言(完整版)_第82张图片
定义结构体类型变量:
在这里插入图片描述

#include 

struct Book
{
	char title[128];
	char author[40];
	float price;
	unsigned int date;
	char publisher[40];
};
int main(void)
{
	struct Book book;
	return 0;
}

访问结构体变量:
要访问结构体成员,我们需要引入一个新的运算符一点号(.)运算符。比如book . title就是引用book结构体的title成员,它是一个字符数组;而book .price则是引用book结构体的price成员,它是一个浮点型的变量。

#include 

struct Book
{
	char title[128];
	char author[40];
	float price;
	unsigned int date;
	char publisher[40];
}book;
int main(void)
{

	printf("请输入书名: ");
	scanf_s("%s",book.title);
	printf("请输入作者: ");
	scanf_s("%s",book.author);
	printf("请输入售价: ");
	scanf_s("%f",&book.price);
	printf("请输入出版日期: ");
	scanf_s("%d",& book.date);
	printf("请输入出版社: ");
	scanf_s("%s",book.publisher);

	printf("\n=====数据录入完毕=====");
	printf("书名: %s\n",book.title);
	printf("作者: %s\n", book.author);
	printf("售价: %.2f\n",book.price);
	printf("出版日期: %d\n",book.date);
	printf("出版社: %s\n",book.publisher);

	return 0;
}

学习C语言(完整版)_第83张图片
初始结构体的指定成员值
其语法和数组指定初始化元素类似,不过结构体指定初始化成员使用点号(.)运算符和成员名。
学习C语言(完整版)_第84张图片
算一算:

#include 
int main(void)
{
	struct A
	{
		char a;
		int b;
		char c;
	}a = { 'x',520,'0' };
	printf("size of a = %d\n", sizeof(a));
	printf("%c %d %c\n", a.a, a.b, a.c);
	return 0;
}
//运行结果是:12
//运行结果是:x 520 0

原因是:学习C语言(完整版)_第85张图片

结构体数组和结构体指针

结构体嵌套:
学习C语言(完整版)_第86张图片

#include 

struct Date
{
	int year;
	int month;
	int day;
};

struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;
	char publisher[40];
}book = {
	"《带你学C带你飞》",
	"小甲鱼",
	48.8,
	{2017,11,11},
	"清华大学出版社",
};
int main(void)
{

	printf("书名: %s\n", book.title);
	printf("作者: %s\n", book.author);
	printf("售价: %.2f\n", book.price);
	printf("出版日期: %u-%u-%u\n", book.date.year, book.date.month, book.date.day);
	printf("出版社: %s\n", book.publisher);

	return 0;
}

结构体数组
第一种方法是在声明结构体的时候进行定义:
学习C语言(完整版)_第87张图片
第二种方法是先声明一个结构体类型(比如上面Book),再用此类型定义一个结构体数组:
学习C语言(完整版)_第88张图片
结构体指针:
学习C语言(完整版)_第89张图片
通过结构体指针访问结构体成员有两种方法:
(*结构体指针).成员名
结构体指针->成员名
(注:. 是适用于对象,->是适用于指针)
示范:

#include 

struct Date
{
	int year;
	int month;
	int day;
};

struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;
	char publisher[40];
}book = {
	"《带你学C带你飞》",
	"小甲鱼",
	48.8,
	{2017,11,11},
	"清华大学出版社",
};
int main(void)
{
	struct Book* pt;
	pt = &book;

	printf("书名: %s\n", (*pt).title);//(*结构体指针).成员名
	printf("作者: %s\n", pt->author);//结构体指针->成员名
	printf("售价: %.2f\n", book.price);
	printf("出版日期: %u-%u-%u\n", book.date.year, book.date.month, book.date.day);
	printf("出版社: %s\n", book.publisher);

	return 0;
}

传递结构体变量和结构体指针

传递结构体变量

#include 
int main(void)
{
	struct Test
	{
		int x;
		int y;
	}t1, t2;
	t1.x = 3;
	t1.y = 4;
	t2 = t1;
	printf("t2.x = %d, t2.y = %d\n", t2.x, t2.y);
	return 0;
}//运行结果是t2.x = 3, t2.y =4

28.typedef

#include 
typedef int interger;//代表将interger替换为int
//也可以替换为define interger int
int main(void)
{
	interger a;
	int b;
	a = 520;
	b = a;
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("size of a = %d\n", sizeof(a));
	return 0;
}

在这里插入图片描述
相比起宏定义的直接替换,typedef是对类型的封装。

#include 
#include 

struct Date
{
	int year;
	int month;
	int day;
};
	int main(void)
	{
		struct Date* date;
		date = (struct Date*)malloc(sizeof(struct Date));
		if (date == NULL)
		{
			printf("内存分配失败!\n");
			exit(1);
		}
		date->year = 2017;
		date->month= 5;
		date->day = 15;

		printf("%d-%d-%d\n", date->year, date->month, date->day);
		return 0;
	}
	//用typedef替换为:
#include 
#include 

typedef struct Date
{
	int year;
	int month;
	int day;
}DATE;
	int main(void)
	{
		struct Date* date;
		date = (DATE*)malloc(sizeof(DATE));
		if (date == NULL)
		{
			printf("内存分配失败1 \n");
			exit(1);
		}
		date->year = 2017;
		date->month= 5;
		date->day = 15;

		printf("%d-%d-%d\n", date->year, date->month, date->day);
		return 0;
	}
	//把struct Date 替换为 DATE

typedef可以定义多种类型: typedef struct Date DATE,*PDATE
关于typedef的一些复杂声明语句:
学习C语言(完整版)_第90张图片

#include 
typedef int(*PTR_TO_ARRAY)[3];
int main(void)
{
	int array[3] = { 1, 2, 3 };
	PTR_TO_ARRAY ptr_to_array = &array;
	int i;
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", (*ptr_to_array)[i]);
	}
	return 0;
}

学习C语言(完整版)_第91张图片

#include 
typedef int (*PTR_TO_FUN) (void);
int fun(void)
{
	return 520;
}
	int main(void)
	{
		PTR_TO_FUN ptr_to_fun = &fun;
		printf("%d\n",(*ptr_to_fun)());
		return 0;
	}

学习C语言(完整版)_第92张图片

29.共用体

学习C语言(完整版)_第93张图片

#include 
#include 
union Test
{
	int i;
	double pi;
	char str[6];
};
int main(void)
{
	union Test test;
	test.i = 520;
	test.pi = 3.14;
	strcpy(test.str,"FishC");
	printf("addr of test.i; %p\n", &test.i);
	printf("addr of test.pi: %p\n", &test.pi);
	printf("addr of test.str: %p\n", &test.str);
	return 0;
}

初始化共用体:
学习C语言(完整版)_第94张图片

30.枚举类型

声明枚举类型:
在这里插入图片描述

enum Week {sun, mon, tue, wed, thu, fri, sat};

定义枚举变量:
在这里插入图片描述

enum Week today;

枚举类型里的枚举值默认为从开始初始化,如

enum Week {sun, mon, tue, wed, thu, fri, sat};    //声明枚举类型Week,默认枚举值初始化从零开始,如枚举值名称sun~sat从0~6进行逐一赋值
#include 
#include 
int main(void)
{
    enum Week { sun, mon, tue, wed, thu, fri, sat };    //声明枚举类型Week,默认枚举值初始化从领开始,如枚举值名称sun~sat从0~6进行逐一赋值
    enum Week today;         //定义枚举类型变量today
    struct tm* p;
    time_t t;

    time(&t);
    p = localtime(&t);

    today = p->tm_wday;

    switch (today)
    {
    case mon:
    case tue:
    case wed:
    case thu:
    case fri:
        printf("干活!T_T\n");
        break;
    case sat:
    case sun:
        printf("放假!^_^\n");
        break;
    default:
        printf("Error!\n");
    }

    return 0;
}

31.命令行参数

在某些情况下,main函数可以有参数,例如:

 int main(int argc,char *argv[])

其中,argc和argv就是main函数的形参,它们是程序的“命令行参数”。
argv是*char指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串。
通常main函数和其他函数组成一个文件模块,有一个文件名。
对这个文件进行编译和连接,得到可执行文件(后缀为.exe)。用户执行这个可执行文件,操作系统就调用main函数,然后由main函数调用其他函数,从而完成程序的功能。
main函数的形参是从哪里传递给它们的呢?
显然形参的值不可能在程序中得到。
main函数是操作系统调用的,实参只能由操作系统给出。

#include 
int main(int argc,char *argv[])
{ while(argc>1)
   { ++argv; 
      printf("%s\n", *argv);
      --argc;
    }
    return 0;
}

在VC++环境下编译、连接后,“工程”—“设置”—“调试”—“程序变量”中输入“China Beijing”,再运行就可得到结果。

32.头文件

头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。

在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。

引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。

A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。
引用头文件的语法
使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

#include 

这种形式用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

#include "file"

这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。
引用头文件的操作
#include 指令会指示 C 预处理器浏览指定的文件作为输入。预处理器的输出包含了已经生成的输出,被引用文件生成的输出以及 #include 指令之后的文本输出。例如,如果您有一个头文件 header.h,如下:

char *test (void);

和一个使用了头文件的主程序 program.c,如下:

int x;
#include "header.h"

int main (void)
{
   puts (test ());
}

编译器会看到如下的代码信息:

int x;
char *test (void);

int main (void)
{
   puts (test ());
}

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

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

33.文件读写

程序员如何创建、打开、关闭文本文件或二进制文件?
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。
打开文件
可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

FILE *fopen( const char * filename, const char * mode );

在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
学习C语言(完整版)_第95张图片
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

关闭文件
为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:

 int fclose( FILE *fp );

如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。

C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
写入文件
下面是把字符写入到流中的最简单的函数:

int fputc( int c, FILE *fp );

函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:

int fputs( const char *s, FILE *fp );

函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。您也可以使用 int fprintf(FILE *fp,const char *format, …) 函数来写把一个字符串写入到文件中。尝试下面的实例:
注:/tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmp、D:\tmp等。

#include 
 
int main()
{
   FILE *fp = NULL;
 
   fp = fopen("/tmp/test.txt", "w+");
   fprintf(fp, "This is testing for fprintf...\n");
   fputs("This is testing for fputs...\n", fp);
   fclose(fp);
}

当上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行。接下来让我们来读取这个文件。
读取文件
下面是从文件读取单个字符的最简单的函数:

int fgetc( FILE * fp );

fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF。下面的函数允许您从流中读取一个字符串:

char *fgets( char *buf, int n, FILE *fp );

函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。

如果这个函数在读取最后一个字符之前就遇到一个换行符 ‘\n’ 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。您也可以使用 int fscanf(FILE *fp, const char *format, …) 函数来从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。

#include 
 
int main()
{
   FILE *fp = NULL;
   char buff[255];
 
   fp = fopen("/tmp/test.txt", "r");
   fscanf(fp, "%s", buff);
   printf("1: %s\n", buff );
 
   fgets(buff, 255, (FILE*)fp);
   printf("2: %s\n", buff );
   
   fgets(buff, 255, (FILE*)fp);
   printf("3: %s\n", buff );
   fclose(fp);
 
}

当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:

1: This
2: is testing for fprintf...

3: This is testing for fputs...

首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。其次,调用 fgets() 读取剩余的部分,直到行尾。最后,调用 fgets() 完整地读取第二行。

经典例题

【程序1】
题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?
1.程序分析:可填在百位、十位、个位的数字都是1、2、3、4。组成所有的排列后再去掉不满足条件的排列。

#include 

int main()
{
	int i, j, k,n=0;
	for (i = 1; i <= 4; i++)
	{
		for (j = 1; j <= 4; j++)
		{
			if (i != j)
			{
				for (k = 1; k <= 4; k++)
				{
					if (k != i && k != j)
					{
						n = n + 1;
						printf("%d%d%d\n", i, j, k);
					}
				}
			}
		}
	}
	printf("\n");
	printf("共有%d个数\n", n);

	return 0;
}

【程序2】
题目:企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10%;利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%;20万到40万之间时,高于20万元的部分,可提成5%;40万到60万之间时高于40万元的部分,可提成3%;60万到100万之间时,高于60万元的部分,可提成1.5%,高于100万元时,超过100万元的部分按1%提成,从键盘输入当月利润I,求应发放奖金总数?

#include 

int main()
{
	float i,j,d;
	printf("请输入当月利润:(万元)\n");
	scanf_s("%f", &i);

	if (i > 100)j = 10;
	else j = i / 10;

	switch ((int)j)
	{
	case 0:d = i*0.1; break;
	case 1:d = 1 + (i - 10) * 0.075; break;
	case 2:case 3:d = 1 + 0.75 + (i - 20) * 0.05; break;
	case 4:case 5:d = 1 + 0.75 + 1 + (i - 40) * 0.03; break;
	case 6:case 7:case 8:case 9:d = 1 + 0.75 + 1 + 0.6 + (i - 60) * 0.015; break;
	case 10:d = 1 + 0.75 + 1 + 0.6 + 0.6 + (i - 100) * 0.01; break;
	}

	printf("总奖金数为:%.4f万元\n", d);

	return 0;
}

【程序3】
题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

#include 
#include 

int main()
{
	int i,j,k,n;
	for (i = 1; i < 100000; i++)
	{
		j = sqrt(i + 100); k = sqrt(i + 268);
		if (j * j == i + 100 && k * k == i + 268)
		{
			printf("%d\n", i);
		}
	}

	return 0;
}

【程序4】
题目:输入某年某月某日,判断这一天是这一年的第几天?
1.程序分析:以3月5日为例,应该先把前两个月的加起来,然后再加上5天即本年的第几天,特殊情况,闰年且输入月份大于3时需考虑多加一天。

#include 
#include 

int main()
{
	int year, month, day, num;
	printf("请输入年月日:\n");
	scanf_s("%d%d%d", &year, &month, &day);

	switch (month)
	{
	case 1:num = 0; break;
	case 2:num = 31; break;
	case 3:num = 31 + 28; break;
	case 4:num = 31 + 28 + 31; break;
	case 5:num = 31 + 28 + 31 + 30; break;
	case 6:num = 31 + 28 + 31 + 30 + 31; break;
	case 7:num = 31 + 28 + 31 + 30 + 31 + 30; break;
	case 8:num = 31 + 28 + 31 + 30 + 31 + 30 + 31; break;
	case 9:num = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 30; break;
	case 10:num = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 30 + 31; break;
	case 11:num = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 30 + 31 + 30; break;
	case 12:num = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 30 + 31 + 30 + 31; break;
	}
	num = num + day;
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
	{
		if (month > 3)
		{
			num++;
		}
	}
	printf("总天数为:%d天\n", num);

	return 0;
}

【程序5】
题目:输入三个整数x,y,z,请把这三个数由小到大输出。
1.程序分析:我们想办法把最小的数放到x上,先将x与y进行比较,如果x>y则将x与y的值进行交换,然后再用x与z进行比较,如果x>z则将x与z的值进行交换,这样能使x最小。

#include 

int main()
{
	int a, b, c,t;
	printf("请输入a,b,c的值:\n");
	scanf_s("%d%d%d", &a, &b, &c);

	if (a > b)
	{
		t = a; a = b; b = t;
	}
	if (a > c)
	{
		t = a; a = c; c = t;
	}
	if (b > c)
	{
		t = b; b = c; c = t;
	}

	printf("顺序为:%d %d %d\n", a, b, c);
	return 0;
}

【程序6】
题目:输出9*9口诀。

#include 

int main()
{
	int i, j;
	for (i = 1; i < 10; i++)
	{
		for (j = 1; j <= i; j++)
		{
			printf("%d*%d=%d  ", i, j, i * j);
		}
		printf("\n");
	}
	return 0;
}

【程序7】
题目:判断101-200之间有多少个素数,并输出所有素数。

#include 

int main()
{
	int i, j,n=0;
	for (i = 101; i <= 200; i+=2)
	{
		for (j = 2; j <= i - 1; j++)
		{
			if (i % j == 0)break;
		}
		if (j > i - 1)
		{
			n = n + 1;
			printf("输出的值为:%d\n", j);
		}
	}
	printf("\n");

	printf("素数个数:%d\n", n);
	
	return 0;
}

【程序8】
题目:求1+2!+3!+…+20!的和

#include 

int main()
{
	long long i,s=1,sum=0;
	for (i = 1; i <= 20; i++)
	{
		s = s * i;
		sum = sum + s;
	}

	printf("输出的值为:%lld\n", sum);
	
	return 0;
}

【程序9】
题目:利用递归方法求5!。

#include 

int fac(int n);
int fac(int n)
{
	int x;
	if (n == 1) x= 1;
	else x = fac(n - 1) * n;
	return x;
}

int main()
{
	int n,x;
	printf("请输入你的值:\n");
	scanf_s("%d", &n);

	x = fac(n);
	printf("输出的值为:%d\n", x);
	
	return 0;
}

【程序10】
题目:有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比第一个人大两岁。最后问第一个人,他说是10岁。请问第五个人多大?

#include 

int age(int n);
int age(int n)
{
	int x;
	if (n == 1)x = 10;
    else x = age(n - 1) + 2;
	return x;
}

int main()
{
	printf("第5个小孩的年龄是%d\n",age(5));
	return 0;
}

【程序11】
题目:求100之内的素数

#include 

int main()
{
	int i, j, n = 0; ;
	for (i = 2; i <= 100; i++)
	{
		for (j = 2; j <= i - 1; j++)
		{
			if (i % j == 0)break;
		}
		if (j >= i) 
		{
			printf("%d是一个素数\n", i);
			n += 1;
		}
		if (n % 10 == 0)printf("\n");
	}
	return 0;
}

【程序12】
题目:对10个数进行排序

#include 

int main()
{
	int i, j,t;
	int a[10];
	printf("请输入你的10个数:\n");
	for (i = 0; i < 10; i++)
	{
		scanf_s("%d", &a[i]);
	}
	for (i = 0; i < 9; i++)
	{
		for (j = 0; j < 9 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;
			}
		}
	}
	printf("从小到大排序的结果是:\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

【程序13】
题目:求一个3*3矩阵对角线元素之和

#include 

int main()
{
	int i, j,sum=0;
	int a[3][3];

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			scanf_s("%d", &a[i][j]);
		}
	}

	printf("输出的矩阵为:\n");
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}

	for (i = 0; i < 3; i++)
	{
		sum = sum + a[i][i];
	}
	printf("输出的值为:%d\n", sum);
	return 0;
}

【程序14】
题目:有一个已经排好序的数组。现输入一个数,要求按原来的规律将它插入数组中。

#include 

int main()
{
	int i, j, t;
	int a[11] = { 1,4,6,7,9,11,13,16,19,23 };
	printf("请输入你的数:\n");
	scanf_s("%d", &a[10]);

	for (i = 0; i < 10; i++)
	{
		if (a[10] < a[i])
		{
			t = a[10];
			for (j = 10; j > i; j--)//将后续的数往后面移一位
			{
				a[j] = a[j - 1];
			}
			a[i] = t;//插入该数
		}
	}

	for (i = 0; i < 11; i++)
	{
		printf("%d ", a[i]);
	}

	return 0;
}

【程序15】
题目:将一个数组逆序输出。

#include 
#define N 7

int main()
{
	int a[N] = { 1,2,3,5,7,9,11 };
	int i;

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

也可以这样写:

#include 
#define N 7

int main()
{
	int a[N] = { 1,2,3,5,7,9,11 };
	int i,t;

	for (i = 0; i < N / 2; i++)
	{
		t = a[i]; a[i] = a[N - 1 - i]; a[N - 1 - i] = t;
	}

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

【程序16】
题目:学习static定义静态变量的用法

#include 

void f();
void f()
{
	static int m = 0; int n = 0;
	printf("m=%d,n=%d\n", m, n);
	m++, n++;
}

int main()
{
	int i;
	for (i = 1; i <= 3; i++)
	{
		f();
	}
	return 0;
}

【程序17】
题目:学习使用auto定义变量的用法

#include 

int main()
{
	auto int a = 0, i;
	for (i = 1; i <= 3; i++)
	{
		printf("a=%d ", a);
		a++;

		auto int b = 0;
		printf("b=%d ", b);
		b++;
	}
	
	return 0;
}

【程序18】
题目:学习使用static的另一用法。
文件1:

#include 

int a = 1;//定义全局变量
int main()
{
	f();
	return 0;
}

文件2:

#include 

extern int a;//声明a为已经定义的外部变量
void f();
void f()
{
	printf("%d\n", a);
}

使用static 之后:
文件1:

#include 

static int a = 1;//定义静态全局变量,只可在本文件引用
int main()
{
	f();
	return 0;
}

文件2:

#include 

extern int a;//声明a为已经定义的外部变量
void f();
void f()
{
	printf("%d\n", a);
}

【程序19】
题目:学习使用external的用法。

#include 

int main()
{
	extern int a;//用extern声明变量a已经在别处定义过了
	printf("%d\n", a);
	return 0;
}
int a = 1;//定义全局变量a

【程序20】
题目:学习使用register定义变量的方法。

#include 

int main()
{
	register int i, sum = 0;//将循环中频繁使用到的变量,定义为寄存器变量,提高效率
	for (i = 1; i <=10000; i++)
	{
		sum = sum + i;
	}
	printf("%d\n", sum);
	return 0;
}

【程序21】
题目:宏#define命令练习(1)

#include 

#define PI 3.14
#define R 4

int main()
{
	double area;
	area = PI * R * R;
	printf("面积大小为:%.2f\n", area);
	return 0;
}

【程序22】
题目:宏#define命令练习(2)

#include 

#define PI 3.14
#define area(r) PI*(r)*(r)

int main()
{
	double r = 4;
	printf("面积大小为:%.2f\n", area(r));
	return 0;
}
#include

#define exchange(a,b) {int t;t=a;a=b;b=t;}

int main()
{
    int x = 10, y = 20;
    printf("x=%d,y=%d\n", x, y);
    exchange(x,y);
    printf("x=%d,y=%d\n", x, y);
    return 0;
}

【程序23】
题目:宏#define命令练习(3)

#include

#define PRINT printf("*\n")

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

【程序24】
题目:#include 的应用练习 
test.h文件代码如下:

#define PI 3.14

主文件代码如下:

#include 
#include "PI.h"
int main()
{
    double r = 4;
    printf("%.2f\n", PI * r * r);
    return 0;
}

【程序25】
题目:学习使用按位与 & 。
程序分析:0&0=0; 0&1=0; 1&0=0; 1&1=1 。

#include

int main()
{   
    int a, b;
    a = 63;//00111111
    b = 170;//10101010
    printf("%d\n",a&b);//00101010 相同位都为1时,结果才为1,a&b=2+8+32=42
    return 0;
}

【程序26】
题目:学习使用按位或 | 。
程序分析:0|0=0; 0|1=1; 1|0=1; 1|1=1

#include

int main()
{   
    int a, b;
    a = 0;//0000000
    b = 170;//10101010
    printf("%d\n",a|b);//10101010 相同位有一个为1,结果就为1 a|b=2+8+32+128=170
    return 0;
}

【程序27】
题目:学习使用按位异或 ^ 。   
程序分析:0^0=0; 0^1=1; 1^0=1; 1^1=0

#include

int main()
{   
    int a, b;
    a = 255;//11111111
    b = 170;//10101010
    printf("%d\n",a^b);//01010101 1/相同位相同结果为0,不同结果为1 a^ b=1+4+16+64=85
    return 0;
}

【程序28】
题目:打印出杨辉三角形(要求打印出10行如下图)   
程序分析:
       1
      1  1
      1  2  1
      1  3  3  1
      1  4  6  4  1
      1  5  10 10 5  1

#include

int main()
{   
    int i, j;
    int a[10][10];

    for (i = 0, j = 0; i < 10; i++)
    {
        a[i][i] = 1; a[i][j] = 1;
    }
    for (i = 2; i < 10; i++)//前两行都是1,从第3行开始赋值
    {
        for (j = 1; j < i; j++)
        {
            a[i][j] = a[i - 1][j - 1] + a[i - 1][j];//其余数=左上角的数+正上方的数
        }
    }

    for (i = 0; i < 10; i++)
    {
        for (j = 0; j <= i; j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

【程序29】
题目:输入3个数a,b,c,按大小顺序输出。(使用指针的方法)

#include

int main()
{   
    int a, b, c,temp;
    int* p1, * p2, * p3;
    printf("请输入a,b,c的值:\n");
    scanf_s("%d%d%d", &a, &b, &c);

    p1 = &a, p2 = &b, p3 = &c;
    if (a > b)
    {
        temp = *p1; *p1 = *p2; *p2 = temp;
    }
    if (a > c)
    {
        temp = *p1; *p1 = *p3; *p3 = temp;
    }
    if (b > c)
    {
        temp = *p2; *p2 = *p3; *p3 = temp;
    }
    printf("从小到大的顺序输出:%d %d %d\n", *p1, *p2, *p3);//printf("%d %d %d\n",a,b,c)
    return 0;
}

也可以这样写:

#include

void swap(int* x, int* y);
void swap(int* x, int* y)
{
    int t;
    t = *x, * x = *y, * y = t;
}

int main()
{   
    int a, b, c;
    int* p1, * p2, * p3;
    printf("请输入a,b,c的值:\n");
    scanf_s("%d%d%d", &a, &b, &c);

    p1 = &a, p2 = &b, p3 = &c;
    if (a > b)
    {
        swap(p1, p2);
    }
    if (a > c)
    {
        swap(p1, p3);
    }
    if (b > c)
    {
        swap(p2, p3);
    }
    printf("从小到大的顺序输出:%d %d %d\n", a, b, c);
    return 0;
}

【程序30】
题目:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组。

#include

int main()
{   
    int a[20], i, n, temp1, temp2, max, min;
    printf("请输入元素个数:\n");
    scanf_s("%d", &n);

    printf("输入元素的值:\n");
    for (i = 0; i < n; i++)
    {
        scanf_s("%d", &a[i]);
    }
    
    max = a[0]; 
    for (i = 0; i < n; i++)
    {
        if (a[i] >= max)
        {
            max = a[i];
            temp1 = i;
        }
    }
    temp2 = a[temp1]; a[temp1] = a[0]; a[0] = temp2;

    min = a[0];
    for (i = 0; i < n; i++)
    {
        if (a[i] <= min)
        {
            min = a[i];
            temp1 = i;
        }
    }
    temp2 = a[temp1]; a[temp1] = a[n - 1]; a[n - 1] = temp2;

    for (i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    return 0;
}

【程序31】
题目:写一个函数,求一个字符串的长度,在main函数中输入字符串,并输出其长度。

#include
#include 

int f(char str[]);
int f(char str[])
{
    char* p = str;
    int i = 0;
    while (*p != '\0')
    {
        i++;
        p++;
    }
    return i;
    /*
    int i;
    i = strlen(str);
    return i;
    */
}

int main()
{   
    char str[20]; int x;
    printf("输入字符串:\n");
    gets(str);//scanf_s("%s", str);接受含空格的字符串会导致结果不正确

    x=f(str);
    printf("字符串的长度:%d\n", x);
    return 0;
}

【程序32】
题目:编写input()和output()函数输入,输出5个学生的数据记录。

#include

struct Student
{
    char name[10];
    int num;
    char sex;
    int age;
    float score;
};

void input(struct Student student[]);
void input(struct Student student[])
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("输入第%d个学生的数据记录(姓名,学号,性别,年龄,成绩)\n",i+1);
        scanf_s("%s %d %c %d %f", student[i].name, &student[i].num, &student[i].sex, &student[i].age, &student[i].score);
    }
}

void output(struct Student student[]);
void output(struct Student student[])
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("输出第%d个学生的数据记录\n", i + 1);
        printf("%-7s%-5d%-5c%-5d%-6.1f", student[i].name, student[i].num, student[i].sex, student[i].age, student[i].score);
    }
}
int main()
{
    struct Student student[5];
    input(student);
    output(student);
    
    return 0;
}

【程序33】
题目:输入一个整数,并将其反转后输出。

#include

int main()
{
    int n, m;
    printf("请输入一个整数:\n");
    scanf_s("%d", &n);
    m = n % 10;
    while (n / 10 != 0)
    {
        n = n / 10;
        m = m * 10 + n % 10;
    }
    printf("反转后的整数为:%d\n", m);
    return 0;
}

【程序34】
题目:编写一个函数,输入n为偶数时,调用函数求1/2+1/4+…+1/n,当输入n为奇数时,调用函数1/1+1/3+…+1/n(利用指针函数)

#include

double odd(int n);
double odd(int n)
{
    int i; double sum = 0;
    for (i = 1; i <= n; i+=2)
    {
        sum = sum + 1.0 / i;
    }
    return sum;
}

double even(int n);
double even(int n)
{
    int i; double sum = 0;
    for (i = 2; i <= n; i+=2)
    {
        sum = sum + 1.0 / i;
    }
    return sum;
}

int main()
{
    int n;
    double (*p)(int n);//定义指向函数的指针

    printf("请输入整数n:\n");
    scanf_s("%d", &n);

    if (n % 2 == 0)
    {
        p = even;
    }
    else p = odd;

    printf("%lf\n", (*p)(n));
    return 0;
}

【程序35】
题目:指向指针的指针

#include

int main()
{
    char* s[] = { "man","woman","girl","boy","sister" };
    char** q;//定义指针的指针
    int k;
    for (k = 0; k < 5; k++)
    {
        q=&s[k];
        printf("%s\n", *q);
    }
    return 0;
}

【程序36】
题目:找到年龄最大的人,并输出。

#define N 4
#include "stdio.h"

static struct man
{
	char name[20];
	int age;
} person[N] = { "li",18,"wang",19,"zhang",20,"sun",22 };
int main()
{
	struct man* q, * p;
	int i, m = 0;
	p = person;
	for (i = 0; i < N; i++)
	{
		if (m < p->age)
		{
			m = p->age;
			q = p;
		}
		p++;
	}
	printf("%s,%d", (*q).name, (*q).age);

	return 0;
}

【程序37】
题目:字符串排序。

#include "stdio.h"
#include "string.h"

void swap(char* p1, char* p2);
void swap(char* p1, char* p2)
{
	char str[20];
	strcpy(str, p1);
	strcpy(p1, p2);
	strcpy(p2, str);
}

int main()
{
	char str1[20], str2[20], str3[20];
	printf("输入字符串;\n");
	gets(str1);
	gets(str2);
	gets(str3);

	if (strcmp(str1, str2) > 0)
	{
		swap(str1, str2);
	}
	if (strcmp(str1, str3) > 0)
	{
		swap(str1, str3);
	}
	if (strcmp(str2, str3) > 0)
	{
		swap(str2, str3);
	}
	printf("从小到大的排序为:\n");
	printf("%s %s %s\n", str1, str2, str3);

	return 0;
}

【程序38】

#include "stdio.h"

#define N 5
int main()
{
	int a[N] = { 1,2,3,4,5 };
	int i, j, t;
	i = 0; j = N - 1;
	for (; i < j; i++, j--)
	{
		t = *(a + i);
		*(a + i) = *(a + j);
		*(a + j) = t;
	}

	for (i = 0; i < N; i++)
	{
		printf("%d ", a[i]);
	}

	return 0;
}

你可能感兴趣的:(C语言,笔记,编程语言)