概述
C語言對於Android開發來說還是非常必要的,不管你是要閱讀原始碼,還是想要學習NDK,音視訊,效能優化等,都不可避免需要接觸到C,而且C語言屬於系統級的語言,作業系統核心都有C的身影,所以我今天學習一下C語言,本篇部落格作為筆記,以防以後忘記
C簡介
C語言最初適用於系統開發工作的,特別是組成作業系統的程式,由於C語言產生的程式碼執行速度與彙編編寫的程式碼執行速度幾乎相同,所以採用C語言作為系統開發語言,下面列舉幾個使用C的例項
- 作業系統
- 文字編輯器
- 印表機
- 網路驅動器等
C環境的設定
寫在原始檔的中程式碼,使我們人類可以看懂的,他需要編譯轉換為機器語言,這樣CPU可以按照指令執行程式,而C語言可以通過GUN編譯器,把原始碼編譯為機器語言
Mac上直接下載Xcode就可以使用GUN編譯器,開發工具可以使用CLion
或者直接用文字編譯器
先寫一個HelloWord
#include <stdio.h>
int main()
{
/* 我的第一個 C 程式 */
printf("第一個c程式Hello, World! \n");
return 0;
}
複製程式碼
一個C程式包括
- 前處理器指令 : 程式的第一行
#include <stdio.h>
是預處理指令,告訴C編譯器實際編譯之前需要包括stdio.h
檔案 - 函式 : 下一行
int main()
是主函式,程式從這裡開始執行,printf()
是程式中另一個可用的函式作用是在螢幕上顯示Hello, World!
- 變數
- 語句&表示式
- 註釋: /* ... */這就是一個註釋
return 0;
標識此函式結束,並返回0
編譯執行C程式
- 首先開啟文字編輯器新增上方程式碼
- 儲存檔案為hello.c
- 開啟命令列,進入檔案的資料夾
- 命令列輸入gcc hello.c,編譯程式碼
- 如果沒有錯誤的話,資料夾就會生成a.out的可執行檔案
- 命令列輸入a.out,執行程式
- 然後螢幕上就可以看到
Hello, World!
Last login: Mon Feb 17 14:53:00 on ttys002
L-96FCG8WP-1504:~ renxiaohui$ cd Desktop/
L-96FCG8WP-1504:Desktop renxiaohui$ cd c/
L-96FCG8WP-1504:c renxiaohui$ gcc hello.c
L-96FCG8WP-1504:c renxiaohui$ a.out
第一個c程式 Hello, World!
L-96FCG8WP-1504:c renxiaohui$
複製程式碼
C語言入門
1 基本語法
識別符號
標記符是用來標記變數,函式或者任何使用者自定的變數名稱,一個識別符號以字母A—Z,a-z,或者_下劃線開始,後面跟0個或多個字母,數字,下劃線
識別符號中不允許出現標點字元,比如@%,識別符號是區分大小寫的
關鍵字
這些關鍵字不能作為常量名或者變數名,其他識別符號的名稱
關鍵字 | 說明 |
---|---|
continue | 結束當前迴圈,開始下一輪迴圈 |
switch | 用於開關語句 |
case | 開關語句分支 |
default | 開關語句中的其他分支 |
break | 跳出當前迴圈 |
do | 迴圈語句的迴圈體 |
while | 迴圈語句的迴圈條件 |
if | 條件語句 |
else | 條件語句否定分支與if一起使用 |
for | 一種迴圈語句 |
goto | 無條件跳轉語句 |
return | 子程式返回語句,可帶引數可不帶引數 |
char | 宣告字元變數或者返回值型別 |
double | 宣告雙精度浮點型別物件或函式返回值型別 |
float | 宣告浮點型變數或返回值型別 |
short | 宣告短整形變數或者返回值型別 |
int | 宣告整形變數或者返回值型別 |
long | 宣告長整形變數或返回值型別 |
unsigned | 宣告無符號型別變數或返回值型別 |
void | 宣告函式無返回值或無引數,宣告無型別指標 |
enum | 宣告列舉型別 |
static | 宣告靜態變數 |
auto | 自動宣告變數 |
const | 定義常量,如果一個變數被const修飾,則它的值不能被改變 |
extern | 宣告變數或函式在其他檔案或本檔案的其他位置定義 |
register | 宣告暫存器變數 |
signed | 宣告有符號型別的變數 |
sizeof | 計算資料型別或變數的長度 |
struct | 宣告結構體型別 |
union | 宣告共用體 |
typedef | 給資料型別取別名 |
volatile | 說明變數在執行過程中可以隱含的改變 |
可以看到70%都是java中有的,學習起來並不是很難
2 資料型別
C中的資料型別可以分為以下幾種
型別 | 描述 |
---|---|
基本資料型別 | 他們是算術型別,包含倆種型別,整數型別和浮點數型別 |
列舉型別 | 他們也是算術型別,被用來定義在程式中只能賦予其一定的離散整數值得變數 |
void | 標識沒有可用的值 |
派生型別 | 他包括指標型別,陣列型別,結構型別,共用體型別和函式型別 |
我們先介紹基本資料型別
整數型別
型別 | 儲存大小 | 值範圍 |
---|---|---|
char | 1位元組 | -128-127或0-255 |
unsigned char | 1位元組 | 0-255 |
signed char | 1位元組 | -128-127 |
int | 2或4位元組 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2或4位元組 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2位元組 | -32,768 到 32,767 |
unsigned short | 2位元組 | 0 到 65,535 |
long | 4位元組 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4位元組 | 0 到 4,294,967,295 |
各型別的儲存大小,與系統位數有關,為了得到準確大小可以用sizeof
來計算
#include <stdio.h>
#include <limits.h>
int main(){
printf("int 儲存大小 : %lu \n", sizeof(int));
return 0 ;
}
複製程式碼
int 儲存大小 : 4
複製程式碼
浮點型別
型別 | 儲存大小 | 值範圍 | 精度 |
---|---|---|---|
float | 4位元組 | 1.2E-38 到 3.4E+38 | 6 位小數 |
double | 8位元組 | 2.3E-308 到 1.7E+308 | 15位小數 |
long double | 16位元組 | 3.4E-4932 到 1.1E+4932 | 19位小數 |
void型別
型別 | 描述 |
---|---|
函式返回為空 | C 中有各種函式都不返回值,或者您可以說它們返回空。不返回值的函式的返回型別為空。例如 void exit (int status); |
函式引數為空 | C 中有各種函式不接受任何引數。不帶引數的函式可以接受一個 void。例如 int rand(void); |
指標指向void | 型別為void*指標代表物件的地址,而不是型別,例如,記憶體分配函式 void *malloc( size_t size ); 返回指向 void 的指標,可以轉換為任何資料型別。 |
3 變數
變數是程式可操作的儲存區名稱,C中每個變數都有特定的型別,型別決定了變數的大小和佈局,該範圍內的值都可以儲存在記憶體中
C語言中有基本資料型別的變數,也可以有其他型別的變數,比如陣列,指標,列舉,結構體,共用體等
變數的定義
變數的定義就是告訴儲存器何處建立變數的儲存,以及如何建立變數的儲存,變數定義指定資料型別,幷包含該型別的一個或多個變數列表
type variable_list;
複製程式碼
type必須是一個有效的C資料型別,variable_list為變數的名字,可以有多個
int i, j, k;
char c, ch;
float f, salary;
double d;
複製程式碼
上方i,j,k等宣告並定義了變數i,j,k
變數可以在宣告的時候初始化
type variable_name = value;
例如
extern int d = 3, f = 5; // d 和 f 的宣告與初始化
int d = 3, f = 5; // 定義並初始化 d 和 f
byte z = 22; // 定義並初始化 z
char x = 'x';
複製程式碼
C中變數的宣告
變數的宣告,像編輯器保證變數以指定的型別和名稱存在,這樣編輯器就可以在不知道變數完整細節的情況下,也能進一步編譯
變數宣告有倆種情況
- 一種是需要建立儲存空間的,例如:int a 在宣告的時候就已經建立了儲存空間。
- 另一種是不需要建立儲存空間的,通過extern關鍵字宣告變數名而不定義它,例如:extern int a 其中變數 a 可以在別的檔案中定義的
- 除非有extern關鍵字,其他的都是變數的定義
extern int i; //宣告,不是定義
int i; //宣告,也是定義
複製程式碼
4 常量
常量是固定的值,在程式期間不會改變,這些固定的值又叫做字面量
常量可以是任何基本資料型別,常量在值定義之後不會修改
定義常量
在C中有倆種定義常量的方式
- 使用
#define
前處理器
#define identifier value
#include <stdio.h>
#include <limits.h>
#define FFF 10
#define DDD 20
#define HHH '\n'
int main(){
int a ;
a =FFF*DDD;
printf("值,%d",a);
printf("\n字串,%c",HHH);
return 0 ;
}
複製程式碼
值,200
字串,
複製程式碼
- 使用
const
關鍵字
const type variable = value;
int main() {
const int FFF =10;
const int DDD=20;
const char HHH='\n';
int a;
a = FFF * DDD;
printf("值,%d", a);
printf("\n字串,%c", HHH);
return 0;
}
複製程式碼
值,200
字串,
複製程式碼
5 儲存類
儲存類定義C中變數,函式的範圍和生命週期,這些說明符放在他們所修飾的型別之前,下面列出可用的儲存類
- auto
- register
- static
- extern
auto儲存類
auto儲存類是所有區域性變數預設的儲存類
{
int mount;
auto int month;
}
複製程式碼
上面的兩種寫法都是一樣的,auto只能用在函式內,即只能修飾區域性變數
register
用於定義儲存在暫存器中而不是RAM的區域性變數,這意味著變數的最大大小是暫存器的大小
{
register int miles;
}
複製程式碼
暫存器只用於需要快速訪問的變數,比如計數器
static
編譯器在宣告週期內保持保持區域性變數的值,而不需要每次進入和離開作用域是建立和銷燬,因此使用static修飾區域性變數,可以函式呼叫間保持區域性變數的值
static也可以用於全域性變數,當static修飾全域性變數時,變數的作用域會限制在他的本檔案中,也就是隻有本檔案才可以訪問(普通的全域性變數,使用extern外部宣告後任何檔案都可以訪問)
static函式:和上方的全域性變數一樣(非靜態函式可以在另一個檔案中直接引用,甚至不必使用extern宣告)
extern
用於提供一個全域性變數的引用,全域性變數對所有的程式檔案都可見
當在A檔案定義一個全域性變數a,B檔案想要使用A檔案的變數a,可以在B檔案使用extern關鍵字拿到a的引用
第一個檔案
#include <stdio.h>
int count =5;
extern void add();
int main() {
add();
return 0;
}
複製程式碼
第二個檔案
#include <stdio.h>
extern int count;
void add(){
printf("count=%d\n",count);
}
複製程式碼
執行後
bogon:untitled renxiaohui$ gcc text.c tex1.c
bogon:untitled renxiaohui$ a.out
count=5
複製程式碼
6 運算子
同java,不在記錄
7 判斷
同java
8 迴圈
同java
9 函式
定義函式
return_type function_name( parameter list )
{
body of the function
}
複製程式碼
一個函式的組成部分
- 返回值型別:一個函式可以返回一個值,
return_type
是一個函式返回值的資料型別,有些函式不返回資料,這種情況下return_type
的關鍵字是void
- 函式名稱:函式的實際名稱
function_name
- 函式引數:當呼叫函式是,需要向函式傳遞一個值,就是引數,引數可以有多個,也可以沒有
- 函式主體:函式內實現邏輯的語句
int Max(int num1,int num2){
int result;
if(num1>num2){
result=num1;
}else{
result=num2;
}
return result;
}
複製程式碼
這就是一個函式的簡單例項
函式宣告
函式的宣告會告訴編譯器,函式的名稱和如何呼叫函式,函式的實際主體可以單獨定義
函式宣告包括以下幾個部分
return_type function_name( parameter list );
複製程式碼
針對上方的函式我們可以宣告
int Max(int num1,int num2);
複製程式碼
在函式宣告中,引數名稱並不是很重要,可以省略掉
int Max(int ,int);
複製程式碼
當你在一個原始檔定義一個函式,在另一個檔案呼叫這個函式時,函式宣告是必要的,這種情況下,你需要在呼叫函式檔案的頂部宣告函式
呼叫函式
#include <stdio.h>
int Max(int,int);
int main() {
int num1 = 100;
int num2 = 120;
int rec;
rec = Max(num1,num2);
printf("比較結果為%d\n",rec);
return 0;
}
int Max(int num1, int num2) {
int result;
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
複製程式碼
結果為
比較結果為120
複製程式碼
C的作用域規則
區域性變數
在某個函式塊內宣告的變數為區域性變數,他只能被該函式或者該程式碼塊內的函式語句使用,區域性變數外部是不可知的
#include <stdio.h>
int main ()
{
/* 區域性變數宣告 */
int a, b;
int c;
/* 實際初始化 */
a = 10;
b = 20;
c = a + b;
printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
return 0;
}
複製程式碼
這裡的a,b,c 都是區域性變數
全域性變數
全域性變數定義在函式的外部,通常是程式的頂部,全域性變數在整個程式的生命週期內都是有效的,在任意的函式內部可以訪問全域性變數。
全域性變數可以被任意函式訪問,也就是說全域性變數在宣告後在整個程式中都是可用的
區域性變數和全域性變數名稱可以相同,但是在函式內,如果倆個名字相,會使用區域性變數,不會使用全域性變數
#include <stdio.h>
//全域性變數
int a = 10;
int c = 40;
int main(){
//區域性變數
int a = 20;
int b =30;
printf("a=%d\n",a);
printf("b=%d\n",b);
printf("c=%d\n",c);
return 0;
}
複製程式碼
輸出
a=20
b=30
c=40
複製程式碼
全域性變數與區域性變數的區別
- 全域性變數儲存在記憶體的全域性儲存區,佔用靜態的儲存單元
- 區域性變數儲存在棧中,只有在函式被呼叫時,才動態的為變數分配儲存單元
10 陣列
宣告陣列
type arrayName [ arraySize ];
複製程式碼
這是一個一維陣列,arraySize必須是大於0的常量,type是任意有效的c資料型別,宣告一個含有10個double資料的nums陣列
double nums [10];
複製程式碼
初始化陣列
初始化固定數量的陣列
int nums [3] = {10,2,3}
複製程式碼
初始化數量不固定的陣列
int nums []={1,2,3,4,5}
複製程式碼
為某個陣列賦值
nums[3] = 6;
複製程式碼
訪問陣列元素
int a = num[3];
複製程式碼
例項
#include <stdio.h>
int main(){
int n[10];
int i,j;
for(i=0;i<10;i++){
n[i]=i*10;
}
for (int j = 0; j < 10; ++j) {
printf("%d = %d\n",j,n[j]);
}
}
複製程式碼
結果
0 = 0
1 = 10
2 = 20
3 = 30
4 = 40
5 = 50
6 = 60
7 = 70
8 = 80
9 = 90
複製程式碼
11 列舉
列舉是C語言中的一種基本資料型別
enum 列舉名 {列舉元素1,列舉元素2,……};
複製程式碼
假如我們定義一週七天,假如不用列舉,用常量
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
複製程式碼
假如用列舉
enum Day{
MON=1, TUE, WED, THU, FRI, SAT, SUN
}
複製程式碼
這樣看起來會很簡潔
注意:預設第一個成員變數為0,後面的順序加1,如果第一個是1,後面是2,以此類推
列舉變數的定義
1 先定義列舉型別在定義變數
enum DAY{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
複製程式碼
2 定義列舉型別的同時定義列舉變數
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
複製程式碼
3 省去列舉名稱直接定義列舉變數
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
複製程式碼
例項
#include <stdio.h>
enum DAY {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
int main() {
enum DAY day;
day=THU;
printf("enun= %d\n",day);
return 0;
}
複製程式碼
結果
enun= 4
複製程式碼
在C語言中,列舉是被當做int
或者 unsigned int
來處理的,所以按照C語言規範是沒有辦法遍歷列舉的
列舉在switch中使用
#include <stdio.h>
#include <stdlib.h>
enum Color {
red=1, green, blue
};
int main() {
enum Color enumColor;
printf("請選擇你喜歡的顏色(1 紅色 2 綠色 3 藍色)");
scanf("%d",&enumColor);
switch (enumColor){
case red:
printf("你喜歡紅色\n");
break;
case green:
printf("你喜歡綠色\n");
break;
case blue:
printf("你喜歡藍色\n");
break;
}
return 0;
}
複製程式碼
結果
請選擇你喜歡的顏色(1 紅色 2 綠色 3 藍色)1
你喜歡紅色
複製程式碼
12 指標
每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用&符號
訪問地址,他表示在記憶體中的一個地址
#include <stdio.h>
int main(){
int a ;
int b[10];
printf("a 的記憶體地址=%p\n",&a);
printf("b 的記憶體地址=%p\n",&b);
return 0;
}
複製程式碼
結果
a 的記憶體地址=0x7ffee5e086c8
b 的記憶體地址=0x7ffee5e086d0
複製程式碼
什麼是指標
指標是一個變數,其值為另一個變數的記憶體地址,使用指標之前需要對其進行宣告
type *var-name;
複製程式碼
type
是指標的型別,他必須是一個有效的C資料型別,var-name
是指標的名稱,*
用來表示這個變數是指標。
int *aa;//一個整形指標
double *bb;//一個double型別的指標
複製程式碼
如何使用指標
使用指標會頻繁的進行如下幾個操作,定義一個指標變數,把變數地址賦值給指標,訪問指標變數中的可用地址
#include <stdio.h>
int main(){
int a =20;//定義int值
int *b;//定義指標
b=&a;//為指標賦值
printf("變數的地址值=%p\n",&a);
printf("指標的值=%p\n",b);
printf("指標的地址值=%d\n",*b);
return 0;
}
複製程式碼
結果
變數的地址值=0x7ffeef4356f8
指標的值=0x7ffeef4356f8
指標的地址值=20
複製程式碼
C中的NULL指標
變數宣告的時候,如果沒有明確的地址可以賦值,為指標賦值一個NULL是一個良好的習慣,賦值為NULL被稱為空指標
int main(){
int *var = NULL;
printf("var的地址為=%p\n",var);
return 0;
}
複製程式碼
var的地址為=0x0
複製程式碼
指標指向指標
#include <stdio.h>
int main(){
int a;
int *b;
int **c;
a=40;
b=&a;
c=&b;
printf("a的值為=%d\n",a);
printf("b的值為=%d\n",*b);
printf("c的值為=%d\n",**c);
return 0;
}
複製程式碼
a的值為=40
b的值為=40
c的值為=40
複製程式碼
傳遞陣列給函式,函式返回陣列
#include <stdio.h>
int sum(int *arr,int size);
int* getarr();
int main() {
int a[4] = {1, 2, 3, 4};
int v = sum(a,4);
printf("sum=%d\n",v);
int *p = getarr();
for (int i = 0; i < 4; ++i) {
printf("陣列=%d\n",*(p+i));
printf("陣列=%d\n",p[i]);
}
return 0;
}
int sum(int *arr, int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
printf("i=%d\n", arr[i]);
printf("i=%d\n", *(arr+i));
sum+=arr[i];
}
return sum;
}
int * getarr(){
static int arr[4]={2,4,5,7};
return arr;
}
複製程式碼
i=1
i=1
i=2
i=2
i=3
i=3
i=4
i=4
sum=10
陣列=2
陣列=2
陣列=4
陣列=4
陣列=5
陣列=5
陣列=7
陣列=7
複製程式碼
指標運算
C指標用數字表示地址,因此可以進行算術運算,++,--,+,-等,假如prt是一個int型別的地址1000,那麼執行prt++,prt將指向1004,即當前位置移動4個位元組,假如prt是一個char型別的地址1000,那麼執行prt++,prt將指向1001,這個跟型別也是相關的
- 指標的每一次遞增,他實際上會指向下一個元素的儲存單元
- 指標的每一次遞減,他實際上指向上一個元素的儲存單元
- 指標在遞增和遞減跳躍的位元組數,取決於指標所指向變數的資料型別的長度,比如int就是4個位元組
遞增一個指標
#include <stdio.h>
const int Max = 3;
int main() {
int arr[3] = {1, 2, 3};
int i ,*p;
//給p指標賦值陣列中第一個元素的地址
p = arr;
for (int i = 0; i < Max; ++i) {
printf("元素的地址=%p\n", p);
printf("元素的地址=%d\n", *p);
//移動到下個位置
p++;
}
return 0;
}
複製程式碼
元素的地址=0x7ffee165b6ac
元素的地址=1
元素的地址=0x7ffee165b6b0
元素的地址=2
元素的地址=0x7ffee165b6b4
元素的地址=3
複製程式碼
指標陣列
有一種情況,我們陣列內可以儲存記憶體地址值
#include <stdio.h>
const int Max= 3;
int main(){
int arr[3]={1,2,3};
int *p[Max];
for (int i = 0; i <Max ; ++i) {
p[i]=&arr[i];
}
for (int j = 0; j < Max; ++j) {
printf("指標陣列資料=%p\n",p[j]);
printf("指標陣列資料值=%d\n",*p[j]);
}
return 0;
}
複製程式碼
指標陣列資料=0x7ffee7cda6ac
指標陣列資料值=1
指標陣列資料=0x7ffee7cda6b0
指標陣列資料值=2
指標陣列資料=0x7ffee7cda6b4
指標陣列資料值=3
複製程式碼
指向指標的指標
指標也可以指向指標
#include <stdio.h>
int main(){
int a = 10;
int *b;
int **c;
b=&a;
c=&b;
printf("a的值為=%d\n",a);
printf("b的值為=%d\n",*b);
printf("c的值為=%d\n",**c);
}
複製程式碼
a的值為=10
b的值為=10
c的值為=10
複製程式碼
傳遞指標給函式
#include <stdio.h>
#include <time.h>
void getlong(unsigned long *a);
int main() {
unsigned long b;
getlong(&b);
printf("b的值為=%ld\n",b);
}
void getlong(unsigned long *a) {
*a = time(NULL);
return;
}
複製程式碼
b的值為=1585048748
複製程式碼
13 函式指標和回撥函式
函式指標是指向函式的指標變數,函式指標可以向普通函式一樣,傳遞引數,呼叫函式
函式返回值型別 (* 指標變數名) (函式引數列表);
複製程式碼
#include <stdio.h>
int max(int, int);
int main() {
//p是函式指標
int (*p)(int, int) = &max;//&可以省略
int a, b, c, d;
printf("請輸入三個數字:");
scanf("%d,%d,%d", &a, &b, &c);
d = p(p(a, b), c);//相當於呼叫max(max(a,b),c);
printf("d的值為=%d\n", d);
return 0;
}
int max(int a, int b) {
return a > b ? a : b;
}
複製程式碼
輸出
請輸入三個數字:1,2,3
d的值為=3
複製程式碼
將指標函式當做引數傳遞給函式
#include <stdio.h>
#include <stdlib.h>
void setvalue(int *arr, int b, int(*p)(void)) {
for (int i = 0; i < b; ++i) {
arr[i] = p();
}
}
int stett(int(*p)(void)){
return p();
}
int getvalue(void) {
return rand();
}
int main() {
int arr[10];
setvalue(arr, 10, getvalue);
for (int i = 0; i < 10; ++i) {
printf("i=%d\n", arr[i]);
}
int b;
b= stett(getvalue);
printf("b=%d\n", b);
return 0;
}
複製程式碼
結果
i=16807
i=282475249
i=1622650073
i=984943658
i=1144108930
i=470211272
i=101027544
i=1457850878
i=1458777923
i=2007237709
b=823564440
複製程式碼
14 字串
C語言定義字串Hello,兩種形式,字串和字元陣列的區別:最後一位是否是空字元
#include <stdio.h>
int main(){
char hello[6]= {'H','e','l','l','o','\0'};
char hello1[]= "hello";
char *hello2="hello";
printf("測試=%s\n",hello);
printf("測試=%s\n",hello1);
printf("測試=%s\n",hello2);
return 0;
}
複製程式碼
測試=Hello
測試=hello
測試=hello
複製程式碼
15 結構體
結構體可以儲存不同型別的資料項
定義一個結構體
struct tag {
member-list
member-list
member-list
...
} variable-list ;
複製程式碼
- tag:結構體標籤
- member-list:結構體資料項
- variable-list:結構體變數
struct Book {
int book_id ;
char title[50];
char author[50];
char subject[50];
} book;
複製程式碼
在一般情況下,tag、member-list、variable-list 這 3 部分至少要出現 2 個。以下為例項:
//此宣告宣告瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//同時又宣告瞭結構體變數s1
//這個結構體並沒有標明其標籤
struct
{
int a;
char b;
double c;
} s1;
//此宣告宣告瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//結構體的標籤被命名為SIMPLE,沒有宣告變數
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE標籤的結構體,另外宣告瞭變數t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef建立新型別
typedef struct
{
int a;
char b;
double c;
} Simple2;
//現在可以用Simple2作為型別宣告新的結構體變數
Simple2 u1, u2[20], *u3;
複製程式碼
結構體中可以含有其他結構體和指標
//結構體包含其他結構體
struct AA{
int a;
struct Book b;
};
//結構體包含自己的指標
struct BB{
int b;
struct BB *nextbb
};
複製程式碼
結構體的初始化
struct Book {
int book_id ;
char title[50];
char author[50];
char subject[50];
} book= {1,"c語言","小明","bb"};
複製程式碼
title : c語言
author: 小明
subject: bb
book_id: 1
複製程式碼
訪問結構體的成員
訪問結構體的成員可以用.
符號,比如上方的book.title;
int main(){
struct Book book1;
strcpy(book1.title,"學習書");
strcpy(book1.author,"小紅");
strcpy(book1.subject,"111");
book1.book_id=222;
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);
return 0;
}
複製程式碼
title : 學習書
author: 小紅
subject: 111
book_id: 222
複製程式碼
結構體作為函式的引數
void printstuct(struct Book book);
int main(){
struct Book book1;
strcpy(book1.title,"學習書");
strcpy(book1.author,"小紅");
strcpy(book1.subject,"111");
book1.book_id=222;
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);
printstuct(book1);
return 0;
}
void printstuct(struct Book book){
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
複製程式碼
title : 學習書
author: 小紅
subject: 111
book_id: 222
Book title : 學習書
Book author : 小紅
Book subject : 111
Book book_id : 222
複製程式碼
指向結構體的指標
定義,賦值,呼叫
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
複製程式碼
int main() {
struct Book book1;
strcpy(book1.title, "學習書");
strcpy(book1.author, "小紅");
strcpy(book1.subject, "111");
book1.book_id = 222;
printstuct1(&book1);
return 0;
}
void printstuct1(struct Book *book) {
printf("Book title : %s\n", book->title);
printf("Book author : %s\n", book->author);
printf("Book subject : %s\n", book->subject);
printf("Book book_id : %d\n", book->book_id);
}
複製程式碼
Book title : 學習書
Book author : 小紅
Book subject : 111
Book book_id : 222
複製程式碼
16 共用體
共用體是一個特殊的資料型別,允許在相同的儲存位置,儲存不同的資料型別,可以定義一個帶有多個成員的共用體,但是任何時候只能一個成員帶值
定義共用體
用union
定義共用體,
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
複製程式碼
union tag
是可選的,每個member definition;
都是標準的變數定義,如int i char b等,在分號之前可以定義一個或多個共用體變數是可選的
定義一個成員有int,float,char[]的共用體
#include <stdio.h>
#include <string.h>
union Data {
int a;
char b[100];
float c;
};
int main() {
union Data data;
printf("資料長度=%lu\n", sizeof(data));
data.a = 1;
data.c = 10.00;
strcpy(data.b, "測試資料");
printf("資料%d\n",data.a);
printf("資料%f\n",data.c);
printf("資料%s\n",data.b);
return 0;
}
複製程式碼
資料長度=100
資料-393497114
資料-5278115000342806695772160.000000
資料測試資料
複製程式碼
我們看到資料a,c成員的值有損壞,是因為最後賦值的變數佔用了的記憶體位置,這也是b變數可以正確輸出的原因
我們同一時間只能使用一個變數
#include <stdio.h>
#include <string.h>
union Data {
int a;
char b[100];
float c;
};
int main() {
union Data data;
printf("資料長度=%lu\n", sizeof(data));
data.a = 1;
printf("資料%d\n",data.a);
data.c = 10.00;
printf("資料%f\n",data.c);
strcpy(data.b, "測試資料");
printf("資料%s\n",data.b);
return 0;
}
複製程式碼
資料長度=100
資料1
資料10.000000
資料測試資料
複製程式碼
在這裡所有的成員都可以正確輸出,是因為同一時間只用到了一個變數
17 typedef
C語言提供typedef
關鍵字,可以使用它為型別起一個新的名字
#include <stdio.h>
typedef unsigned int TEXT;
int main(){
TEXT a = 11;
printf("引數為=%d\n",a);
return 0;
}
複製程式碼
引數為=11
複製程式碼
也可以為使用者自定義的資料型別去一個新的名字,比如結構體
#include <stdio.h>
#include <string.h>
typedef unsigned int TEXT;
typedef struct BOOKS {
char a[50];
char b[50];
} Book;
int main() {
TEXT a = 11;
printf("引數為=%d\n", a);
Book book;
strcpy(book.a, "測試1");
strcpy(book.b, "測試2");
printf("a=%s\n", book.a);
printf("b=%s\n", book.b);
return 0;
}
複製程式碼
引數為=11
a=測試1
b=測試2
複製程式碼
typedef和#define 的區別
#define
是一個C指令,用於為各種資料型別定義別名,與typedef
類似,但是他有以下幾種不同
typede
f僅限於為型別定義符號名稱,#define
不僅為型別定義別名,也可以為數值定義別名typedef
為編譯器解釋執行,#define
為預編譯器進行處理
#define TRUE 0
#define FALSE 1
int main() {
printf("數值為=%d\n", TRUE);
printf("數值為=%d\n", FALSE);
return 0;
}
複製程式碼
數值為=0
數值為=1
複製程式碼
18 輸入和輸出
getchar() & putchar() 函式
int getchar(void)
函式從螢幕讀取下一個可用的字元,並把它返回為一個整數。這個函式在同一個時間內只會讀取一個單一的字元。您可以在迴圈內使用這個方法,以便從螢幕上讀取多個字元。
int putchar(int c)
函式把字元輸出到螢幕上,並返回相同的字元。這個函式在同一個時間內只會輸出一個單一的字元。您可以在迴圈內使用這個方法,以便在螢幕上輸出多個字元。
#include <stdio.h>
int main(){
printf("請輸入一個字元\n");
int a = getchar();
printf("你的輸入為\n");
putchar(a);
printf("\n");
}
複製程式碼
請輸入一個字元
text
你的輸入為
t
複製程式碼
gets() & puts() 函式
char *gets(char *s)
函式從stdin讀取一行到s指向的快取區,直到一個終止符或一個EOF
int puts(const char *s)
函式吧一個字串s和一個尾隨的換行符寫入stdout
#include <stdio.h>
int main(){
char a[100];
printf("輸入你的字元\n");
gets(a);
printf("你輸入的字元為\n");
puts(a);
return 0;
}
複製程式碼
輸入你的字元
text
你輸入的字元為
text
複製程式碼
scanf() 和 printf() 函式
int scanf(const char *format, ...)
函式從標準輸入流 stdin 讀取輸入,並根據提供的 format 來瀏覽輸入。
int printf(const char *format, ...)
函式把輸出寫入到標準輸出流 stdout ,並根據提供的格式產生輸出。
format 是一個簡單的常量字串,但是你可以定義%s,%d,%c,%f來讀取字串,數字,字元,浮點數
#include <stdio.h>
int main() {
char a[100];
int b;
printf("輸入文字\n");
scanf("%s%d", a, &b);
printf("你輸入的文字=%s,%d\n",a,b);
return 0;
}
複製程式碼
輸入文字
text 123
你輸入的文字=text,123
複製程式碼
19 檔案讀寫
#include <stdio.h>
int main() {
FILE *file;
//開啟檔案允許讀寫,如果不存在則建立該檔案
file = fopen("/Users/renxiaohui/Desktop/test.txt", "w+");
//寫入一行
fprintf(file, "this is a text\n");
//再寫入一行
fputs("this is a text aaaa\n", file);
fclose(file);
}
複製程式碼
會在建立檔案test.txt,並寫入倆行文字
#include <stdio.h>
int main() {
FILE *file1;
char a[255];
//開啟一個檔案,只讀
file1 = fopen("/Users/renxiaohui/Desktop/test.txt", "r");
//讀取檔案,當遇到第一個空格或者換行符會停止讀取
fscanf(file1, "%s", a);
printf("1= %s\n", a);
//讀取字串到緩衝區,遇到換行符或檔案的末尾 EOF返回
fgets(a, 255, file1);
printf("2= %s\n", a);
fgets(a, 255, file1);
printf("3= %s\n", a);
//關閉檔案
fclose(file1);
}
複製程式碼
讀取剛才建立的檔案
1= this
2= is a text
3= this is a text aaaa
複製程式碼
20 前處理器
C前處理器不是編譯器的組成部分,他是編譯過程中的一個單獨步驟,他們會在編譯器實際編譯之前完成所需的預處理
所有的前處理器都是以(#)開頭的,他必須是第一個非空字元,為了增強可讀性,前處理器應該從第一列開始,下面列出比較重要的前處理器
指令 | 描述 |
---|---|
#define | 定義巨集 |
#undef | 取消定義的巨集 |
#include | 包含一個原始檔程式碼 |
#ifdef | 如果巨集已經定義則返回真 |
#ifndef | 如果巨集沒有定義則返回真 |
#if | 如果給定條件為真則編譯一下程式碼 |
#else | #if的替代方案 |
#elseif | 如果前面的 #if 給定條件不為真,當前條件為真,則編譯下面程式碼 |
#endif | 結束一個#if。。#else 語句 |
#error | 遇到標準錯誤是,輸出錯誤訊息 |
#pragma | 使用標準化方法,向編譯器釋出特殊指令到編譯器中去 |
預編譯器例項
#define MAX 20
複製程式碼
這個指令表示,把所有的MAX定義為20,使用#define定義常量可以增加可讀性
#include <stdio.h>
#include "myheader.h"
複製程式碼
第一個表示從系統庫獲取stdio.h
庫,並新增到原始檔中,一個是從本地目錄獲取myheader.h
,並新增到原始檔中
#undef FILE
#define FILE 99
複製程式碼
取消已經定義的FILE,並把它重新定義為99
#ifdef MESSAGE
#define MESSAGE "you wish"
#endif
複製程式碼
這個表示只有MESSAGE未定義時,才定義MESSAGE為you wish
預定義巨集
ANSI C中預定義巨集,我們可以在程式設計中使用這些巨集,但不可以改變他的值
巨集 | 描述 |
---|---|
1__DATE__ | 當前日期,一個以 "MMM DD YYYY" 格式表示的字元常量。 |
1__TIME__ | 當前時間,一個以 "HH:MM:SS" 格式表示的字元常量。 |
1__FILE__ | 這會包含當前檔名,一個字串常量。 |
1__LINE__ | 這會包含當前行號,一個十進位制常量。 |
1__STDC__ | 當編譯器以 ANSI 標準編譯時,則定義為 1。 |
看下使用
#include <stdio.h>
int main() {
printf("data=%s\n",__DATE__);
printf("file=%s\n",__FILE__);
printf("time=%s\n",__TIME__);
printf("line=%d\n",__LINE__);
printf("stdic=%d\n",__STDC__);
return 0;
}
複製程式碼
data=Mar 31 2020
file=text27.c
time=15:31:49
line=19
stdic=1
複製程式碼
引數化巨集
引數化巨集可以模擬函式,例如下面是一個計算數字平方的方法
int square(int x) {
return x * x;
}
複製程式碼
可以用引數化巨集來表示
#define square(x) ((x)*(x))
複製程式碼
在使用引數化巨集之前必須使用#define來定義,引數列表必須包含在圓括號內的,並且必須緊跟在巨集名稱之後,不允許有空格
#define square(x) ((x)*(x))
int main() {
printf("平方為=%d\n",square(5));
return 0;
}
複製程式碼
平方為=25
複製程式碼
預處理運算子
巨集延續運算子(\)
一個巨集通常寫在一個單行上,但是如果巨集太長,一個單行容不下,則使用巨集延續運算子(\)例如
#define message_for(a,b) \
printf(#a"and "#b"we are friend\n")
複製程式碼
字串常量化運算子(#)
在巨集定義中,需要把一個巨集的引數轉化為字串常量時,則使用字串常量化運算子(#)例如:
#define message_for(a,b) \
printf(#a"and "#b"we are friend\n")
int main() {
message_for("xiaoming","xiaohong");
return 0;
}
複製程式碼
"xiaoming"and "xiaohong"we are friend
複製程式碼
** 標記貼上運算子(##)**
直接看程式碼比較
#define add(n) printf("token" #n " = %d\n",token##n)
int main() {
int token34 =40;
add(34);
return 0;
}
複製程式碼
token34 = 40
複製程式碼
這個是怎麼發生的,因為這個實際會從編譯器中產出以下實際輸出
printf ("token34 = %d", token34);
複製程式碼
defined()運算子
前處理器defined運算子是在常量表示式中的,用來確定一個識別符號是否已經#define定義過,如果定義過為真,如果沒有定義過值為假
#if !defined(TEXT)
#define TEXT "text\n"
#endif
int main() {
printf(TEXT);
return 0;
}
複製程式碼
text
複製程式碼