概述
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
10 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
全局變量與局部變量的區別
- 全局變量保存在內存的全局儲存區,佔用靜態的儲存單元
- 局部變量保存在棧中,只有在函數被調用時,才動態的爲變量分配儲存單元
11 數組
聲明數組
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
12 枚舉
枚舉是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
你喜歡紅色
13 指針
每一個變量都有一個內存位置,每一個內存位置都定義了可使用&符號
訪問地址,他表示在內存中的一個地址
#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
14 函數指針和回調函數
函數指針是指向函數的指針變量,函數指針可以向普通函數一樣,傳遞參數,調用函數
函數返回值類型 (* 指針變量名) (函數參數列表);
#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
15 字符串
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
16結構體
結構體可以存儲不同類型的數據項
定義一個結構體
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
17 共用體
共用體是一個特殊的數據類型,允許在相同的儲存位置,儲存不同的數據類型,可以定義一個帶有多個成員的共用體,但是任何時候只能一個成員帶值
定義共用體
用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
數據測試數據
在這裏所有的成員都可以正確輸出,是因爲同一時間只用到了一個變量
18 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
19 輸入和輸出
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
20 文件讀寫
#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
21 預處理器
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
22 頭文件
頭文件是後綴名爲.h
的文件,包含了函數聲明和宏定義,有倆種頭文件系統頭文件和c程序員自己編寫的頭文件
在C程序中建議把所有的常量,宏,系統全局變量,和函數原型寫入頭文件中
引入頭文件
使用預處理命令#include
引入頭文件,有倆種類型
#include <file>
這種形式用於引用系統頭文件,他在系統目錄搜索頭文件
#include "file"
這種形式用於引用用戶頭文件,他在當前的文件目錄搜索頭文件
只引用一次頭文件
如果一個頭文件引用被引用倆次,編譯器會處理倆次頭文件的內容,這將產生錯誤,爲了防止這種情況,標準的做法
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
如果沒有定義HEADER_FILE
則定義引入頭文件,如果下次在引用因爲HEADER_FILE
已經定義則不會再次引入
關於.h和.c的區別請看這篇文章
https://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
23 錯誤處理
當C語言發生錯誤時,大多數的C會返回1或NULL,同時會設置一個錯誤代碼errno
,改錯誤代碼是全局變量,表示執行函數期間發生了錯誤,可以在 errno.h
頭文件中找到各種錯誤代碼
所以程序員可以通過返回值來判斷是否有錯誤,開發人員在初始化時吧errno
初始化爲0,是一個良好的編程習慣,0表示沒有錯誤
errno、perror() 和 strerror()
C語言提供了perror() 和 strerror()來顯示errno相關的文本信息
perror()
函數:他將顯示你傳入的文字後面跟一個冒號和來errno
相關的文本信息strerror()
函數:返回一個指針,指向當前 errno 值的文本表示形式。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *pf;
int errnum;
pf = fopen("aaa.txt", "rb");
if (pf==NULL){
//stderr,輸出到屏幕
printf("錯誤號爲%d\n",errno);
fprintf(stderr,"錯誤爲%s\n",strerror(errno));
perror("perror顯示錯誤");
}
return 0;
}
錯誤號爲2
錯誤爲No such file or directory
perror顯示錯誤: No such file or directory
24 內存管理
C語言爲內存的分配提供了幾個函數,在<stdlib.h>文件頭中可以找到
函數 | 描述 |
---|---|
void *calloc(int num, int size); | 在內存中動態的分配num個長度爲size的連續空間,並將每一個字節初始化爲0 |
void free(void *address); | 該函數釋放address所指向的內存塊,釋放的是動態分配的內存空間 |
void *malloc(int num); | 在堆區分配一塊指定大小的內存空間,用來儲存數據,該內存空間在函數執行完之後不會初始化,他們的值是位置的 |
void *realloc(void *address, int newsize); | 該函數重新分配內存,新內存分配到newsize大小 |
注意:void* 表示未確定類型的指針,void*類型指針可以通過強制轉換轉換爲任意其他類型的指針
動態分配內存
編程時如果你預先知道數組的大小,那麼定義數組時會比較容易,比如一個可以容納100個字符的數組
char name[100];
但是如果不知道需要儲存文本長度的,那就需要動態分配內存了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char a[100];
char *b;
strcpy(a, "固定數組100");
//動態分配內存
b = (char *) malloc(200 * sizeof(char));
// b= calloc(200, sizeof(char));
if (b == NULL) {
printf("分配內存失敗");
} else {
strcpy(b, "動態分配內存成功");
}
printf("值爲=%s\n",b);
//重新分配內存
b = realloc(b, 500 * sizeof(char));
if (b == NULL) {
printf("分配內存失敗");
} else {
strcpy(b, "重新分配內存成功");
}
printf("值爲=%s\n",b);
//釋放內存
free(b);
return 0;
}
值爲=動態分配內存成功
值爲=重新分配內存成功
上面分配內存的代碼也可以替換爲
b= calloc(200, sizeof(char));
25 命令行參數
執行程序時可以從命令行傳遞參數給程序,這些值爲命令行參數
,命令行參數是通過main函數參數來處理的,其中,argc 是指傳入參數的個數,argv[] 是一個指針數組,指向傳遞給程序的每個參數
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc == 2) {
printf("參數爲 %s\n", argv[1]);
} else if (argc > 2) {
printf("參數超過倆個\n");
} else {
printf("只有一個參數\n");
}
return 0;
}
L-96FCG8WP-1504:untitled renxiaohui$ gcc text30.c
L-96FCG8WP-1504:untitled renxiaohui$ a.out text
參數爲 text
參考
https://www.runoob.com/cprogramming/c-tutorial.html