C 語言教程
參考於視頻:C語言教程(vs2019版);用於C入門、期末備考。
練習網站:編程入門訓練。
C語言教程推薦: C語言入門教程
第一章 入門
- C 語言是一種通用的、面向過程式的計算機程序設計語言。
- 1972 年,為了移植與開發 UNIX 操作系統,丹尼斯·里奇在貝爾電話實驗室設計開發了 C 語言。
- 當前最新的 C 語言標准為 C18 ,在它之前的 C 語言標准有 C17、C11...C99 等。
- 編譯器安裝,初學建議使用Dev-C++,安裝步驟。
- 注意:如果是C程序,文件后綴保存為.c;如果是C++程序,文件后綴保存為.cpp
- 筆者使用的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最常用的,一定要記住。
- 注釋: 先 Ctrl+ K,然后 Ctrl+ C
- 取消注釋: 先 Ctrl+ K,然后 Ctrl + U
在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;
}
關於前++和后++的區別:
- 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;
}
- 注:一個break只能跳出一層循環。
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;
}
形參與實參的區別:
參數 | 位置 | 使用范圍 | 傳遞方向 |
---|---|---|---|
形參 | 函數定義時括號中的參數 | 函數體內 | 形參不可以傳遞給實參 |
實參 | 調用時函數是括號中的參數 | 主調函數 | 實參可以傳遞給形參 |
以下為專業說法(了解即可):
- 形參變量只有在函數被調用時才會分配內存,調用結束后,立刻釋放內存,所以形參變量只有在函數內部有效,不能在函數外部使用。
- 實參可以是常量、變量、表達式、函數等,無論實參是何種類型的數據,在進行函數調用時,它們都必須有確定的值,以便把這些值傳送給形參,所以應該提前用賦值、輸入等辦法使實參獲得確定值。
- 實參和形參在數量上、類型上、順序上必須嚴格一致,否則會發生“類型不匹配”的錯誤。當然,如果能夠進行自動類型轉換,或者進行了強制類型轉換,那么實參類型也可以不同於形參類型。
- 函數調用中發生的數據傳遞是單向的,只能把實參的值傳遞給形參,而不能把形參的值反向地傳遞給實參;換句話說,一旦完成數據的傳遞,實參和形參就再也沒有瓜葛了,所以,在函數調用過程中,形參的值發生改變並不會影響實參。
作用域:
- 任何一種編程中,作用域是程序中定義的變量所存在的區域,超過該區域變量就不能被訪問。
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;
}
- 在程序中,局部變量和全局變量的名稱可以相同,但是在函數內,如果兩個名字相同,會使用局部變量值,全局變量不會被使用。
本章練習:函數
第七章 數組
數組是把一系列相同數據類型的變量組合在一起的數據集合。
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語言如何理解指針。
指針就是存儲了另一個數據的內存地址的一種數據類型。即指針中的數據就是另一個數據的內存地址。
- 指針就是內存地址,指針變量是用來存放內存地址的變量。
- 就像其他變量或常量一樣,您必須在使用指針存儲其他變量地址之前,對其進行聲明。指針變量聲明的一般形式為:
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 語言中,允許幾種不同類型的變量存放到同一段內存單元中。即覆蓋技術:幾個變量互相覆蓋。
- 這種幾個不同的變量共同占用一段內存的結構,被稱為共用體類型結構,簡稱共用體。
- 一般定義形式為:
union 共用體名
{
數據類型 成員名 1;
數據類型 成員名 2;
......
數據類型 成員名 n;
}變量名表列;
- 必須先定義共用體變量,才能在后續的程序中引用它。不能直接引用共用體變量,而只能引用共用體變量中的成員。
- 引用方法如下:
共用體變量名.成員名
- 案例
#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):存儲在外部介質上的數據集合。
- 編寫C語言程序,主要通過鍵盤輸入給變量賦值,通過顯示器顯示輸出數據。如果我們想讀取磁盤已有的數據,需要讀取磁盤文件,同時也可以將數據保存到文件中,進行永久存儲。
- 文件的3種分類方法如下:
- ①按數據存放形式分為 ASCII 文件(文本文件)和二進制文件;
- ②按存取方式分為順序方式存取和隨機方式存取;
- ③按處理方式分為緩沖文件和非緩沖文作。
- 文件類型指針
- 每個被使用的文件都會在內存中開辟一個相應的文件信息區,用來存放文件的相關信息(比如文件的名字,文件讀寫方式等),這些信息都保存在一個結構體變量中,而這個結構體類型是有系統聲明的,不需要我們自己去定義。
- 這個結構體由系統聲明為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的類型包含的內容不完全相同,但是都大同小異。
- 每當我們通過程序去打開文件的時候,系統會根據文件的情況自動創建一個FILE結構的變量,並填充其中的信息,我們可以不用關注具體細節。
- 在程序中直接利用 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 讀寫文件
-
字符 讀/寫 函數 fgetc()和fputc()
- 向文本讀寫一個字符。
-
fgetc()函數的功能是從指定的文件中讀取一個字符,其調用的格式為:
字符變量 = fgetc (文件指針);
如果在執行fgetc()函數時遇到文件結束符,函數會返回一個文件結束符標志EOF(-1)。
- fputc()函數的功能是把一個字符寫入指定的文件中,其調用的格式為:
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()
- fgets()函數的功能是從指定的文件中讀取一個字符串,其調用的格式為:
fgets(字符數組名,n,文件指針);
-
其中,n是一個正整數,表示從文件中讀出的字符串不超過n-1個字符。在讀入一個字符串后加上字符串結束標志'\0'。
-
如果在執行fgets()函數時如果文件內的字符串讀取c完畢,函數會返回0。
-
fputs()函數的功能是把一個字符串寫入指定的文件中,其調用的格式為:
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()
- fread()函數的功能是從文件中讀取字節長度為size的n個數據,並存放到buf指向的內存地址中去。
- 函數的返回值為實際讀出的數據項個數。
- 其調用的格式為:
fread(buf,size,n,文件指針);
- 比如:
fread(fa,4,5,fp);
// 其意義是從fp所指向的文件中,每次讀4個字節長度(int)送入到fa指向的內存地址中去,連續讀5次。也就是說,讀5個int類型的數據到fa指向的內存中。
- fread()函數的功能是將buf中存放的size*n個字節的數據輸出到文件指針所指向的文件中去。
- 函數的返回值為實際寫入的數據項個數。
- 其調用的格式為:
fwrite(buf,size,n,文件指針);
fread()和fwrite()函數一般適用於二進制文件,它們是按數據塊的大小來處理輸入/輸出的。
格式化讀/寫函數fscanf()和fprintf()
- 格式化讀/寫函數與標准的格式輸入/輸出函數功能相同,只不過它們的讀/寫對象不是鍵盤和顯示器,而是文件。
- fscanf()和fprintf()函數只適用於ASCII碼文件的讀/寫。
- 兩個函數的格式如下:
fscanf(文件指針,格式字符串,輸入列表);
fprintf(文件指針,格式字符串,輸出列表);
fscanf()和fprintf()函數對文件進行讀/寫,使用方便,容易理解。但由於在輸入時需要將ASCII碼轉換為二進制格式,在輸出時又要將二進制格式轉換為字符,花費時間較長,所以在內存與磁盤交換數據頻繁的時候,最好不要用這兩個函數。
10.4 文件的隨機讀寫
在C語言中,打開文件時,文件指針指向文件頭,即文件的起始位置。在讀寫文件時,需要從文件頭開始,每次讀寫完一個數據后,文件指針會自動指向下一個數據的位置。但有時不想從文件頭開始讀取文件,而是讀取文件中某個位置的數據。這時,系統提供了定位到某個數據存儲位置的函數。
- 文件頭定位函數rewind()
- rewind()函數用於把文件指針移動到文件首部,其調用的格式為:
rewind(文件指針);
- 當前讀/寫位置函數ftell()
- ftell()函數用於確定文件指針的當前讀/寫位置。
- 此函數有返回值,若成功定位,則返回當前位置;否則返回-1。
- 其調用的格式為:
ftell(文件指針);
- 隨機定位函數fseek()
- fseek()函數用於將文件指針移動到某個確定的位置。
- 此函數有返回值,若成功移動,則返回當前位置;否則返回-1。
- 其調用的格式為:
fseek(文件指針,位移量,起始點);
- 其中:位移量指從起始點向前移動的字節數,大多數C版本要求該位移量為long型數據;起始點有三種選擇,具體的含義見下表:
起始點取值含義
起始點 | 表示符號 | 數字表示 |
---|---|---|
文件首 | SEEK_SET | 0 |
當前位置 | SEEK_CUR | 1 |
文件尾 | SEEK_END | 2 |
- 例如,將指針位置移動到距離文件開頭100字節處:
fseek(fp,100L,0)
注意:fseek()函數一般用於二進制文件,因為文本文件計算位置往往比較混亂,容易發生錯誤。
- 文件結束檢測函數feof()
- feof()函數用於判斷文件是否處於文件結束為止。
- 該函數有返回值,如果文件結束,函數的返回值為1;否則返回值為0。
- 其調用的格式為:
feof(文件指針);
第十一章 預處理指令
11.1 宏替換
- C 預處理器不是編譯器的組成部分,但是它是編譯過程中一個單獨的步驟。即,C 預處理器只不過是一個文本替換工具而已,它們會指示編譯器在實際編譯之前完成所需的預處理。
- 通常將C 預處理器(C Preprocessor)簡寫為 CPP。
- C 語言提供的預處理功能有三種,分別是:宏定義、文件包含和條件編譯。
- 所有的預處理器命令都是以井號(#)開頭。
- 它必須是第一個非空字符,為了增強可讀性,預處理器指令應從第一列開始。
- 在 C 語言中,宏分為有參數和無參數兩種。無參宏的宏名后不帶參數,其定義的一般形式為:
#define 標識符 字符串(或值);
// 例如:
#define PI 3.1415926
- 在宏定義中的參數稱為形式參數,在宏調用中的參數稱為實際參數。對於帶參數的宏,在調用中,不僅要宏展開,而且要用實參去代換形參。
- 帶參宏定義與宏調用的一般形式為:
#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;
}
由此,可以知道宏替換相當於實現了一個函數調用的功能,而事實上,與函數調用相比,宏調用更能提高。
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。群資源全部免費,歡迎進群交流。但注意:不要發廣告!不要發廣告!不要發廣告!否則飛機票!
但對於計算機高級語言的學習仍不可止步於此。學編程最重要的是:敲代碼。不是去看,而是去敲。重要的事情說三遍:
敲代碼!敲代碼!敲代碼
!