C語言學習筆記–基礎語法三
1、共用體與位域
共用體,是一種特殊的數據類型,允許在相同的內存位置,存儲不同的數據類型。可以定義一個帶有多個成員的共用體,但是使用的時候,只能有一個成員有效的擁有數值。
共用體提供了一種使用相同內存的有效方式。
定義共用體
union
關鍵字,類似與結構體的定義。123456union [union tag]{member definition;member definition;...member definition;}[one or more union variables];union tag爲可選項,類似結構體的定義,可以先聲明,也可以聲明並定義。
12345678910111213141516//這個共用體,聲明了一個Data共用體,它的變量data,以及包含了三種數據類型,i、f、str[20],共用一個內存地址,這塊內存大小由共用體內最大成員決定。此處就是char類型的str,有20個字節位置。union Data {int i;float f;char str[20];};//main()函數void main(){//聲明共用體變量。union Data data;//顯示共用體內存佔用大小printf("data的內存佔用:%d \n",sizeof(data));}//編譯運行,結果會顯示爲20共用體成員的訪問也類似與結構體,使用
.
符號。123456789101112131415161718192021222324union Data {int i;float f;char str[20];};void main(){union Data data;//賦值data.i = 10;data.f = 220.5;strcpy(data.str,"C Programming");//輸出printf("data.i: %d \n",data.i);printf("data.f: %f \n",data.f);printf("data.str: %s \n",data.str);//這樣的輸出結果,只有最後一次會輸出正確的結果,上兩次都會數據損壞,因爲共用體只有一個可用內存變量。//如果每次賦值後都輸出一下,是沒問題的,或者同時使用同一個成員變量,可以更改變量值,也是可行的。data.i = 20;printf("data.i: %d \n",data.i);data.i = 99;printf("data.i %d \n",data.i);}位域
位域雖然上一節也有講,但是不清楚,這麼理解吧,位域、結構體、共用體三個概念比較理解,結構體和位域用
struct
,而共用體用union
關鍵字| 名稱 | 是否共用內存 | 備注 |
| —- | ———————————– | —————————————- |
| 結構體 | 內部成員各自有自己聲明的內存空間 | 看作JAVA語言中的一個bean實體類 |
| 共用體 | 內部成員共享內存地址和空間,最大的決定整體空間大小 | 類似與槍支,可能適配多個型號的子彈,但是每一次開槍,槍管裏就只能一個類型的子彈(姑且這麼比喻吧) |
| 位域 | 類似共用體,但是算作特殊的結構體,可以單個指定成員佔用的內存二進制位數 | 算是一個只有一種類型成員的共用體,暫且這麼理解。 |示例:
123456789101112131415161718//定義簡單的結構體struct{unsigned int widthValidated;unsigned int heightValidated;} status1;//定義位域struct {unsigned int widthValidated : 1;unsigned int heightValidated :1;} status2;//main()void main(){printf("status1佔用內存:%d \n",sizeof(status1));printf("status2佔用內存:%d \n",sizeof(status2));}//結果會分別是8位和4位,因爲結構體算是兩個成員獨立佔用,而位域,算是兩個成員共用,而且雖然佔用4個字節,但是數據只用了1位,若是int widthValidated : 5; 那麼就會是8位了吧,位域不恩那個超出的,也不太清楚了,以後再回來看位域的聲明
1234567struct {/*type爲整數類型,決定了如何解釋位域的值,member_name爲位域的名稱,width爲位域中位的數量,不能大於制定類型的位寬度,不如int,就不能大於4個字節。*/type [member_name] : width;}位域指定了變量的位數,也就限制了數值的範圍,超出後會無法正確完成
|
|
typedef
typedef
C語言提供的一個用於給類型起新名字的關鍵字。類似與linux系統alias
命令,就是用於自定義別名的一個關鍵字指令。1234//定義unsigned char 爲BYTEtypedef unsigned char BYTE;//那麼就可以用來聲明變量BYTE b1;//就相當與unsigned char b1;習慣性的將
typedef
別名化的類型寫作大寫,當然也可以小寫。可以作用與基本數據類型,也可以作用與結構體、共用體、位域之類的自定義類型。typedef 和 #define
#define
是C指令,用於定義數據類型的別名,類似typedef
- typedef僅用於爲類型定義別名,而#define也可以定義數值的別名,如1可以定義爲ONE
- typedef由編譯器執行解釋,#define由預編譯器處理
12345678//定義void main(){printf("TURE:%d,TWO:%d \n",TURE,TWO);}I/O
C語言提供了一系列的內置函數用於輸入和輸出操作。
標準文件
C語言把左右設備都當作文件處理,類似與linux下,一切皆文件。
| 標準文件 | 文件指針 | 設備 |
| —- | —— | —- |
| 標準輸入 | stdin | 鍵盤 |
| 標準輸出 | stdout | 屏幕 |
| 標準錯誤 | stderr | 您的屏幕 |文件指針是訪問文件的方式。
getchar()和putchar()函數
int getchar(void)
函數從屏幕讀取下一個可用字符,一次讀一個,可以房子循環裏面使用。int putchar(Int c)
函數向屏幕輸出字符,一次輸一個,循環使用。123456789>> void main(){> int c;> printf("請輸入字符:");> c = getchar();> printf("\n 您輸入的字符是:");> putchar(c);> }>
>
gets()和puts()
char *gets(char *s)函數從
stdin
讀取一行到s
所指向的緩衝區,一知道遇到終止符或者EOF
。int puts(const char *s)函數把字符串s和一個尾隨的換行符寫入到
stdout
。123456789>> void main(){> char str[100];//緩存區域> printf("Enter a value:");> gets(str);> printf("\n Your value:");> puts(str);> }>
>
scanf()和printf()
int scanf(const char \*format,...)
函數從標準輸入流stdin
讀取輸入,根據fromat
來瀏覽輸入。int printf(const char \*format,...)
函數吧輸出以format
格式顯示,可以是%s、%d、%c、%f
等。
Note:
scanf()
讀取輸入,需要輸入的數據格式跟format
的一樣才行,不然會報錯。遇到空格會停止讀取,便認爲結束了一個字符的讀取。文件讀寫
- 打開文件,
fopen()
函數來創建一個新的,或者打開已有的文件。
1FILE *fopen(const char * filename,const char * mode);其中
finename
是字符串,命名文件用的。mode
是讀寫模式| mode | description |
| —- | ———————————— |
| r | 打開一個已有的文本文件,允許讀取文件 |
| w | 打開一個文本文件,允許寫入文件。若不存在,會新建文件,從頭開始寫起 |
| a | 打開一個文本文件,追加模式寫入,不存在則新建 |
| r+ | 打開一個文本文件,允許讀寫文件 |
| w+ | 打開一個文本文件,允許讀寫。若已存在,文件會被截斷爲另長度,不存在則新建 |
| a+ | 打開一個文本文件,允許讀寫。不存在則新建,讀取從頭開始,寫入則是追加模式 |若是處理的是二進制文件,寫法有點差異,多個
b
字符1"rb","wb","ab","rb+","r+b","wb+","w+b","ab+","a+b"- 關閉文件,
fclose()
關閉文件的函數
int fclose(FILE *fp);
若關閉成功,返回0
,關閉失敗,返回EOF
(定義在stdio.h
中的常量)寫入文件
fputc()
函數,將c的字符,寫入到fp的輸出流,成功則返回寫入的字符,失敗返回EOF。1int fputc(int c,FILE *fp);fputc()
函數也可以吧字符串s寫入到fp的輸出流中,成功返回非負值,失敗返回EOF。12int fputs(const char *s,FILE *fp);int fpuintf(FILE *fp,const char *format,...);示例,會在當前文件夾下,生成text文檔,含有兩句話。
12345678void main(){FILE *fp;fp = fopen("./text.txt","w+");fprintf(fp,"This is testing for fprintf...\n");fputs("這是測試fputs輸入\n",fp);fclose(fp);}讀取文件
fgetc
和fgets
函數123456//讀取一個字符,正確返回該字符,錯誤返回EOFint fgetc(FILE * fp);//讀取字符串流,直到讀取到null的標識符,所以之前讀取了有效的n-1個,要是遇到\n或者EOF則會返回讀取的字符。char *fgets(char *buf,int n,FILE *fp);//從文件中讀取,遇到空格會停止int fscanf(FILE *fp,const char *format,...);示例,讀取上面的文件
12345678910111213141516void main(){FILE *fp;char buff[255];fp = fopen("./text.txt","r");//只會讀取第一個單詞,因爲遇到空格了fscanf(fp,"%s",buff);printf("1: %s\n",buff);//會讀取一句話,遇到\n或者EOF結束fgets(buff,255,(FILE*)fp);printf("2: %s\n",buff);fgets(buff,255,(FILE*)fp);printf("3: %s\n",buff);fclose(fp);}首先
fscanf
只讀取了This
,因爲它遇到了空格,之後調用fgets
讀取剩餘部分,知道遇到\n
或者EOF,而第二次調用fgets
讀取了一整句。二進制讀寫函數
12size_t fread(void *ptr,size_t size_of_elements,size_t number_of_elements,FILE *a_file);size_t fwrite(const void *ptr,size_t size_of_elements,size_t size_of_elements,FILE *a_file);常用與存儲塊的讀寫,通常數組或結構體
- 打開文件,
2、預處理器
預處理器不是編譯器的組成部分,其會在編譯器實際編譯之前,指示編譯器做一些預處理工作。C與處理器(C Preprocessor)簡稱CPP
,以#
開頭,位於行首,文件首。
指令 | 描述 |
---|---|
#define | 定義宏 |
#include | 包含一個源代碼文件 |
#undef | 取消已定義的宏 |
#ifdef | 如果定義了宏,返回真 |
#ifndef | 如果沒有定義宏,返回真 |
#if | 條件語句,滿足條件,則執行下面代碼 |
#else | 與if搭配使用 |
#elif | 也就是else if語句快 |
#endif | 結束一個#if…#else語句塊 |
#error | 遇到標準錯誤時,輸出錯誤 |
#pragma | 使用標準化方法,向編譯器發布特殊指令 |
示例:
|
|
預定義宏
ANSI C定義了許多宏,但是不能直接修改
| 宏 | 描述 |
| ——– | ———————- |
| _DATE_ | 當前日期,”MMM DD YYYY”格式顯示 |
| _TIME_ | 當前時間,”HH:MM:SS”格式顯示 |
| _FILE_ | 這會包含當前文件名,一個字符串常量 |
| _LINE_ | 這會包含當前行號,一個十進制常量 |
| _STDC_ | 當編譯器以ANSI標準編譯時,則定義爲1 |預處理器運算符
宏延伸
\
一個宏通常寫在一行,若是太長,可以用
\
符號延伸12printf(#a#b );字符串常量化
#
定義宏時候,參數轉化爲字符常量,需要用
#
123456printf(#a //此處就是用了#符號,#b );void main(){msg("小白","小黑");}輸出結果:
1小白和小黑是朋友標記粘貼
##
合並兩個參數
123456void main(){int tocken22 = 80;paster(22);//這裏輸入22,在paster函數內合並參數,就得到了token22,然後輸出token22這個變量,就得到了下面的結果,分寫一下printf函數,字符token+#n,也就是token22,token##n,代表了上面定義int token22}輸出結果:
1token22 = 80可以判斷定義
1234567void main(){printf("msg == %s\n",MSG);}參數化的宏
CPP可以使用參數化的宏來模擬函數
123456//求平方的函數int square(int x){return x * x;}//使用宏來定義,名稱和參數的括號之間不能有空格,且緊跟#define然後就可以在源文件中使用
12345void main(){printf("Max between 20 and 10 is : %d\n",MAX(20,10));}
頭文件
.h
文件,包含了C函數聲明和宏定義,被多個源文件共享。分爲編譯器自帶和程序員自定義兩類。用#include
導入。1234//<>包裹的,是系統的.h文件,""包裹的,是程序員自定義的.h文件//都可以通過編譯源碼時候,-l選項,將文件置於列表前。其實,頭文件,就相當與復制,類似與java中的導入包和文件類。導入一次就夠了,不用多次導入。一般需要判斷一下
1234...有條件引用
123456//太多的話,可以宏定義一下
3、類型轉換
編程語言中常見的數據類型轉換,從低類型轉高類型,從高類型轉低類型。向下轉換不安全的,會丟失數據精度。
|
|
整數提升
其實也就是一種類型轉換,將小範圍類型,轉爲大範圍的,char–int
1234char i = 'c';//ascii碼 99int a = 17;int sum = i + c;//得到的sum值,就是int的,116常用轉換
一般都會隱式的向上轉型,向下轉型會丟失精度,且需要強制轉型。
這並不適合
賦值運算符
、邏輯運算符的&&
、||
錯誤處理
C語言不提供對錯誤的處理,其返回值出錯時多是1或者NULL,同時生成錯誤碼errno,可以在
<error.h>
中找到各種錯誤碼。通常通過返回值查找錯誤。一般初始化設這errno=0;errno、perror()和strerror(),C語言提供了兩個函數來顯示errno的錯誤信息。
- perror()函數顯示您傳送的字符串,後面一個冒號、一個空格、和當前errno的文本描述
- strerror()函數返回一個指針,指針指向errno的錯誤描述。
示例:
1234567891011121314151617181920extern int errno;int main(){FILE * pf;int errnum;//以二進制形式打開文件,此處文件不存在,用於模擬錯誤pf = fopen("./no.txt","rb");if(pf==NULL){errnum = errno;fprintf(stderr,"錯誤號: %d \n",errno);perror("通過perror函數輸出錯誤");fprintf(stderr,"strerror函數顯示,打開文件錯誤: %s \n",strerror(errnum));}else{fclose(pf);}return 0;}錯誤種類很多,非法參數,數組越界之類的。程序正常退出時候會帶有一個EXIT_SUCCESS值,其爲宏定義,爲0。錯誤時候EXIT_FAILURE,爲-1。