C語言
數組
定義一維數組
類型符 數組名[常量表達式];
int a[10];
int b[5] = {1, 2, 3, 4, 5};
void func(int n)
{
int a[2 * n];
}
指定數組爲靜態static
存儲方式,則不能用“可變長數組”
static int a[2 * n]; // 不合法,a數組指定爲`static`存儲方式
指針
存儲地址的變量:類型+地址
類型決定了指針加1時,地址移動多少個字節,例如:
int *p, a = 10;
p = &a;
p = p + 1; // 移動4個字節,因爲一個正數在內存中佔4個字節
指針+1,是加上一個元素所佔用的字節數
定義指針
int *p1, *p2, a, b;
引用指針
- & 取址運算符
- * 指針運算符,或間接訪問運算符,
*p
代表指針變量p
指向的對象。
int a = 100, b = 10;
int *p1, *p2;
p1 = &a;
p2 = &b;
指針引用數組
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = &a[0]; // p的值是a[0]的地址
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = a; // p的值是數組首元素(即a[0])的地址
數組引用的三種方法
-
下標法
#inlcude <stdio.h> int main() { int a[10]; int i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(i=0;i<10;i++) { printf("%d", a[i]); } printf("\n"); return 0; }
-
通過數組名計算元素地址,找出元素的值
#inlcude <stdio.h> int main() { int a[10]; int i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(i=0;i<10;i++) { printf("%d", *(a+i)); } printf("\n"); return 0; }
-
用指針變量指向數組元素
#inlcude <stdio.h> int main() { int a[10]; int *p, i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(p=a;p<(a+10);p++) { printf("%d", *p); } printf("\n"); return 0; }
方法1、2效率相同,C編譯系統是將a[i]
轉換爲*(a+1)
處理的,即先計算元素地址,因此方法1、2找數組元素費時較多。第3種方法快於1、2方法,用指針變量直接指向元素,不用每次都重新計算地址,像p++
這樣的自加操作是比較快的。有規律地改變地址值能大大提高執行效率。不過方法1、2,能更直觀判斷處理到第幾個元素了,而第3種方法則不那麼直觀。
指針,數組作爲形參
void func(int a[]);
void func(int *p);
C編譯都是將形參數組作爲指針變量來處理的
指針引用字符串
char string[] = "I love China";
char *string = "I love China";
指向函數的指針
編譯系統爲函數代碼分配一段存儲空間,這段存儲空間的起始地址(又稱入口地址)稱爲這個函數的指針。
類型名 (*指針變量名)(函數參數列表);
int (*p) (int, int);
用函數指針變量調用函數
#include <stdio.h>
int main()
{
int max(int, int);
int (*p) (int, int);
int a, b, c;
p = max;
printf("please enter a and b: ");
scanf("%d,%d", &a, &b);
c = (*p)(a, b);
return 0;
}
動態爲函數指針綁定結構相同(返回類型,參數個數,每個參數的類型相同)的函數
#include <stdio.h>
int main()
{
int max(int, int);
int min(int x, int y);
int (*p) (int, int);
int a, b, c, n;
printf("please enter a and b: ");
scanf("%d,%d", &a, &b);
printf("please choose 1 or 2: ");
scanf("%d", &n);
if (n == 1) p = max;
else p = min;
c = (*p)(a, b);
return 0;
}
用指向函數的指針作爲函數參數
void fun(int (*x1)(int), int (*x2)(int, int))
{
int a, b, i = 3, j = 5;
a = (*x1)(i);
b = (*x2)(i, j);
}
一個完整的例子:
#include <stdio.h>
int main()
{
int fun(int x, int y, int (*p)(int, int));
int max(int, int);
int min(int, int);
int add(int, int);
int a = 34, b = -21, n;
printf("please choose 1, 2 or 3: ");
scanf("%d", &n);
if (n == 1) fun(a, b, max);
else if (n == 2) fun(a, b, min);
else if (n == 3) fun(a, b, add);
return 0;
}
in fun(int x, int y, int (*p)(int, int))
{
int result;
result = (*p)(x, y);
printf("%d\n", result);
}
int max(int x, int y)
{
int z;
if (x > y) z = x;
else z = y;
return z;
}
int min(int x, int y)
{
int z;
if (x > y) z = y;
else z = x;
return z;
}
int add(int x, int y)
{
return x + y;
}
返回指針值的函數
類型名 *函數名(參數列表)
float *search((*p)[4], int n);
動態內存分配
void *malloc(unsigned size);
void *calloc(unsigned n, unsigned int size);
void free(void *p);
void *realloc(void *p, unsigned int size);
void指針
可以指向任意類型的指針
int a = 3;
int *p1 = &a;
char *p2;
void *p3;
p3 = (void *)p1;
p2 = (char *)p3;
p3 = &a;
printf("%d", *p3); // p3得到純地址,但並不指向a,不能通過*p3輸出a的值
結構體
定義使用結構體
struct 結構體名
{
成員列表
} 變量名列表;
例:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1, student2;
初始化和引用:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1={101, "Li Lin", 'M', 8, 0.1, "123 Nanhu Road"};
某一成員初始化:
struct Student b = {.name="Zhang"};
引用和可進行的操作:
student1.num = 10001;
student1.num;
student1.birthday.month;
student1.age++;
sum = student1.score + student2.score;
student1 = student2;
scanf("%d",&student1.num); // 輸入&student1.num的值
printf("%o", &student1); // 結構體的首地址
結構體數據和結構體指針
結構體數組
struct Votes
{
char name[20];
int count;
} leader[3] = {"A", 10, "B", 8, "C", 5};
struct Votes
{
char name[20];
int count;
};
struct Votes leader[3];
struct Votes leader[3] = {"A", 10, "B", 8, "C", 5};
結構體指針
struct Votes v;
struct Votes *p;
p = &v;
(*p).count = 1000;
p->count = 1000;
指向運算符:
->
,p->count
等價於(*p).count
指向結構體數組的指針
struct Votes *p;
struct Votes leader[3] = {"A", 10, "B", 8, "C", 5};
for (p = leader;p<(leader+3);p++)
{
printf("%s: %d", p->name, p->count);
}
共同體(聯合體)
所有成員從同一起始地址開始存儲。
union 共同體名
{
成員列表;
} 變量列表;
特點:
-
在某一瞬間,只能存放其中一個成員的值,而不能同時存放多個。
-
可以初始化共同體,但只能有一個變量
union Data { int i; char ch; float f; } d = {1, 'a', 1.5}; // 錯誤,不能初始化3個成員,因爲它們佔用同一塊存儲單元 union Data d = {16}; // 初始化第一個成員 union Data d = {.ch = 'j'}; // 初始化指定的成員
-
起作用的成員是最後一次被賦值的成員
枚舉(enumeration)
聲明和使用枚舉類型
enum [枚舉名] {枚舉元素列表};
enum Weekday
{
sun, mon, tue, wed, thu, fri, sat
};
然後可以用此類型來定義枚舉類型變量:
enum Weekday workday, weekend;
// 他們的直只能來自枚舉類型定義中的枚舉常量(枚舉元素)之一:sun, mon, tue, wed, thu, fri, sat
workday = mon; // 正確
weekend = sum; // 正確
workday = monday; // 錯誤,monday不是枚舉類型Weekday的枚舉常量(枚舉元素)
也可以不聲明有名字的枚舉類型,而直接定義枚舉變量:
enum {sun, mon, tue, wed, thu, fri, sat} workday, weekend;
性質:
-
枚舉常量不能賦值
-
每個枚舉元素都代表一個整數,C語言按照定義時的順序默認它們的值爲0,1,2,3…
指定元素值的定義:
enum Weekday { sun=7, mon=1, tue, wed, thu, fri, sat // sun爲7,mon爲1,後依序加1,sat爲6 } workday, weekend;
-
枚舉元素可以比較大小
if (workday == mon) ; if (workday > sun) ;
使用typedef
聲明新類型
-
簡單地用一個新的類型名代替原有類型名
typedef int Integer; typedef float Real;
-
用簡單的類型名代替複雜的類型表示方法
(1). 新類型名代表結構體類型
typedef struct { int month; int day; int year } Date; Date birthday; // 不能再寫成struct DateStruct birthday; Date *p;
(2). 新類型名代表數組類型
typedef int Num[100]; // 聲明Num爲數組類型名 Num a; // a爲100個元素大小的整數數組
(3). 新類型名代表指針類型
typedef char * String; String p, s[10]; // 定義p爲字符指針變量,s爲字符指針數組
(4). 新類型名代表指向函數的指針
typedef int (* Pointer)(); // 聲明Pointer爲指向函數的指針類型,該函數返回整型值 Pointer p1, p2; // p1, p2爲Pointer類型的指針
文件操作
ANSI C標準採用”緩衝文件系統“處理數據文件,自動在內存中爲程序中每個文件開闢一個文件緩衝區。內存到硬盤輸出必須先送到內存緩衝區,內存緩衝區裝滿後寫入硬盤,從硬盤到內存,一次從硬盤讀取文件的的一部分到內存緩衝區(寫滿),然後從緩衝區逐個地將數據送到程序數據區(給程序變量)。
文件類型指針
由一個結構體保存文件信息:文件名,文件狀態,大小,當前位置等。一種C
編譯環境提供的stdio.h
有以下文件類型聲明:
typedef struct
{
short level; // 緩衝區“滿”或“空”的程度
unsigned flags; // 文件狀態標誌
char fd; // 文件描述符
unsigned char hold; // 如緩衝區無內容不讀取字符
short bsize; // 緩衝區的大小
unsigned char * buffer; // 數據緩衝區的位置
unsigned char * curp; // 指針當前的指向
unsigned istemp; // 臨時文件指示器
short token; // 用於有效性檢查
} FILE;
打開關閉文件
#include <stdio.h>
FILE *fp;
fp = fopen("filename", "r");
文件打開模式:
模式字符 | 含義 | 如果文件不存在 |
---|---|---|
“r” | 讀取 | 出錯 |
“w” | 寫入 | 新建文件 |
“a” | 追加 | 出錯 |
“rb” | 二進制讀取 | 出錯 |
“wb” | 二進制寫入 | 新建文件 |
“ab” | 二進制追加 | 出錯 |
“r+” | 讀寫 | 出錯 |
“w+” | 讀寫 | 新建文件 |
“a+” | 讀追加 | 出錯 |
“rb+” | 二進制讀寫 | 出錯 |
“wb+” | 二進制讀寫 | 新建文件 |
“ab+” | 二進制讀追加 | 出錯 |
fclose(fp);
順序讀寫數據文件
讀寫單個字符:fgetc
,fputc
char fgetc(FILE *fp);
int fputc(char ch, FILE *fp)
讀寫字符串: fgets
, fputs
char * fgets(char *str, int n, FILE *fp); // 讀取長度爲n-1的字符串,存放到str裏面
int fputs(char *str, FILE *fp)
格式化讀寫
fprintf(FILE *fp, char *fmt_str,...);
fscanf(FILE *fp, char *fmt_str,...);
fprintf(fp, "%d,%6.2f", i, f);
fscanf(fp, "%d,%6.2f", &i, &f);
輸入時需要將文件中的編碼字符轉換爲二進制形式再保存在變量中;輸出時又需要將二進制形式轉換成編碼字符。轉換操作需要耗費較多時間,因此對於內存與硬盤頻繁交換數據的場景,儘量避免使用fprintf
和fscanf
函數。
二進制讀寫
fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);
buffer: 地址,存放從文件讀入的數據
size: 數據項大小(字節數),通常通過sizeof()
函數得到
count: 要讀寫多少個數據項(每個數據項的長度爲size)
fp: FILE指針
隨機讀寫
rewind(FILE *fp); // 返回到文件頭
fseek(FILE *fp, int count, int start) // 偏移位置
ftell(FILE *fp); // 文件當前位置
起始點 | 變量名 | 數字 |
---|---|---|
文件開始位置 | SEEK_SET | 0 |
文件當前位置 | SEEK_CUR | 1 |
文件結尾位置 | SEEK_END | 2 |
文件讀寫錯誤檢測
ferror(FILE *fp);
clearerr(FILE *fp);