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()函数,表示输入了。
- 具体参考:VS2017C4996报错。
第二章 变量与常量
- 此处先做了解!!!
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 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else 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 语句有以下两种用法:
- 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
- 它可用于终止 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 语言中有三个地方可以声明变量:
- 在函数或块内部的局部变量
- 在所有函数外部的全局变量
- 在形式参数的函数参数定义中
- 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。
- 全局变量是定义在函数外部,通常是在程序的顶部。
- 全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。
- 全局变量可以被任何函数访问。
- 也就是说,全局变量在声明后整个程序中都是可用的。
- 具体案例如下:
#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 什么是指针?
- 参考于知乎:C语言如何理解指针。
<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>。不是去看,而是去敲。重要的事情说三遍:
敲代码!敲代码!敲代码
!