C 语言教程

参考于视频:C语言教程(vs2019版);用于C入门、期末备考。

练习网站:编程入门训练

C语言教程推荐: C语言入门教程

第一章 入门

  • C 语言是一种通用的、面向过程式的计算机程序设计语言。
  • 1972 年,为了移植与开发 UNIX 操作系统,丹尼斯·里奇在贝尔电话实验室设计开发了 C 语言。
  • <mark>当前最新的 C 语言标准为 C18</mark> ,在它之前的 C 语言标准有 C17、C11...C99 等。

  • 编译器安装,初学建议使用Dev-C++,安装步骤
  • <mark>注意:如果是C程序,文件后缀保存为.c;如果是C++程序,文件后缀保存为.cpp</mark>
  • 笔者使用的VS2017编译器。安装步骤!!!

  • 安装好之后,编译/执行 C 程序:
#include <stdio.h>

int main()
{
	/* 我的第一个C程序 */
	printf("Hello, World! \n");
	return 0;
}

/* #include 包含 <stdio.h> .h head 头文件 stdio */
// standard i input o out输出

// 这个文件可能会包含一个标准输入输出的头文件

#include <stdio.h>

int main()
{
	/* 我的第一个C程序 注释:不被编译器识别 */
	// 注释
	printf("Hello, World! \n");

	// print 打印 f format 格式化
	// printf 格式化输出
	return 0;
}

C 程序主要包括以下部分:

  • 预处理器指令

    • include <stdio.h>

  • 函数

    • int main(){}
  • 变量

  • 语句 & 表达式

    • printf("Hello world! \n");
  • 注释

    • // 单行注释
      
      /* 单行注释 */
      /* 
       多行注释
       多行注释
       多行注释
       */
      

VS注释与取消注释快捷键

这个是VS最常用的,一定要记住。

  • 注释: 先 <kbd> Ctrl</kbd>+ <kbd> K</kbd>,然后 <kbd> Ctrl</kbd>+ <kbd> C</kbd>
  • 取消注释: 先 <kbd> Ctrl</kbd>+ <kbd> K</kbd>,然后 <kbd> Ctrl </kbd> + <kbd> U</kbd>

在VS中使用scanf(),会报错。如下:

  • 只需进行如下修改即可:
scanf_s("%d", &number);   // 只是vs中要修改,
// 其他的编译器中输入函数仍用:
// scanf("%d",&number);
// 进行数据输入!!!

另一种解决方法:修改VS配置,就可以使用scanf()函数,表示输入了。


第二章 变量与常量

  • 此处先做了解!!!

C语言标识符:

  • C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。
  • 一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
  • C 标识符内不允许出现标点字符,比如 @、$ 和 %。
  • C 是区分大小写的编程语言。

标识符分类:

  • 关键字(32个);
  • 预定义标识符;
  • 用户自定义的标识符。

2.1 变量

  • 变量:在程序运行中不断变化的量。
  • 它需要定义后才能使用,具体格式如下:

变量的定义格式:

变量类型 变量名;
// 变量在定义时,可以赋初值:
变量类型 变量名 = 初值;
#include <stdio.h>
int main()
{
	int number = 50;
	printf("number = %d \n", number);
	return 0;
}

  • 变量名的命名:
    • 1.见名知义,采用英文单词组合,不要出现拼音;
    • 2.命名的长度应当符合“min-length && max-information”原则;
    • 3.尽量避免名字中出现数字编号,如 Value1,Value2 等;
    • 4.C语言严格区分大小写,注意类似x和X,1(数字 1)和 l(小写字母 l)之间,0(数字 0)和 o(小写字母 o)之间的区别;
    • 5.宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
#include <stdio.h>
int main()
{
	// 不规范变量名
	int a = 1,b = 2;
	// 规范的命名
	// 1. 见名知义
	int dollor = 100;
	// 2.下划线命名法
	int sun_flower = 100;
	// 3.驼峰命名法
	int sunFlowerValues = 100;
	// 4.匈牙利命名法

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

一般来说,基本数据类型分为整型、浮点型、字符型,C++中包括布尔型;每种类型又可分为若干种类型,常用基本类型可分为如下:

类型 存储大小 取值范围 大致范围
整型 int 4字节 -2,147,483,648 ~ 2,147,483,647 -2 x 109 ~2 x 109
long long 8字节 -263 ~ +(263 -1) -9 x 1018 ~ 9 x 1018
浮点型 float 4字节 -2128 ~ +2128 实际精度6 ~ 7 位
double 8字节 -21024 ~ +21024 实际精度15 ~ 16 位
字符型 char 1字节 -128 ~ 127 -128 ~ 127
布尔型 bool 1字节 0(false) or 1(true) 0(false) or 1(true)
  • 自行测试如下:
#include <stdio.h>
int main()
{
	int number = 50;
	double moon = 99.9;
	printf("number = %d \n", number);
	printf("moon = %lf \n", moon);
	return 0;
}

强制类型转换:

  • 强制类型转换格式:
(新类型名) 变量名
#include <stdio.h>

int main()
{
	double r = 12.56;
	int a = 3, b = 5;
	printf("%d \n", (int)r);
	printf("%d \n", a / b);
	printf("%.lf", (double)a / (double)b);
	return 0;
}

2.2 常量

C 常量:

  • 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量
  • 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
  • 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
  • 在 C 中,有两种简单的定义常量的方式:

    • 使用 #define 预处理器。
    • 使用 const 关键字。

#include <stdio.h>
// 宏定义
#define PI 3.14
#define SUN_FLOWER 100;

int main()
{
	// 定义常量时,全大写。
	const int IPHONE = 999;
	return 0;
}
  • #define 是宏定义,它不能定义常量,但宏定义可以实现在字面意义上和其它定义常量相同的功能,本质的区别就在于 #define 不为宏名分配内存,而 const 也不为常量分配内存,怎么回事呢,其实 const 并不是去定义一个常量,而是去改变一个变量的存储类,把该变量所占的内存变为只读!

基本数据类型书写:

  • 整型:

    • 默认为10进制 ,10 ,20。
    • 以0开头为8进制,045,021。
    • 以0b开头为2进制,0b11101101。
    • 以0x开头为16进制,0x21458adf。
  • 实型

    • 单精度常量:2.3f 。
    • 双精度常量:2.3,默认为双精度。
  • 字符型常量

    • 用英文单引号括起来,只保存一个字符'a'、'b' 、'*' 。
    #include<stdio.h>
    
    int main()
    {
    	char character='A';
    	
    	printf("character = %c \n", character);
    	return 0;
    }
    
    • 转义字符: 一些前面有反斜杠的字符。

      转义序列 含义
      \ \ 字符
      ' ' 字符
      " " 字符
      ? ? 字符
      \a 警报铃声
      \b 退格键
      \f 换页符
      \n 换行符
      \r 回车
      \t 水平制表符
      \v 垂直制表符
      \ooo 一到三位的八进制数
  • 字符串常量

    • 用英文的双引号引起来 可以保存多个字符:"abc"。

第三章 运算符

3.1 算术运算符

运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
-- 自减运算符,整数值减少 1 A-- 将得到 9
#include <stdio.h>

int main()
{
	int number_1 = 1;
	int number_2 = 2;

	int number_3 = number_1 + number_2;
	
	printf("sum = %d \n", number_3);

	return 0;
}

关于<mark>前++和后++的区别</mark>:

  • i++是先用i,然后在让i+1,然后++i是先对i+1,然后在去使用i。
  • 前加: ++a 先运算,再赋值;
  • 后加: a++ 先赋值,再运算;
  • 给一个例子:
#include <stdio.h>

int main()
{
	int a = 0;
	int b = 0;
	int c = ++a;
	int d = b++;
	
	printf("前加加 = %d , 后加加 = %d\n", c,d);

	return 0;
}

3.2 赋值运算符

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
>>= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

3.3 位运算符

运算符 描述 实例
& 按位与操作,按二进制位进行"与"运算。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1; (A & B) 将得到 12,即为 0000 1100
| 按位或运算符,按二进制位进行"或"运算。运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1; (A | B) 将得到 61,即为 0011 1101
^ 异或运算符,按二进制位进行"异或"运算。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0; (A ^ B) 将得到 49,即为 0011 0001
~ 取反运算符,按二进制位进行"取反"运算。运算规则:~1=-2; ~0=1; (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<< 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 A << 2 将得到 240,即为 1111 0000
>> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 A >> 2 将得到 15,即为 0000 1111

3.4 C 中的运算符优先级

类别 运算符 结合性
后缀 () [] -> . ++ - - 从左到右
一元 + - ! ~ ++ - - (type)* & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 AND & 从左到右
位异或 XOR ^ 从左到右
位或 OR | 从左到右
逻辑与 AND && 从左到右
逻辑或 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 , 从左到右
#include <stdio.h>

int main()
{
	int a = 20;
	int b = 10;
	int c = 15;
	int d = 5;
	int e;

	e = (a + b) * c / d;      // ( 30 * 15 ) / 5
	printf("e = %d\n", e);

	e = ((a + b) * c) / d;    // (30 * 15 ) / 5
	printf("e = %d\n", e);

	e = (a + b) * (c / d);   // (30) * (15/5)
	printf("e = %d\n", e);

	e = a + (b * c) / d;     //  20 + (150/5)
	printf("e = %d\n", e);

	return 0;
}

本章练习:运算符

第四章 判断结构

4.1 if

#include <stdio.h>

int main()
{
	int age;
	printf("请输入你的年龄:");
	scanf("%d", &age);
	if (age >= 18) {
		// 符合上括号的条件,执行此处代码。
		printf("成年了!");
	}
	
	return 0;
}

关系运算符:

  • 假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真。 (A == B) 为假。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。 (A != B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (A > B) 为假。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 (A >= B) 为假。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 (A <= B) 为真。
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	if (a==b) {
		// 符合上括号的条件,执行此处代码。
		printf("a与b相等!");
	}
	
	return 0;
}

逻辑运算符:

  • 假设变量 A 的值为 1,变量 B 的值为 0
运算符 描述 实例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A && B) 为真。
#include <stdio.h>

int main()
{
	int a = 1;
	int b = 0;
	if (a&&b) {
		// 符合上括号的条件,执行此处代码。
		printf("条件为真!");
	}
	
	return 0;
}

4.2 if…else…

C 语言提供了以下类型的判断语句:

语句 描述
if 语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if...else 语句 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套 if 语句 您可以在一个 ifelse if 语句内使用另一个 ifelse if 语句。
switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套 switch 语句 您可以在一个 switch 语句内使用另一个 switch 语句。
#include <stdio.h>

int main()
{
	int age;
	int math_score;

	printf("请输入你的年龄: \n");
	scanf("%d", &age);

	printf("请输入你的数学成绩: \n");
	scanf("%d", &math_score);

	if (age >= 18 && math_score >= 90) {
		printf("成年了,可以玩游戏!");
	}
	else {
		printf("未成年人,禁止玩游戏!!!");
	}
	
	return 0;
}

4.3 if…else if…

#include <stdio.h>

int main()
{
	int age = 90;

	if (age >= 18) {
		printf("满足成年了! \n");
	}
	else if (age >= 30) {
		printf("满足结婚年龄!\n");
	}
	else if (age >= 70) {
		printf("满足养老年龄!\n");
	}
	else if (age >= 100) {
		printf("长寿者!\n");
	}
	else {
		printf("再见!\n");
	}
	return 0;
}

4.4 ? : 运算符(三元运算符)

  • 格式:
Exp1 ? Exp2 : Exp3;
// Exp1、Exp2 和 Exp3 是表达式。请注意,冒号的使用和位置。
// 如果Exp1符合,则执行Exp2;反之,执行Exp3
#include <stdio.h>

int main()
{
	int num;
	printf("输入一个数字: ");
	scanf("%d", &num);
	(num % 2 == 0) ? printf("偶数 \n") : printf("奇数 \n");

	return 0;
}

4.5 switch

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。

#include <stdio.h>

int main()
{
	char grade;
	printf("请输入成绩字母:");
	scanf("%c", &grade);
	switch (grade)
	{
	case 'A':
		printf("很棒!\n");
		break;
	case 'B':
	case 'C':
		printf("做得好!\n");
		break;
	case 'D':
		printf("您通过了!\n");
		break;
	case 'E':
		printf("最好再试一下\n");
		break;
	default:
		printf("无效的成绩\n");
	}
	return 0;
}

附:ASCII码对照表

  • 空格的ASCII码值为0;
  • ‘0’的ASCII码值为48;
  • ‘A’的ASCII码值为65;
  • ‘a’的ASCII码值为97;
  • 大小写字母的ASCII码值相差32。
ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符
0 NUT 32 (space) 64 @ 96
1 SOH 33 ! 65 A 97 a
2 STX 34 " 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 BEL 39 , 71 G 103 g
8 BS 40 ( 72 H 104 h
9 HT 41 ) 73 I 105 i
10 LF 42 * 74 J 106 j
11 VT 43 + 75 K 107 k
12 FF 44 , 76 L 108 l
13 CR 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DCI 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 S 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 TB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92 / 124 |
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 `
31 US 63 ? 95 _ 127 DEL

本章练习:判断

第五章 循环结构

5.1 while循环

  • 只要给定的条件为真,C 语言中的 while 循环语句会重复执行一个目标语句。
  • while的格式如下:
while(条件A){
    ...
}
  • 只要条件A成立,就反复执行省略号的内容。
  • 如果不加大括号,则while循环只作用于while后的第一个完整语块(while大括号后到第一个分号之间的语句)。
#include<stdio.h>
#define ONE_KILOMETER 1000
int main()
{
	int run_meter = 0;
	while (run_meter <= ONE_KILOMETER) {
		printf("runing...%d \n", run_meter);
		run_meter += 1;
	}
	printf("我他妈终于跑完了!!!\n");
	return 0;
}
  • 死循环
#include<stdio.h>
#define ONE_KILOMETER 1000
int main()
{
	int run_meter = 0;
	while (run_meter <= ONE_KILOMETER) {
		printf("runing...%d \n", run_meter);
	}
	printf("我他妈终于跑完了!!!\n");
	return 0;
}

5.2 break 和 continue 语句

C 语言中 break 语句有以下两种用法:

  1. break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
  2. 它可用于终止 switch 语句中的一个 case。
  • break 语句的语法:
break;
#include<stdio.h>
#define GOLD 1000
int main()
{
	int rush = 1;
	while (rush <= GOLD) {
		if (rush == 500) {
			printf("挖到铁矿!!!扔掉不要!\n");
			rush++;
			break;
		}

		printf("rush = %d \n", rush);
		rush++;
	}
	printf("挖完了!!\n");
	return 0;
}
  • 注:<mark>一个break只能跳出一层循环</mark>。

C 语言中的 continue 语句有点像 break 语句。但它不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。

  • continue 语句的语法:
continue;
#include<stdio.h>

int main()
{
	int rush = 1;
	while (rush <= GOLD) {
		if (rush == 500) {
			printf("挖到铁矿!!!扔掉不要!\n");
			rush++;
			/*
			满足一定要求的时候,如果使用continue语句,
			就代表:我不要了,满足条件中的内容。
			---> 但是我还是要继续干,还是要继续执行循环,
			但是要注意,一定要在continue之前rush++
			(注意不要遗漏自增变量)
			*/
			continue;
		}

		printf("rush = %d \n", rush);
		rush++;
	}
	printf("挖完了!!\n");
	return 0;
}

5.3 do…while 语句

与while类似。

  • do...while 循环的语法:
do
{
   ...
       
}while( 条件A );
  • do...while语句会先执行省略号的内容一次,然后才判断条件A是否成立。如果成立,继续执行省略号的内容,直到某一次条件A不在成立,则退出循环。
#include<stdio.h>

int main()
{
	int rush = 1;
	do {
		/*
		先执行一次循环,后判断,判断结果为true,继续执行do中的代码块。
		*/
		rush++;
		printf("rush = %d \n", rush);

	} while (rush <= 1000); // 注意结尾,有分号!

	return 0;
}

5.4 for 语句

  • for 循环的一般形式为:
for(表达式1; 表达式2; 表达式3){
  语句块
}
  • 它的运行过程为:
    • 先执行“表达式1”。
    • 再执行“表达式2”,如果它的值为真(非0),则执行循环体,否则结束循环。
    • 执行完循环体后再执行“表达式3”。
    • 重复执行步骤 2) 和 3),直到“表达式2”的值为假,就结束循环。
  • 上面的步骤中,2) 和 3) 是一次循环,会重复执行,for 语句的主要作用就是不断执行步骤 2) 和 3)。
  • “表达式1”仅在第一次循环时执行,以后都不会再执行,可以认为这是一个初始化语句。
  • “表达式2”一般是一个关系表达式,决定了是否还要继续下次循环,称为“循环条件”。
  • “表达式3”很多情况下是一个带有自增或自减操作的表达式,以使循环条件逐渐变得“不成立”。
#include<stdio.h>
#define GOLD 1000
int main()
{
	int rush;  // 声明一个变量rush

	// 1.初始化的变量
	// 2.判断条件(满足或不满足)
	// 3.自增衡量变量
	
	/* 需注意,不是必选,但是如果没有,可能会出问题,除非是业务要求。
	比如说死循环...可能就没有上述三点,但是一个常用的循环,都应该
	具备上述三个要素,除非是特殊情况。 */

	for (rush = 1; rush < GOLD; rush++)
	{
		printf("rush = %d \n", rush);
	}
	printf("挖完了!\n");

	return 0;
}

for循环嵌套:

#include <stdio.h>

int main()
{
	int row, column;
	for (row = 1; row <= 9; row++) {
		for (column = 1; column <= 9; column++) {
			printf("%d*%d=%2d\t", row, column, row*column);
		}
		printf("\n");
	}
	return 0;
}

本章练习:循环

第六章 函数

函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。

  • C 语言中的函数定义的一般形式如下:
返回类型 函数名称(参数类型 参数)
{
	说明语句 /*函数体*/ 
	执行语句 
}
  • 案例
#include <stdio.h>

// 声明函数
void sum(int number_1, int number_2);

// 函数名: sum
// 函数的返回值类型: void
// 函数的参数: 有两个参数,参数的类型均为int

// main 主函数,整个程序的入口
int main()
{
	sum(95, 63); // 调用函数
	sum(5, 6); // 调用函数
	return 0;
}

// 定义函数
// 大括号内为函数体
void sum(int number_1, int number_2) {
	printf("sum = %d \n", number_1 + number_2);
}
  • 案例2
#include <stdio.h>

// main 主函数,整个程序的入口

// 声明函数
// 形参,形式参数,可理解为无实际意义的参数
int sum(int number_1, int number_2); //此处的number_1,number_2即为形参

// 函数名: sum
// 函数的返回值类型: int
// 函数的参数: 有两个参数,参数的类型均为int

int main()
{
    // 实参,可理解为具体的值
	int result = sum(95, 63); // 调用函数,此处的95,63即为实参
	printf("%d \n", result);
	return 0;
}

// 定义函数
// 大括号内为函数体
int sum(int number_1, int number_2) {
	int score_sum = number_1 + number_2;

	return score_sum;
}

形参与实参的区别:

参数 位置 使用范围 传递方向
形参 函数定义时括号中的参数 函数体内 形参不可以传递给实参
实参 调用时函数是括号中的参数 主调函数 实参可以传递给形参

以下为专业说法(<mark>了解即可</mark>):

  • 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
  • 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
  • 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
  • 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

作用域:

  • 任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。

C 语言中有三个地方可以声明变量:

  1. 在函数或块内部的局部变量
  2. 在所有函数外部的全局变量
  3. 形式参数的函数参数定义中

  • 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。
  • 全局变量是定义在函数外部,通常是在程序的顶部。
    • 全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。
    • 全局变量可以被任何函数访问。
    • 也就是说,全局变量在声明后整个程序中都是可用的。
  • 具体案例如下:
#include <stdio.h>
// 函数 作用域
int sum(int number_1, int number_2);

int sun_flower = 100; // 声明全局变量,在程序的任意函数内均可调用
// 不在任何一个函数中的变量,全局变量

int main()
{
	// 局部变量声明 
	int sun, moon;

	sum(95, 63); // 此处的值,只是为了补充格式,进行函数调用

	printf("main_sun_flower = %d \n", sun_flower);
	return 0;
}

int sum(int number_1, int number_2) {
	int score_sum = number_1 + number_2;
	printf("main_sun_flower = %d \n", sun_flower);
	return score_sum;
}
  • <mark>在程序中,局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用</mark>。

本章练习:函数

第七章 数组

数组是把一系列相同数据类型的变量组合在一起的数据集合。

7.1 一维数组

  • 一维数组的定义格式:
数据类型 数组名[数组大小];
  • 案例:
#include <stdio.h>
#define ARRAY_SIZE 5
int main()
{
	// 第一个下标(号)
	// 规定下标从0开始,0代表第一个元素,那第二个:2-1  第三元素:3-1  第四个元素的下标:4-1  N:n-1
	
	// 数据类型 数组名[数组大小];
	unsigned plants[ARRAY_SIZE];
	int age = 100;

	// 给下标为2,也就是第三个元素(下标+1=第几个元素),赋给他值。
	// 数组名[下标] = 值;
	plants[2] = 99;

	// 数组元素的下标从0开始,规律:第N个元素的下标为N-1,下标 = N-1 
//	printf("plant---%u \n", plants[0]);

	// 遍历数组
	for (int i = 0; i < 5; i++)
	{
		printf("plant---%u \n", plants[i]);
	}
	return 0;
}

7.2 二维数组

#include <stdio.h>

int main()
{
	int score[4][6] = { {1,2,3},{4,5,6},{7,8,9},{52,6,75}};
	printf("score = %d \n", score[0][0]);
	printf("score = %d \n", score[1][2]);

	return 0;
}

  • 数组的表驱动法:一种编程模式,从表里面查找信息而不使用逻辑语句(if、case)。事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。
  • 闰年判断案例:
#include <stdio.h>
#include <stdbool.h>

int days_of_month(int month, int year);
bool is_leap(int year);

int main()
{
	// 表驱动法
	printf("总共 %d 天! \n", days_of_month(2,2016));

	return 0;
}

int days_of_month(int month,int year) 
{
	int is_leap_res = is_leap(year);

	int day_array[12] = {31,is_leap(year) ? 29 : 28 ,31,30,31,30,31,31,30,31,30,31};

	return day_array[month - 1];
}

// 自定义bool,C语言没有bool类型
// 计算该年份是否为闰年
/**
* @params year 传入一个年份,整型数据
* @return bool 如果为真,那么返回true,则为闰年,否则false平年
*/
bool is_leap(int year) {
	if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
	{
		return true;
	}
	else {
		return false;
	}
}

7.3 enum(枚举)

  • 枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。
  • 枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
#include <stdio.h>
#define PI 3.14
// 宏定义

int main()
{
	// 定义枚举
	enum DAY
	{
		MON = 1, TUE = 2, WED = 3, TUH = 4, FRI = 5, SAT = 6, SUN = 7
	};

	// 使用枚举
	enum DAY d = FRI;
	printf("%d", d);

	return 0;
}

本章练习:数组

第八章 指针

8.1 什么是指针?

<mark>指针就是存储了另一个数据的内存地址的一种数据类型</mark>。即指针中的数据就是另一个数据的内存地址

  • 指针就是内存地址,指针变量是用来存放内存地址的变量。
  • 就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;

/* type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。
用来声明指针的星号 * 与乘法中使用的星号是相同的。
但是,在这个语句中,星号是用来指定一个变量是指针。*/

int*    ip;    /* 一个整型的指针 */
double* dp;    /* 一个 double 型的指针 */
float*  fp;    /* 一个浮点型的指针 */
char*   ch;    /* 一个字符型的指针 */
  • 案例:
#include <stdio.h>

int main()
{
	int sun_flower = 100;
	int* p = &sun_flower;
	printf("sun_flower = %p \n", &sun_flower);
	// p输出地址,p指针
	printf("p = %p \n", p);

	printf("拿到sun_flower的真实数据 : %d \n", sun_flower);
	printf("通过指针拿到sun_flower的值 : %d \n", *p);
	return 0;
}
  • 举个例子,做个比较:
int a; // 定义一个变量a,用于保存一个int类型。

int* b; // 定义一个指针变量b,用于保存一个地址,这个地址所保存的数据应该是int类型。
  • 是变量就应该可以赋值,指针变量也一样。但一般不会给指针直接赋值一个数值,而是将其他变量的地址赋值给指针,或者其他指针的值赋值给这个指针。
  • 继续上面的例子:
b = &a; // 把变量a的地址赋值给b。“&”操作是取变量的地址。
  • 继续举例:
int* c; // 我们又定义一个指针c

c = b; // 将b的值赋值给c,上面已经知道b是指针,它的值是a的地址,那么现在c的值和b一样,也是个a的地址。
  • 完整代码:
#include <stdio.h>

int main()
{
	int a=10; // 定义一个变量a,用于保存一个int类型。
	int *b; // 定义一个指针变量b,用于保存一个地址,这个地址所保存的数据应该是int类型。

	b = &a;  // 把变量a的地址赋值给b。“&”操作是取变量的地址。

	int *c; // 我们又定义一个指针c
	c = b; // 将b的值赋值给c,上面已经知道b是指针,它的值是a的地址,那么现在c的值和b一样,也是个a的地址。

	printf("b=%d,c=%d \n", b,c); // b和c相同吗?

	return 0;
}
  • 指针变量保存的值,也就是地址是可以变的。

  • 举个数组初始化的例子:
#include <stdio.h>

int main()
{
	int d[100];
	int *e;

	e = &d[0]; // e保存了数组d的第一个数据的地址

	for (int i = 0; i < 100; i++) {
		*e = 0; // 把该地址中的数据赋值0
		e++; // 地址累加一次,也就是数组中下一个数据的地址
		printf("e=%d \n", e);
	}

	return 0;
}
  • 指针和其他变量一样,可以运算(一般是加减),也可以重新赋值。

  • 说了这么多,指针有啥用?
  • 比方说,我们有个函数,如下:
int add(int x){
    return (x+1); // 把输入的值加1并返回结果。
}
  • 好了,应用的时候是这样的:
{
    int a=1;
    a=add(a); // add函数返回的是a+1
	// 现在 a等于2
}
  • 完整代码:
#include <stdio.h>
int add(int x);

int main()
{
	int a = 1;
	a = add(a); // add函数返回的是a+1
	// 现在a等于2
	printf("a=%d \n", a);
	return 0;
}

int add(int x) {
	return (x + 1); // 把输入的值加1并返回结果。
}
  • 很简单吧,就是把a都累加一次。

  • 用指针怎么写:
void add(int *y){ // 给入的是一个int指针,是一个地址。
    *y = *y + 1; // *是指"引用",这个地址所保存的变量
	// 这条语句的意思就是,把这个地址里的值加1,然后放回这个地址。
}
  • 把这个函数用起来:
{
    int a=1;
	add(&a); // 把a的地址传到函数里
	// add函数,就是把a的值加1,再放回到变量a里。
	// 现在a等于2
}
  • 完整代码:
#include <stdio.h>
int add(int x);

int main()
{
	int a = 1;
	a = add(a); // add函数返回的是a+1
	// 现在a等于2
	printf("a=%d \n", a);
	return 0;
}

int add(int x) {
	return (x + 1); // 把输入的值加1并返回结果。
}
  • 自行测试如下:
#include <stdio.h>
// %p指的是内存地址类型(打印出的是16进制数据)
int main()
{
	int a = 1;
	int* p = &a;
	printf("%p \n", &a);  //①
	printf("%p \n", p);   //②
	printf("%p \n", &p);   //③
	return 0;
}

8.2 C 中的 NULL 指针

  • 赋为 NULL 值的指针被称为指针。
#include <stdio.h>

int main()
{
	int* ptr = NULL; //空指针
	printf("*ptr = %p \n", ptr);

	return 0;
}
  • 野指针
#include <stdio.h>

int main()
{
	int* ptr = NULL; //空指针
	printf("*ptr = %p \n", ptr);

	int* p; // 野指针
	// 此处程序会随机分配地址,防止空指针产生异常!
	// 无意义指向
	printf("p = %d \n", *p);
	return 0;
}

8.3 指针数组与字符串

  • 字符串实际上是使用 null 字符 \0 终止的一维字符数组。
  • 一个以 null 结尾的字符串,包含了组成字符串的字符。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'}; // 声明和初始化创建了一个 RUNOOB 字符串
  • 依据数组初始化规则,您可以把上面的语句写成以下语句:
char site[] = "RUNOOB";
  • 案例:
#include <stdio.h>

int main()
{
	// 指针数组
	int* p_arry[3] = { NULL,NULL,NULL };

	// 可存储多个地址

//	char greeting[6] = { 'H','e','l','l','o','\0' };
	char greeting[] = "Hello";
	for (int i = 0; i < 6; i++)
	{
		printf("%c", greeting[i]);
	}

	return 0;
}

8.4 多级指针

  • 多级指针就是指针的指针的指针。
  • 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。
  • 通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
  • 一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int** var;
  • 案例:
#include <stdio.h>

int main()
{
	// 多级指针
	int number = 100;
	int* p_one = &number; // 获取 number 的地址

	int** p_two = &p_one; // 使用运算符 & 获取 p_one 的地址

	printf("number = %d \n", number);
	printf("p_one = %p \n", p_one);
	printf("*p_one = %d \n", *p_one);
	printf("p_two = %p \n", p_two);
	printf("**p_two = %d \n", **p_two);

	return 0;
}

8.5 指针作为函数返回值

  • C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。
  • 案例: 定义了一个函数 strlong(),用来返回两个字符串中较长的一个。
#include <stdio.h>
#include <string.h>

// 返回长度最长的字符串
char *strlong(char *str1, char *str2) {
	printf("str1 address = %p \n", &str1);
	printf("str2 address = %p \n", &str2);
	if (strlen(str1) >= strlen(str2)) {
		return str1;
	}
	else {
		return str2;
	}
}
int main() {
	char str1[30], str2[30], *str;
	gets(str1);
	gets(str2);
	str = strlong(str1, str2);
	printf("str address = %p \n", &str);
	printf("Longer string: %s\n", str);
	return 0;
}

第九章 结构体和共用体

9.1 结构体

  • 类型定义(Typedef)
    • 在 C 语言中,可以使用typedef用来定义数据类型取新的名字。
    • typedef 语句的一般形式是:
typedef 已定义的类型 新的类型;

// 例如
typedef int INTEGER; // 指定用 INTEGER 代表 int 类型
typedef float REAL; // 指定用 REAL 代表 float 类型
  • 案例
#include <stdio.h>

int main()
{
	typedef int MYINT;	// 自定义int类型MYINT
	MYINT a = 0;
	printf("%d \n", a);

	typedef char MYCHAR;	// 自定义char类型MYCHAR
	MYCHAR ch = 'a';
	printf("%c\n", ch);
	return 0;
}
  • 结构体的定义。
    • 先看个例子:
#include <stdio.h>

int main()
{
	struct { int x; int y;}point; // 结构体变量
	point.x = 10;
	point.y = 11;
	printf("%d %d\n", point.x,point.y);
	return 0;
}
  • 给上面的结构体类型换名。
#include <stdio.h>

int main()
{
	typedef struct { int x; int y;} Point; // 结构体变量
	Point point;	// 从Point替换为point
	point.x = 10;
	point.y = 11;
	printf("%d %d\n", point.x,point.y);
	return 0;
}
  • 为了在所有的函数中使用,将结构体定义在main函数外面。
#include <stdio.h>
typedef struct // 结构体变量
{ 
	int x; 
	int y; 
} Point;

int main()
{
	Point point;	// 调用结构体
	point.x = 10;
	point.y = 11;
	printf("%d %d\n", point.x,point.y);
	return 0;
}
  • 另一种中定义格式:
#include <stdio.h>
struct Point	// 结构体变量
{ 
	int x; 
	int y; 
};

int main()
{
	struct Point point;	 // 调用结构体
	point.x = 10;
	point.y = 11;
	printf("%d %d\n", point.x,point.y);
	return 0;
}
  • 指向结构体的指针
#include <stdio.h>
typedef struct 
{ 
	int x; 
	int y; 
}Point;

int main()
{
	Point point;
	Point* p;
	p = &point;
	p->x = 10;
	p->y = 11;
	printf("%d %d\n", p->x,p->y);
	return 0;
}
  • 自引用结构指针
    • 在结构体内部定义自引用类型的指针。
#include <stdio.h>
typedef struct Point
{ 
	int x; 
	int y; 
	struct Point* next;	// 自引用类型的指针
}Point;

int main()
{
	Point p1,p2,p3,p4,p5;
	Point* p;
	p1.x = 1; p1.y = 7;
	p2.x = 4; p2.y = 3;
	p3.x = 2; p3.y = 4;
	p4.x = 3; p4.y = 2;
	p5.x = 1; p5.y = 6;

	p1.next = &p2;
	p2.next = &p3;
	p3.next = &p4;
	p4.next = &p5;
	p5.next = NULL;

	for(p=&p1;p!=NULL;p=p->next)
		printf("(%d, %d)\n", p->x,p->y);

	return 0;
}
  • 结构体定义的基本格式:
// 格式①
typedef struct // 结构体变量
{ 
	int x; 
	int y; 
} Point;

// 格式②
struct Point	// 结构体变量
{ 
	int x; 
	int y; 
};

// 格式③
typedef struct Point
{ 
	int x; 
	int y; 
	struct Point* next;	// 自引用类型的指针
}Point;
// 格式③在C/C++中均可使用

9.2 共用体

  • 在 C 语言中,允许几种不同类型的变量存放到同一段内存单元中。即覆盖技术:几个变量互相覆盖。
  • 这种几个不同的变量共同占用一段内存的结构,被称为共用体类型结构,简称<mark>共用体</mark>。
  • 一般定义形式为:
union 共用体名 
{ 
    数据类型 成员名 1; 
    数据类型 成员名 2; 
    ...... 
    数据类型 成员名 n; 
}变量名表列; 
  • <mark>必须先定义共用体变量,才能在后续的程序中引用它。不能直接引用共用体变量,而只能引用共用体变量中的成员。</mark>
  • 引用方法如下:
共用体变量名.成员名  
  • 案例
#include<stdio.h>
union DATA
{
	int i;
	float f;
	char str[10];
};
int main()
{
	union DATA d;
	d.i = 10;
	d.f = 2.3;
	strcpy(d.str, "C 语言");
	printf("d.i:%d \n", d.i);
	printf("d.f:%f \n", d.f);
	printf("d.str:%s \n", d.str);
	return 0;
}

本章练习:结构体

第十章 文件

此处参考于:C语言文件

  • 对于文件的操作分为三个步骤:
    • 第一步:打开文件;
    • 第二步:读写文件;
    • 第三步:关闭文件。

10.1 文件与文件指针

  • 文件(file):<mark>存储在外部介质上的数据集合</mark>。
  • 编写C语言程序,主要通过键盘输入给变量赋值,通过显示器显示输出数据。如果我们想读取磁盘已有的数据,需要读取磁盘文件,同时也可以将数据保存到文件中,进行永久存储。
  • 文件的3种分类方法如下:
    • ①按数据存放形式分为 ASCII 文件(文本文件)和二进制文件;
    • ②按存取方式分为顺序方式存取和随机方式存取;
    • ③按处理方式分为缓冲文件和非缓冲文作。

  • 文件类型指针
    • 每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(比如文件的名字,文件读写方式等),这些信息都保存在一个结构体变量中,而这个结构体类型是有系统声明的,<mark>不需要我们自己去定义</mark>。
    • 这个结构体由系统声明为FILE,包含在头文件“stdio.h”中。其内部具体文件类型声明:
struct _iobuf{
	char* _ptr;
	int   _cnt;
	char* _base;
	int   _flag;
	int   _file;
	int   _charbuf;
	int   _bufsiz;
	char* _tmpfname;
};
typedef struct _iobuf FILE;
  • 对于不同C编译器的FILE的类型包含的内容不完全相同,但是都大同小异。
  • 每当我们通过程序去打开文件的时候,<mark>系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,我们可以不用关注具体细节</mark>。
  • 在程序中直接利用 FILE 定义变量:

其实对于不同C编译器的FILE的类型包含的内容不完全相同,但是都大同小异.
每当我们通过程序去打开文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,我们可以不用关注具体细节.

FILE* pf;	// 创建了一个文件指针变量(FILE*类型)

定义pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能访问该文件,其实也就是说,通过文件指针变量能够找到与它关联的文件。

定义pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能访问该文件,其实也就是说,通过文件指针变量能够找到与它关联的文件.

10.2 文件的打开和关闭

  • 打开文件: fopen( ) 函数

    • 创建一个新的文件或者打开一个已有的文件。
  • 函数原型为:

`FILE` `*``fopen``(``const` `char` `*filename, ``const` `char` `*mode);`
  • filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
参数 作用
r 以只读方式打开文件,该文件必须存在。
r+ 以读/写方式打开文件,该文件必须存在。
rb+ 以读/写方式打开一个二进制文件,只允许读/写数据。
rt+ 以读/写方式打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
a+ 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
wb 以只写方式打开或新建一个二进制文件,只允许写数据。
wb+ 以读/写方式打开或新建一个二进制文件,允许读和写。
wt+ 以读/写方式打开或新建一个文本文件,允许读和写。
at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
// 例如:
FILE* fp;
fp=fopen(d:\\whh.txt","r");
  • 注意:
    • 该文件的目录是绝对路径,因此这样写,如果不写盘符比如 whh.txt 则表示相对路径,表示与本程序同目录下。
    • 路径中的反斜杠虽然只有一个,但这里打了两个,原因在于C语言字符串中对反斜杠要当作转义字符处理,因此要用两个反斜杠才能表示一个。
    • 一旦以r也就是只读的方式打开文件,后面则不允许写数据,否则会出错,一定要保持一致!

  • 关闭文件:fclose( )函数
    • 断开程序与文件关联,切断IO数据流,释放文件不在占用。
  • 函数调用方式:fclose(fp);
int fclose( FILE *fp );
  • 如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF

10.3 读写文件

  • 字符 <mark>读/写</mark> 函数 <mark>fgetc()和fputc()</mark>

    • 向文本读写一个字符。
  • <mark>fgetc()函数的功能是从指定的文件中读取一个字符</mark>,其调用的格式为:

字符变量 = fgetc (文件指针);

如果在执行fgetc()函数时遇到文件结束符,函数会返回一个文件结束符标志EOF(-1)。

  • <mark>fputc()函数的功能是把一个字符写入指定的文件中</mark>,其调用的格式为:
fput(字符,文件指针);
  • 案例:
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	FILE* fp1, *fp2;
	errno_t err;
	char c;
 
	err = fopen_s(&fp1, "d:\\1.txt", "w");
	if (err != 0) {
		printf("文件打开失败!\n");
		exit(0);
	}
	c = getchar();
	while (c != '\n') {
		fputc(c, fp1);
		c = getchar();
	}
	fclose(fp1);
 
	err = fopen_s(&fp2, "d:\\1.txt", "r");
	if (err != 0) {
		printf("文件打开失败!\n");
		exit(0);
	}
	c = fgetc(fp2);
	while (c != EOF) {
		printf("%c", c);
		c = fgetc(fp2);
	}
	printf("\n");
	fclose(fp2);
 
	return 0;
}

  • 字符串读/写函数fgets()和fputs()
  • <mark>fgets()函数的功能是从指定的文件中读取一个字符串</mark>,其调用的格式为:
fgets(字符数组名,n,文件指针);
  • 其中,n是一个正整数,表示从文件中读出的字符串不超过n-1个字符。在读入一个字符串后加上字符串结束标志'\0'。

  • 如果在执行fgets()函数时如果文件内的字符串读取c完毕,函数会返回0。

  • <mark>fputs()函数的功能是把一个字符串写入指定的文件中</mark>,其调用的格式为:

fputs(字符串,文件指针);
  • 其中,字符串可以是字符串常量、字符数组、字符指针变量。
  • 案例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
	FILE* fp1, *fp2;
	errno_t err;
	char s[30];
 
	err = fopen_s(&fp1,"d:\\1.txt", "w");
	if (err != 0) {
		printf("文件打开失败!\n");
		exit(0);
	}
	gets(s);
	while (strlen(s) > 0) {
		fputs(s, fp1);
		gets(s);
	}
	fclose(fp1);
 
	err = fopen_s(&fp2, "d:\\1.txt", "r");
	if (err != 0) {
		printf("文件打开失败!\n");
		exit(0);
	}
	while (fgets(s, 11, fp2) != 0) {
		printf("%s", s);
	}
	printf("\n");
	fclose(fp2);
 
	return 0;
}

  • 数据块读/写函数fread()和fwrite()
  • <mark>fread()函数的功能是从文件中读取字节长度为size的n个数据,并存放到buf指向的内存地址中去</mark>。
    • 函数的返回值为实际读出的数据项个数。
    • 其调用的格式为:
fread(buf,size,n,文件指针);
  • 比如:
fread(fa,4,5,fp);

// 其意义是从fp所指向的文件中,每次读4个字节长度(int)送入到fa指向的内存地址中去,连续读5次。也就是说,读5个int类型的数据到fa指向的内存中。
  • <mark>fread()函数的功能是将buf中存放的size*n个字节的数据输出到文件指针所指向的文件中去。</mark>
    • 函数的返回值为实际写入的数据项个数。
    • 其调用的格式为:
fwrite(buf,size,n,文件指针);

fread()和fwrite()函数一般适用于二进制文件,它们是按数据块的大小来处理输入/输出的。


格式化读/写函数fscanf()和fprintf()

  • 格式化读/写函数与标准的格式输入/输出函数功能相同,只不过它们的读/写对象不是键盘和显示器,而是文件。
  • fscanf()和fprintf()函数只适用于ASCII码文件的读/写。
  • 两个函数的格式如下:
fscanf(文件指针,格式字符串,输入列表);
fprintf(文件指针,格式字符串,输出列表);

fscanf()和fprintf()函数对文件进行读/写,使用方便,容易理解。但由于在输入时需要将ASCII码转换为二进制格式,在输出时又要将二进制格式转换为字符,花费时间较长,所以在内存与磁盘交换数据频繁的时候,最好不要用这两个函数。

10.4 文件的随机读写

在C语言中,打开文件时,文件指针指向文件头,即文件的起始位置。在读写文件时,需要从文件头开始,每次读写完一个数据后,文件指针会自动指向下一个数据的位置。但有时不想从文件头开始读取文件,而是读取文件中某个位置的数据。这时,系统提供了定位到某个数据存储位置的函数。

  • 文件头定位函数rewind()
    • <mark>rewind()函数用于把文件指针移动到文件首部</mark>,其调用的格式为:
rewind(文件指针);
  • 当前读/写位置函数ftell()
    • <mark>ftell()函数用于确定文件指针的当前读/写位置</mark>。
    • 此函数有返回值,若成功定位,则返回当前位置;否则返回-1。
    • 其调用的格式为:
ftell(文件指针);
  • 随机定位函数fseek()
    • <mark>fseek()函数用于将文件指针移动到某个确定的位置</mark>。
    • 此函数有返回值,若成功移动,则返回当前位置;否则返回-1。
    • 其调用的格式为:
fseek(文件指针,位移量,起始点);
  • 其中:位移量指从起始点向前移动的字节数,大多数C版本要求该位移量为long型数据;起始点有三种选择,具体的含义见下表:

起始点取值含义

起始点 表示符号 数字表示
文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2
  • 例如,将指针位置移动到距离文件开头100字节处:
fseek(fp,100L,0)

注意:fseek()函数一般用于二进制文件,因为文本文件计算位置往往比较混乱,容易发生错误。

  • 文件结束检测函数feof()
    • <mark>feof()函数用于判断文件是否处于文件结束为止</mark>。
    • 该函数有返回值,如果文件结束,函数的返回值为1;否则返回值为0。
    • 其调用的格式为:
feof(文件指针);

第十一章 预处理指令

11.1 宏替换

  • C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。即,<mark>C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理</mark>。
  • 通常将C 预处理器(C Preprocessor)简写为 CPP。
  • C 语言提供的预处理功能有三种,分别是:<mark>宏定义、文件包含和条件编译</mark>。
  • 所有的预处理器命令都是以井号(#)开头。
    • 它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
  • 在 C 语言中,宏分为<mark>有参数和无参数</mark>两种。无参宏的宏名后不带参数,其定义的一般形式为:
#define 标识符 字符串(或值);  

// 例如: 
#define PI 3.1415926
  • 在宏<mark>定义中的参数称为形式参数</mark>,在宏<mark>调用中的参数称为实际参数</mark>。对于带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
  • 带参宏定义与宏调用的一般形式为:
#define 宏名(形参表) 字符串; 
// 在字符串中含有各个形参

// 宏调用
宏名(实参表);  
  • 例如:
#define M(y) y*y+3*y // 宏定义

......

k=M(5);	// 宏调用
  • 具体案例:
#include <stdio.h>
#define MAX(a,b) (a>b)?a:b	// 带参数的宏定义
int main()
{
	int x, y, max;
	printf("请输入两个整数: ");
	scanf("%d %d", &x, &y);
	max = MAX(x, y);	// 宏调用
	printf("max=%d\n", max);
	return 0;
}

由此,可以知道<mark>宏替换相当于实现了一个函数调用的功能,而事实上,与函数调用相比,宏调用更能提高</mark>。

11.2 文件包含

  • 文件包含命令行的一般形式为:
#include "文件名" 
// 或者 
#include <文件名> 
  • 文件包含命令的功能:在一个源文件中,将另一个文件包含进来。即,另一个文件是该文件的一部分。
  • 包含命令中的文件名可以用双引号引起来,也可以用尖括号引起来。
  • 文件包含可以嵌套。

C语言头文件<>和""的区别:

  • 头文件#include <> :表示引用标准库头文件,编译器会从系统配置的库环境中去寻找。
  • 头文件#include "":一般表示用户自己定义使用的头文件,编译器默认会从当前文件夹中寻找,如果找不到,则到系统默认库环境中去寻找。

11.3 条件编译

  • 条件编译:可以按不同的条件去编译不同的程序部分。
  • 条件编译的三种形式:
// 第一种形式: 
#ifdef 标识符 
程序段 1 
#else 
程序段 2 
#endif 
// 注:如果标识符已被 #define 命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编译。     
// 第二种形式: 
#ifndef 标识符 
程序段 1 #else 
程序段 2 #endif 
// 注:如果标识符未被#define 命令定义过则对程 序段 1 进行编译,否则对程序段 2 进行编译。    
// 第三种形式: 
#if 常量表达式 
程序段 1 #else 
程序段 2 #endif 
// 注:如果常量表达式的值为真(非 0),则对程序段 1 进行编译,否则对程序段 2 进行编译。 
  • 所有重要的预处理器指令:
指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

本章练习:预处理

🎉🎉🎉全剧终🎉🎉🎉

至此C语言入门就全部结束了。可能会有诸多不足,比如强制类型转换、排序等未作代码说明与文字注解。全文仅记录个人C语言学习过程。另外,笔者还有个专升本备考交流Q群:1055741739。群资源全部免费,欢迎进群交流。但注意:不要发广告!不要发广告!不要发广告!否则飞机票!

但对于计算机高级语言的学习仍不可止步于此。学编程最重要的是:<mark>敲代码</mark>。不是去看,而是去敲。重要的事情说三遍:

敲代码!敲代码!敲代码