C語(yǔ)言知識(shí)點(diǎn)(上)

1.源碼文件如何變成可執(zhí)行文件(*)

需要以下4個(gè)步驟:
(1)預(yù)處理階段:預(yù)處理器根據(jù)以#開(kāi)頭的指令,修改主要包括#include、#define和條件編譯三個(gè)方面,修改源碼內(nèi)容,比如如果源碼中有 #include<stdio.h> 則預(yù)處理器會(huì)讀取文件 stdio.h 文件中的內(nèi)容,并將其直接插入到原來(lái)的源碼文件中,通常另存為以 .i 為擴(kuò)展名的文件。(gcc -E hello.c -o hello.i)
(2)編譯階段:編譯器讀取 .i 文件中的內(nèi)容,并將其翻譯為以 .s 為擴(kuò)展名的匯編語(yǔ)言文件。(gcc -S hello.c -o hello.s)
(3)匯編階段:匯編器將 .s 文件翻譯成機(jī)器碼,并保存為 .o為擴(kuò)展名的文件。
(gcc -c hello.s -o hello.o)
(4)鏈接階段:鏈接器將不同的 .o 文件合并到一起,組成最終的可執(zhí)行文件;比如我們的程序里調(diào)用了 printf 函數(shù),作為一個(gè)C標(biāo)準(zhǔn)函數(shù),printf 單獨(dú)存在于一個(gè) printf.o 的文件中,那么鏈接器將會(huì)找到這個(gè) printf.o 文件,將其中的內(nèi)容合并到我們自己的 .o 文件中,生成可以被加載到內(nèi)存中執(zhí)行的文件。

C語(yǔ)言主要分為幾個(gè)版本:Old Style C、C89、C99和C11。其中,C89、C99和C11是標(biāo)準(zhǔn)語(yǔ)言規(guī)范,現(xiàn)在廣泛使用的是C99。

2.面向過(guò)程和面向?qū)ο蟮膮^(qū)別(*)

(1)面向過(guò)程就是分析出解決問(wèn)題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時(shí)候一個(gè)一個(gè)依次調(diào)用就可以了。
(2)面向?qū)ο笫前褬?gòu)成問(wèn)題事務(wù)分解成各個(gè)對(duì)象,建立對(duì)象的目的不是為了完成一個(gè)步驟,而是為了描敘某個(gè)事物在整個(gè)解決問(wèn)題的步驟中的行為
比如:五子棋
面向過(guò)程:1.開(kāi)始游戲,2、黑子先走,3、繪制畫(huà)面,4、判斷輸贏,5、輪到白子,6、繪制畫(huà)面,7、判斷輸贏,8、返回步驟2,9、輸出最后結(jié)果。
面向?qū)ο螅?.黑白雙方,2、棋盤(pán)系統(tǒng),負(fù)責(zé)繪制畫(huà)面,3、規(guī)則系統(tǒng),負(fù)責(zé)判定諸如犯規(guī)、輸贏等。

特點(diǎn)
  • 面向過(guò)程(蛋炒飯)
    優(yōu)點(diǎn):性能比面向?qū)ο蟾撸驗(yàn)轭愓{(diào)用時(shí)需要實(shí)例化,開(kāi)銷比較大,比較消耗資源;比如單片機(jī)、嵌入式開(kāi)發(fā)、 Linux/Unix等一般采用面向過(guò)程開(kāi)發(fā),性能是最重要的因素。
    缺點(diǎn):沒(méi)有面向?qū)ο笠拙S護(hù)、易復(fù)用、易擴(kuò)展
  • 面向?qū)ο螅ㄉw澆飯)
    優(yōu)點(diǎn):易維護(hù)、易復(fù)用、易擴(kuò)展,由于面向?qū)ο笥蟹庋b、繼承、多態(tài)性的特性,可以設(shè)計(jì)出低耦合的系統(tǒng),使系統(tǒng) 更加靈活、更加易于維護(hù)
    缺點(diǎn):性能比面向過(guò)程低

3.基本數(shù)據(jù)類型

(1)數(shù)值類型

數(shù)值類型分為整型(整數(shù))和浮點(diǎn)型(小數(shù))。按照表示數(shù)字范圍的從大到小。

<1>整型

整數(shù)分為五類:字符型(char)、短整型(short)、整型(int)、長(zhǎng)整型(long)和長(zhǎng)長(zhǎng)整型(long long)

<2>浮點(diǎn)型

浮點(diǎn)型分三類:?jiǎn)尉刃?float)、雙精度型(double)和長(zhǎng)雙精度型(long double)

(2)獲取類型的大小

關(guān)鍵字sizeof:查看變量或者類型大小。


(3)字節(jié)

sizeof獲得數(shù)據(jù)的單位是Byte(字節(jié))。Byte(字節(jié))是計(jì)量存儲(chǔ)容量的一種計(jì)量單位,一個(gè)字節(jié)是8位二進(jìn)制,可容納256個(gè)數(shù)字。一個(gè)ASCII字符就是一個(gè)字節(jié)。

用B表示Byte(字節(jié)),用b表示bit(比特/位).

(4)輸入輸出格式化

double的輸入占位符必須是%lf,輸出占位符可以是%f。

(5)整數(shù)類型

<1>表示范圍

類型的表示范圍與類型大小存在如下關(guān)系:

n表示的是bit位,比如:char是一個(gè)字節(jié),8位,int是4個(gè)字節(jié),32個(gè)二進(jìn)制位,由于存在負(fù)數(shù),所以數(shù)的大小會(huì)減半。

<2>無(wú)符號(hào)整型

在一些特殊情況下,數(shù)據(jù)只用0和整數(shù),不存在負(fù)數(shù),這時(shí)可以使用無(wú)符號(hào)整型unsigned。無(wú)符號(hào)整型只是在原來(lái)的整型前加上關(guān)鍵字unsigned。因?yàn)闆](méi)有負(fù)數(shù),所以數(shù)值的表示范圍擴(kuò)大了一倍。


總之,類型的表示范圍與類型大小存在如下關(guān)系:

<3>整數(shù)類型的選擇
  • 大多數(shù)情況下使用int。
  • 如果int范圍不夠,使用long long。
  • 避免使用long。
  • 謹(jǐn)慎使用unsigned。

(5)浮點(diǎn)類型

<1>浮點(diǎn)數(shù)的范圍
示例 輸出 含義
1.0/0.0 inf 表示正無(wú)窮大
-1.0/0.0 -inf 表示負(fù)無(wú)窮大
0.0/0.0 nan 不存在
<2>浮點(diǎn)數(shù)的精度

注意:浮點(diǎn)類型沒(méi)有無(wú)符號(hào)unsigned類型。

如何比較浮點(diǎn)數(shù)?使用最小誤差。

   float a = 10.2;  
   float b = 9;  
   float c = a - b;
   if(fabs(c - 1.2) < 0.000001){
       printf("%f == 1.2\n",c);//1.200000 == 1.2
}

在誤差范圍內(nèi)認(rèn)為相等。即絕對(duì)值差小于精度最小值。
float浮點(diǎn)數(shù)誤差通常為1-6(6位有效數(shù)字)。
double浮點(diǎn)數(shù)誤差通常為1-15(15位有效數(shù)字)。

<3>浮點(diǎn)類型選擇
  • 大多數(shù)情況下使用double。
  • 盡量不要使用float。
  • 過(guò)程運(yùn)算可以使用long double。

既然浮點(diǎn)數(shù)這么不準(zhǔn)確,為什么還需要?
浮點(diǎn)數(shù)通過(guò)損失精度,獲取更大的表示范圍。

(6)字符類型

字符類型是一個(gè)特殊類型,是整型的一種。使用單引號(hào)表示字符字面量,例如:字母'a'、數(shù)字'1'、空字符''、轉(zhuǎn)義字符\n。

  • 通常使用%c作為格式化占位符輸入輸出,有時(shí)也可以使用%d輸出字符對(duì)應(yīng)ASCII編碼。(C語(yǔ)言中字符即數(shù)字。)
<1>ASCII編碼

ASCII編碼使用7 位二進(jìn)制數(shù)(剩下的1位二進(jìn)制為0)來(lái)表示所有的大寫(xiě)和小寫(xiě)字母,數(shù)字0 到9、標(biāo)點(diǎn)符號(hào), 以及在美式英語(yǔ)中使用的特殊控制字符。

  • ASCII表的特點(diǎn):
    1.字母在ASCII表中是順序排列的。
    2.大寫(xiě)字母和小寫(xiě)字母是分開(kāi)排列的。
<2>運(yùn)算
  • 字符類型可以像整型一樣參與運(yùn)算。
    1.一個(gè)字符加上一個(gè)數(shù)字得到ASCII表中對(duì)應(yīng)新字符。
    2.兩個(gè)字符相減,得到這兩個(gè)字符在表中的距離
<3>轉(zhuǎn)義字符/逃逸字符

在ASCII表中,除了常見(jiàn)的字符(如:大小寫(xiě)字母、數(shù)字等),還包含一些無(wú)法打印出來(lái)的控制字符或特殊字符。這些字符以反斜線\開(kāi)頭,后面接著一個(gè)字符,這種字符被稱作轉(zhuǎn)義字符/逃逸字符。

(7)布爾類型

在C99中新增bool類型表示邏輯值,它只有兩種值(true和false)。使用前需要加上頭文件stdbool.h

(8)數(shù)據(jù)類型轉(zhuǎn)換

<1>自動(dòng)類型轉(zhuǎn)換

當(dāng)運(yùn)算符左右兩邊操作數(shù)的類型不一致時(shí),會(huì)自動(dòng)轉(zhuǎn)換成較大類型。

  • 整型:charshortintlonglong long
  • 浮點(diǎn)型:intfloatdoublelong double
<2>強(qiáng)制類型轉(zhuǎn)換

當(dāng)把一個(gè)較大的類型轉(zhuǎn)換成較小的類型,需要強(qiáng)制轉(zhuǎn)換。
強(qiáng)制轉(zhuǎn)換語(yǔ)法:

(轉(zhuǎn)換后的類型)值
  • 浮點(diǎn)數(shù)轉(zhuǎn)整數(shù)采用的是截?cái)喾绞健?/p>

    printf("%d\n",(int)3.14);//3
    
  • 整型轉(zhuǎn)浮點(diǎn)型也需要強(qiáng)制轉(zhuǎn)換。

    printf("%f\n",(double)2);//2.000000
    

4.運(yùn)算符

(1)算術(shù)運(yùn)算符

+ 、-、*、 /(取整)、 %(取余)

(2)關(guān)系運(yùn)算符

==、!=、>、 <、 >=、 <=

(3)邏輯運(yùn)算符

&& 、  || 、   !

閏年判斷:year%4==0&&year%100!=0 || year%400==0(year能被4整除 and 不能被100整除 or year能被400整除 )

(4)復(fù)合賦值運(yùn)算符

+=、-=、*=、/=、%=

(5)自增、自減運(yùn)算符

自增運(yùn)算符與自減運(yùn)算符優(yōu)先級(jí)高于算術(shù)運(yùn)算符。

<1>前綴自增/自減
++a、--a
<2>后綴自增/自減

(6)運(yùn)算的優(yōu)先級(jí)順序

  • 自增/自減的優(yōu)先級(jí)大于算數(shù)運(yùn)算符的優(yōu)先級(jí);
  • 自增/自減的優(yōu)先級(jí)大于解引用*的優(yōu)先級(jí)
  • 中括號(hào)[ ]的優(yōu)先級(jí)大于解引用*的優(yōu)先級(jí)
  • 結(jié)構(gòu)體中用點(diǎn).的優(yōu)先級(jí)大于取地址&的優(yōu)先級(jí)
  • 結(jié)構(gòu)體中用點(diǎn).的優(yōu)先級(jí)大于接引用*

5.變量

初始化和賦值:

初始化時(shí)在生成變量時(shí)放入數(shù)值,賦值是在已經(jīng)生成變量時(shí)放入數(shù)值。

6.控制語(yǔ)句

(1)條件判斷

<1>if-else

代碼塊與if之間使用空格或者Tab縮進(jìn),不影響編譯和執(zhí)行,只是為了提高代碼可讀性。

if(condition1){

}else if(condition2){

}else{

}
<2>switch case
switch(表達(dá)式){
    case 整型常量1:
       /* 表達(dá)式等于整型常量1執(zhí)行的代碼 */
       break; /* 可選的 */
    case 整型常量2:
       /* 表達(dá)式等于整型常量2執(zhí)行的代碼 */
       break; /* 可選的 */
    default : /* 可選的 */
       /* 表達(dá)式不等于上面所有情況執(zhí)行的代碼 */
}

(2)循環(huán)

<1>while語(yǔ)句
while(條件){
   /* 如果條件為真將重復(fù)執(zhí)行的語(yǔ)句 */
}
<2>do-while
do {
   /* 如果表達(dá)式為真將重復(fù)執(zhí)行的語(yǔ)句 */
}while(條件);//注意while()后的分號(hào);。

do-while循環(huán)是先循環(huán)后判斷,循環(huán)體至少執(zhí)行一次;while循環(huán)是先判斷后循環(huán),循環(huán)體可能一次也不執(zhí)行。

<3>for
for (初始值;條件;遞增或遞減){
   /* 如果條件為真將重復(fù)執(zhí)行的語(yǔ)句 */
}

在while和for循環(huán)中,break是結(jié)束一個(gè)循環(huán)體;continue是結(jié)束單次循環(huán)。

(3)簡(jiǎn)化

<1>省略大括弧

如果if語(yǔ)句、while語(yǔ)句、for語(yǔ)句中只有一個(gè)執(zhí)行語(yǔ)句,可以省略大括弧。

<2>三元運(yùn)算符:?

如果if-else語(yǔ)句只有單個(gè)執(zhí)行語(yǔ)句,可以使用三元運(yùn)算符:?。

7.進(jìn)制

(1)轉(zhuǎn)換

<1>十進(jìn)制轉(zhuǎn)R進(jìn)制

十進(jìn)制轉(zhuǎn)任何進(jìn)制用短除法。從下往上來(lái)統(tǒng)計(jì)。

<2>R進(jìn)制轉(zhuǎn)十進(jìn)制

加權(quán)和。

  0x2A=2*16^1+A*16^0=32+10=42

代碼:

2進(jìn)制數(shù)1011轉(zhuǎn)10進(jìn)制:1
(1*2)+0)*2+1)*2+1)=11
int res=0;
res=res*2+每一位

(2)C語(yǔ)言中的進(jìn)制

<1>進(jìn)制常量表示

C語(yǔ)言不能直接表示二進(jìn)制常量。八進(jìn)制數(shù)字以0開(kāi)頭,十六進(jìn)制數(shù)字以0x或0X開(kāi)頭。

<2>進(jìn)制打印(輸出)

進(jìn)制的輸出其實(shí)與字符輸出是一樣的,根據(jù)占位符的不同輸出不同。

  • 十進(jìn)制:%d
  • 八進(jìn)制:%#o
  • 十六進(jìn)制:%#x
char a = 'a';
printf("%c\t%d\t%#o\t%#x\n",a,a,a);//a,97,0141,0x61
<3>進(jìn)制輸入
  • 10進(jìn)制:%d
  • 8進(jìn)制:%o
  • 16進(jìn)制:%x
scanf("%o",&n);//010
printf("%d\n",n);//8
scanf("%x",&n);//0xa
printf("%d\n",n);//10
<4>%i

%i 可以匹配八進(jìn)制、十進(jìn)制、十六進(jìn)制表示的整數(shù)。
例如: 如果輸入的數(shù)字有前綴 0(018),%i將會(huì)把它當(dāng)作八進(jìn)制數(shù)來(lái)處理,如果有前綴0x (0x54),它將以十六進(jìn)制來(lái)處理。

scanf("%i",&n);//0xa
printf("%d\n",n);//10

8.文件操作

(1)文件輸入輸出

  • 使用printf()和命令行重定向>實(shí)現(xiàn)文件輸出;
  • 使用scanf()和命令行重定向<實(shí)現(xiàn)文件輸入。
<1>實(shí)例
  • hello.c
char name[256];
scanf("%s",name);
printf("Hello %s\n",name);
  • 編譯

    gcc hllo.c -o hello
    
  • 執(zhí)行

echo zhangsan > namefile   //把張三重定向到namefile文件中
./hello < namefile > output   //把輸入定向到./hello 中,然后把執(zhí)行結(jié)果定向到output中。

(2)文件的打開(kāi)和關(guān)閉fopen和fclose

  • 頭文件
    <stdio.h>
<1>打開(kāi)文件fopen
FILE *fopen(const char *pathname, const char *mode);
  • 參數(shù)
No. 參數(shù) 作用
1 filename 需要打開(kāi)的文件
2 mode 文件打開(kāi)方式
  • 返回值
No. 類型 說(shuō)明
1 成功 返回值是指向這個(gè)文件流的文件指針
2 失敗 NULL
  • 打開(kāi)方式
No. 打開(kāi)方式 含義
1 r(read)
2 w(write) 寫(xiě),不存在創(chuàng)建,存在清空寫(xiě)入(w),追加(a)
3 a(append) 追加
4 +(plus) 讀或?qū)懀饕桥浜蟫、w、a使用
5 t(text) 文本文件(默認(rèn))
6 b(binary) 二進(jìn)制文件
<2>關(guān)閉文件fclose
int flcose(FILE* stream);
  • 參數(shù)
    stream文件指針。
  • 返回值
    如果成功釋放,返回0; 否則返回EOF(-1).

(3)文本讀寫(xiě)fprintf()fscanf()

<1>函數(shù)原型
int printf(const char *format, ...);
int fprintf(FILE *stream, char *format, argument...);
int fscanf(FILE *stream, char *format, argument... );

fprintf()/fscanf()printf()/scanf()使用非常相似,區(qū)別在于fprintf()/fscanf()第一個(gè)參數(shù)stream是文件描述符。

<2>用法示例
  • 從文件中讀出數(shù)據(jù)
int i ;
float f ;
char c;
char str[10];
fscanf(fp, "%d %f %c %s\n", &i, &f, &c, str); //字符串是不需要加&
  • 將數(shù)據(jù)寫(xiě)入文件
int i = 10;
float f = 3.14;
char c = 'C';
char str[10] = "haha";
fprintf(fp, "%d %f %c %s\n", i, f, c, str);

如果不需要從文件里面寫(xiě)入字符串,那么就可以用逗號(hào)或者其他符號(hào)來(lái)分隔;如果文件里需要寫(xiě)入字符串,那么字符串與其他數(shù)據(jù)之間只能用空格和回車來(lái)分隔。

<3>實(shí)例
  • 將多個(gè)學(xué)生信息寫(xiě)入文件并讀出。
struct Student {
    char  name[32];   //姓名
    int  age;     //年齡
    float  score;   //成績(jī)
};
int main() {
    //打開(kāi)文件
    FILE* fp =fopen("./info.txt","r");
    if(NULL==fp) {
        perror("文件fp打開(kāi)失敗");
        return 1;
    }
    //從文件中讀取數(shù)據(jù)
    int n;
    fscanf(fp,"%d",&n);
    struct Student student[n];
    for(int i=0; i<n; ++i) {
        fscanf(fp,"%s %d %f",student[i].name,&student[i].age,&student[i].score);
    }
    //將數(shù)據(jù)寫(xiě)入文件
    FILE* fq=fopen("./res.txt","w");
    if(NULL==fq) {
        perror("fq打開(kāi)失敗");
        return 1;
    }
    for(int i=0; i<n; ++i) {
        fprintf(fq,"%s\t%d\t%f\n",student[i].name,student[i].age,student[i].score);
    }
    //關(guān)閉文件
    fclose(fq);
    fclose(fp);
}

(4)二進(jìn)制讀寫(xiě):fread()和fwrite()

對(duì)于二進(jìn)制文件的數(shù)據(jù)讀取和寫(xiě)入是一對(duì),只有以二進(jìn)制的方式寫(xiě)入到文件才能讀出來(lái),不能從文本文件中讀取數(shù)據(jù)。

<1>函數(shù)原型
size_t fread(void *ptr, size_t size, size_t count, FILE* stream);
size_t fwrite(void *ptr, size_t size, size_t count, FILE* stream);
  • 參數(shù)
No. 參數(shù) 作用
1 ptr 一個(gè)指針,在fread()中是從文件里讀入的數(shù)據(jù)存放的地址;在fwrite()中是寫(xiě)入到文件里的數(shù)據(jù)存放 的地址。
2 size 每次要讀寫(xiě)的字節(jié)數(shù)
3 count 讀寫(xiě)的次數(shù)
4 stream 文件指針
  • 返回值
    成功讀取/寫(xiě)入的字節(jié)數(shù)。
<2>用法示例
  • 從文件中讀出字符串
char str[100];   
fread(str, sizeof(str), 1, fp);//每次讀取sizeof(str)個(gè)字節(jié),讀一次
或者:
fread(str,1,sizeof(str),fp);//每次讀取1個(gè)字節(jié),讀取sizeof(str)次,和上面一樣。
  • 將字符串寫(xiě)入文件
char str[] = "Hello World";
fwrite(str, sizeof(str), 1, fp);
或者:
fwite(str,1,sizeof(str),fp);
<3>實(shí)例
  • 往文件中寫(xiě)數(shù)據(jù)(結(jié)構(gòu)體)
typedef struct {
    char  name[32];   //姓名
    int  age;     //年齡
    float  score;   //成績(jī)
}Student;
int main(){
    FILE* fp = fopen("./1.txt","wb");
    if(NULL==fp){
        perror("open failed");
        return 1;
    }
    Student student[2];
    strcpy(student[0].name,"張三");//結(jié)構(gòu)體中的字符串賦值要使用strcpy
    student[0].age=20;
    student[0].score=92.8;
    strcpy(student[1].name,"李四");
    student[1].age=22;
    student[1].score=76.2;
    for(int i=0;i<2;++i){
        fwrite(&student[i],sizeof(Student),1,fp);
    }
    fclose(fp);
}
  • 從文件中讀數(shù)據(jù)(結(jié)構(gòu)體)
int main() {
    //打開(kāi)文件
    FILE* fp =fopen("./1.txt","rb");
    if(NULL==fp) {
        perror("文件fp打開(kāi)失敗");
        return 1;
    }
    Student student;//由于不知道讀取的個(gè)數(shù),所以使用while循環(huán)
    while(fread(&student,sizeof(Student),1,fp)){//讀到數(shù)據(jù)就顯示,否則就退出
        printf("%s\t%d\t%f\n",student.name,student.age,student.score);
    }
    fclose(fp);
}

(1)寫(xiě)操作fwrite()后必須關(guān)閉流fclose()。
(2)不關(guān)閉流的情況下,每次讀或?qū)憯?shù)據(jù)后,文件指針都會(huì)指向下一個(gè)待寫(xiě)或者讀數(shù)據(jù)位置的指針。
(3)sizeof和strlen的區(qū)別,否則就會(huì)讀不到數(shù)據(jù)(在讀數(shù)據(jù)時(shí),使用strlen(buf))或者讀到一半數(shù)據(jù)(寫(xiě)數(shù)據(jù)時(shí),使用sizeof(msg)=8)。
(4)對(duì)于二進(jìn)制文件的寫(xiě)入和讀取是同時(shí)存在的,不能從普通文件中讀取數(shù)據(jù)。
(5)其他類型數(shù)據(jù)的讀取:https://www.cnblogs.com/xudong-bupt/p/3478297.html

<4>二級(jí)制文件和文本文件
比較 文本 二進(jìn)制
優(yōu)勢(shì) 便于人類讀寫(xiě),跨平臺(tái) 文件較小,機(jī)器讀寫(xiě)比較快
劣勢(shì) 文件較大,機(jī)器讀寫(xiě)比較慢 不便人類讀寫(xiě),不跨平臺(tái)
配置 Unix用文件 Windows用注冊(cè)表
  • 說(shuō)明:
    (1)Unix喜歡用文本文件來(lái)做數(shù)據(jù)存儲(chǔ)和程序配置。
    (2)windows喜歡用二進(jìn)制文件。
    (3)數(shù)據(jù)量較多使用數(shù)據(jù)庫(kù)
    (4)多媒體使用二進(jìn)制
    (5)通常使用第三方庫(kù)讀寫(xiě)文件,很少直接讀寫(xiě)二進(jìn)制文件。

(5)文件定位:ftell()fseek()

<1>函數(shù)原型
// 獲取位置
long ftell(FILE* stream);
// 設(shè)置位置
int fseek(FILE* stream,long offset,int whence);
  • 參數(shù)
No. 參數(shù) 含義
1 stream 文件指針
2 offset 偏移量,基于起始點(diǎn)偏移了offset個(gè)字節(jié)
3 whence 起始點(diǎn)
No. whence 數(shù)值 含義
1 SEEK_SET 0 從頭開(kāi)始
2 SEEK_CUR 1 從當(dāng)前開(kāi)始
3 SEEK_END 2 從結(jié)束開(kāi)始
  • 返回值
    ftell()返回文件指針當(dāng)前位置,基于文件開(kāi)頭的偏移字節(jié)數(shù)。
<2>示例:
fseek(stream, 0, SEEK_END);
// 將文件指針指向文件結(jié)尾,并偏移了 0 個(gè)字節(jié),也就是直接將文件指針指向文件結(jié)尾
fseek(stream, -10, SEEK_CUR);
// 將文件指針指向當(dāng)前位置,并偏移了 -10 個(gè)字節(jié),也就是將文件指針往前移動(dòng)10個(gè)字節(jié)
應(yīng)用

獲取文件大小。

    char path[1024];
    scanf("%s",path);
    FILE* fp = fopen(path,"r");
    if(fp){
        fseek(fp,0,SEEK_END);
        long size = ftell(fp);
        printf("%ldB\n",size);
    }

(6)文件結(jié)尾判斷feof()

<1>函數(shù)原型
int feof(FILE* stream);
  • 返回值
    一旦文件指針指向文件結(jié)尾,就返回一個(gè)真值;否則返回非真值。

(7)返回開(kāi)頭rewind()

<1>函數(shù)原型
  void rewind(FILE* stream);
<2>舉例
FILE *fp = fopen("./text.txt", "r+");
fseek(fp, 0, SEEK_END);   // 將文件指針指向文件結(jié)尾
long len = ftell(fp);     // 獲取文件指針位置,得到文件的大小(Byte)
rewind(fp);               // 將文件指針重新指向文件開(kāi)頭

(8) 清空數(shù)據(jù)流fflush()

<1>函數(shù)原型
void fflush(FILE* stream);
<2>使用

在使用多個(gè)輸出函數(shù)連續(xù)進(jìn)行多次輸出時(shí),有可能發(fā)現(xiàn)輸出錯(cuò)誤。因?yàn)橄乱粋€(gè)數(shù)據(jù)再上一個(gè)數(shù)據(jù)還沒(méi)輸出完畢,還在輸出緩沖區(qū)中時(shí),下一個(gè)printf就把另一個(gè)數(shù)據(jù)加入輸出緩沖區(qū),結(jié)果沖掉了原來(lái)的數(shù)據(jù),出現(xiàn)輸出錯(cuò)誤。 在 prinf();后加上fflush(stdout); 強(qiáng)制馬上輸出,避免錯(cuò)誤。

(9)文件重名名rename

 int rename(const char *old_filename, const char *new_filename);

(10)文件刪除remove

int remove(char * filename);

(11)putcgetc

<1>函數(shù)原型
int getc ( FILE * stream );//從stream中獲取一個(gè)字符
int putc ( int character, FILE * stream );//將字符寫(xiě)入到stream中
<2>實(shí)現(xiàn)cp命令
int main(int argc,char* argv[]){
    if(3!=argc){
        perror("argc error");
        return 1;
    }
    FILE* srcfp=fopen(argv[1],"rb");
    if(NULL==srcfp){
        perror("srcfile error");
        return 1;
    }
    FILE* dstfp=fopen(argv[2],"wb");
    if(NULL==dstfp){
        perror("dstfile error");
        return 1;
    }
    while(!feof(srcfp)){//到達(dá)文件末尾返回true,否則返回flase
        putc(getc(srcfp),dstfp);
    }
    fclose(srcfp);
    fclose(dstfp);
}

9.宏定義

(1) 宏定義是什么?

宏是用來(lái)表示一段代碼的標(biāo)識(shí)符。

宏也是標(biāo)識(shí)符,也要滿足標(biāo)識(shí)符的規(guī)則。但通常習(xí)慣使用大寫(xiě)字母和下劃線命名

(2)宏定義怎么用?

<1>宏定義通常有三種用法:
  • 當(dāng)作常量使用。
  • 當(dāng)作函數(shù)使用。
  • 編譯預(yù)處理。
<2>宏定義常量
  • 預(yù)定義宏
    ANSI C標(biāo)準(zhǔn)有定義好的宏定義,稱為預(yù)定義宏。這些宏定義以雙下劃線__開(kāi)頭結(jié)尾。
printf("%s:%d",__FILE__,__LINE__);//源文件名:當(dāng)前語(yǔ)句所在的行號(hào)
printf("%s:%s",__DATE__,__TIME__);//月 日 年 時(shí)間
  • 自定義宏
    除了使用標(biāo)準(zhǔn)定義的宏,可以使用#define指令用來(lái)定義一個(gè)宏。

(1)語(yǔ)法

  #define 標(biāo)識(shí)符 值
  #define PI 3.1415926

(2)說(shuō)明

  • 注意沒(méi)有結(jié)尾的分號(hào),因?yàn)椴皇荂的語(yǔ)句。

  • 名字必須是一個(gè)單詞,值可以是各種東西。

  • 在C語(yǔ)言的編譯器開(kāi)始之前,編譯預(yù)處理程序會(huì)把程序中的名字換成值,是完全的文本替換。

  • 如果一個(gè)宏的值有其他宏的名字,也會(huì)被替換

    #define PI_2 2*PI
    
  • 如果一個(gè)宏的值超過(guò)一行,最后一行之前行末需要加\

    #define PI_2 2 \
                 * \
                 PI
    
  • 宏的值后面出現(xiàn)的注釋不會(huì)被當(dāng)做宏的值的一部分。

    #define PI_2 2*PI  // 二倍的PI
    
<3>帶參數(shù)的宏

宏可以帶參數(shù),使用上有些像函數(shù)。這種宏稱為帶參數(shù)的宏。

  • 語(yǔ)法
#define 標(biāo)識(shí)符(參數(shù)...) 代碼
示例:
#define square(x) ((x)*(x))
#define cube(x) ((x)*(x)*(x))
  • 錯(cuò)誤示范
#define square(x) x*x
square(10);//100
square(10+1);//10+1*10+1=21
  • 說(shuō)明
    上面因?yàn)槿鄙倮ㄌ?hào)導(dǎo)致錯(cuò)誤,稱為宏定義邊際效應(yīng),所以帶參數(shù)的宏需要在以下兩個(gè)位置加上括號(hào):
    (1)參數(shù)出現(xiàn)的每個(gè)地方都要加括號(hào)。
    (2)整個(gè)值要加括號(hào)
  • 參數(shù)的宏也可以有多個(gè)參數(shù)
#define MIN(a,b) ((a)<(b)?(a):(b))

swap函數(shù):

#define SWAP(m,n) {\
    int t=m;\
    m=n;\
    n=t;\
}

盡量避免使用宏定義。

<4>編譯預(yù)處理

有時(shí)我們會(huì)使用沒(méi)有值的宏,這種宏用于條件編譯的,#ifdef #ifndef用于檢查宏是否被定義過(guò)。控制代碼的編譯。

#define TEST
#ifdef TEST
    printf("Test\n");//如果前面定義了:#define TEST,則執(zhí)行這個(gè),否則執(zhí)行后面的。
#else
    printf("No Test\n");
#endif

(3)宏展開(kāi)

宏的本質(zhì)是指編譯前(編譯預(yù)處理階段),用定義中的值或者代碼完全替換宏的標(biāo)識(shí)符。
只替換條件編譯中的宏。使用下面指令來(lái)查看宏的展開(kāi)。

    gcc -E hello.c -o hello.i

(4)編譯預(yù)處理指令

#開(kāi)頭的都是編譯預(yù)處理指令。除了宏定義,還有文件包含#include和條件編譯指令#if、#ifdef #ifndef、#else、#elif、#endif,一般寫(xiě)在.h文件中。

  • 文件包含#include,把文件內(nèi)容包含到代碼中。
  • 條件編譯指令,根據(jù)編譯條件,選擇編譯或者編譯某段代碼。
  • 格式
#ifndef __HELLO_H
#define __HELLO_H
函數(shù)聲明(函數(shù)原型)
#endif //__HELLO_H

10.頭文件

(1)經(jīng)驗(yàn)

編寫(xiě)小的程序可以把代碼寫(xiě)在一個(gè)文件中,當(dāng)編寫(xiě)大程序中,需要把代碼分在多個(gè)文件中。

  • 多個(gè)源代碼文件
    (1)main()里面代碼太長(zhǎng)適當(dāng)分成幾個(gè)函數(shù)。
    (2)一個(gè)源代碼文件太長(zhǎng)適當(dāng)分成幾個(gè)文件。
    (3)兩個(gè)獨(dú)立的源代碼文件不能編譯成可執(zhí)行文件。

(2)頭文件概念

<1>#include指令

把函數(shù)原型放到一個(gè)頭文件.h,在需要調(diào)用這個(gè)函數(shù)的源代碼文件.c時(shí),使用#include指令包含這個(gè)頭文件,使編譯器在編譯的時(shí)候知道函數(shù)的原型。

<2>頭文件作用

頭文件主要用于編譯器的編譯階段,告訴編譯器在代碼中使用了這么一個(gè)函數(shù)。只是告訴編譯器函數(shù)的原型,保證調(diào)用時(shí)參數(shù)類型和個(gè)數(shù)正確

(3)頭文件的使用

在使用和定義函數(shù)的地方都要#include頭文件,#include指令不一定要放在.c文件的最前面,但是通常習(xí)慣這樣做。

#include指令分類

#include指令有兩種形式:
(1)#include <>:編譯器到指定目錄查找,主要用于查找標(biāo)準(zhǔn)庫(kù)的頭文件。
(2)#include "":編譯器先到當(dāng)前目錄查找(.c文件所在目錄),如果沒(méi)有再到指定目錄查找。

#include指令是一個(gè)編譯預(yù)處理指令,和宏一樣,在編譯之前就處理了。它會(huì)把指定的文件原封不動(dòng)的插入到它所在的地方。

(4)頭文件怎么寫(xiě)

頭文件通常用來(lái)存放所有對(duì)外公開(kāi)的函數(shù)的原型和全局變量的聲明
通常任何.c文件都有對(duì)應(yīng)同名的.h文件

<1>聲明
  • 常見(jiàn)的聲明
    • 函數(shù)聲明
    • 變量聲明
    • 結(jié)構(gòu)體聲明
    • 宏聲明
    • 枚舉聲明
    • 類型聲明

通常聲明只能可以放在頭文件中,否則,編譯器連接會(huì)出現(xiàn)重名函數(shù)錯(cuò)誤。

  • 重復(fù)聲明
    在一個(gè)編譯單元中,不允許重復(fù)聲明,尤其是結(jié)構(gòu)體聲明。為了防止頭文件不被多次#include導(dǎo)致重復(fù)聲明。
  • 定義與聲明
    聲明是不產(chǎn)生代碼的語(yǔ)句。定義是產(chǎn)生代碼的語(yǔ)句。
int i;// 變量的定義,在.c文件中
extern int i; // 變量的聲明,在.h文件中
<2>標(biāo)準(zhǔn)頭文件結(jié)構(gòu)

避免頭文件多次包含,必須使用標(biāo)準(zhǔn)頭文件結(jié)構(gòu)。

#ifndef _文件名_H__
#define _文件名_H__
// 聲明
#endif

使用條件編譯和宏,保證頭文件在一個(gè)編譯單元中只會(huì)#include一次。

#pragma once指令也起到相同作用,但是并不是所有編譯器支持。

  • 分析



    在cord.cpp 中第一次遇到coordin.h的文件的時(shí)候,它的內(nèi)容會(huì)被讀取,找到#ifndef COORDIN_H_,并且會(huì)給COORDIN_H_設(shè)定一個(gè)值,之后再次看到coordin.h頭文件時(shí),COORDIN_H_就已經(jīng)被定義了,coordin.h的內(nèi)容就不會(huì)再次被讀取了。

  • 誤區(qū):
    (1)#include不是用來(lái)引入庫(kù)的,是告訴編譯器函數(shù)聲明,
    (2)頭文件只有函數(shù)原型,函數(shù)實(shí)現(xiàn)在.a(Unix)或者.lib(Windows)中。
    (3)現(xiàn)代的C語(yǔ)言編譯器會(huì)默認(rèn)引入所有的標(biāo)準(zhǔn)庫(kù)。

11. 變量的作用域和生存周期

(1)作用域

<1>作用域是什么?

在什么范圍內(nèi)可以訪問(wèn)這個(gè)變量。

<2>作用域怎么用?

局部變量的作用域在變量定義的大括號(hào)以內(nèi)。

(2)生存周期

<1>生存周期是什么?

變量什么時(shí)候出現(xiàn)到什么時(shí)候滅亡。對(duì)于局部變量,生存期與作用域一致。

<2>使用

不要返回局部變量的地址(注意是地址,不是值),返回局部變量的值是可以的。

  • 認(rèn)識(shí)
string& test_str(){
    string str = "test";
    return str;
}
int main(){
    string& str_ref = test_str();
    cout << str_ref << endl;
    return 0;
}

A.編譯警告
B.返回局部變量的引用,運(yùn)行時(shí)出現(xiàn)未知錯(cuò)誤
C.正常編譯且運(yùn)行
D.把代碼里的&都去掉之后,程序可以正常運(yùn)行
  • 分析:
    ABD。
    (1)在C語(yǔ)言中,局部變量是分配在棧空間上的, 當(dāng)函數(shù)調(diào)用結(jié)束后,由編譯器釋放.
    (2)通過(guò)調(diào)用test_str得到了他的局部變量的內(nèi)存地址, 然而在main函數(shù)中調(diào)用函數(shù)時(shí),這個(gè)內(nèi)存地址被”破壞”了,類似于野指針。在c語(yǔ)言中,一種典型的錯(cuò)誤就是將一個(gè)指向局部變量的指針作為函數(shù)的返回值
    (3) 如果返回指針(變量地址),應(yīng)該返回堆區(qū)或者全局區(qū)的地址(全局變量或者靜態(tài)變量)

(3)同名隱藏

在相同作用域中,同名變量會(huì)報(bào)錯(cuò);在不同的作用域中,內(nèi)部變量會(huì)隱藏外部變量,

int main() {
    int n = 1;
    {
        printf("n = %d\n",n);//1
        int n=10;
        printf("n = %d\n",n);//10
        n = 20;
    }
    printf("n = %d\n",n);//1
}

12.變量分類

(1)本地變量/局部變量

<1>概念

在大括號(hào)內(nèi)定義的變量就是本地變量/局部變量。

<2>特點(diǎn)
  • 1.本地變量是定義在代碼塊內(nèi)的,可以定義在函數(shù)的塊內(nèi),可以定義在語(yǔ)句的塊內(nèi)(for),可以定義在一個(gè)隨意的大括弧里面。
  • 2.程序進(jìn)入塊前,內(nèi)部變量不存在,離開(kāi)時(shí)失效。
  • 3.塊外定義的變量,塊內(nèi)仍然有效。(反過(guò)來(lái)不行)

函數(shù)的每次運(yùn)行,都會(huì)產(chǎn)生一個(gè)獨(dú)立的變量空間,在這個(gè)空間中的變量,是函數(shù)這次運(yùn)行獨(dú)有的。
1.定義在函數(shù)內(nèi)部的變量就是本地變量;2.參數(shù)也是本地變量

<3>初始化
  • 1.本地變量不會(huì)默認(rèn)初始化
  • 2.參數(shù)在進(jìn)入函數(shù)時(shí)被初始化。

本地變量/局部變量的生存期和作用域都是在大括號(hào)內(nèi)。

(2)全局變量

<1>定義

定義在函數(shù)外面的變量稱為全局變量。

int n;//全局變量
int main(){
    int m;//局部變量
}
<2>特點(diǎn)

全局變量有全局的生存周期和作用域。

  • 1.不屬于任何函數(shù)。
  • 2.所有函數(shù)內(nèi)部都可以使用。
<3>初始化
  • 沒(méi)有初始化的全局變量會(huì)自動(dòng)初始化為0。
  • 只能用編譯時(shí)刻已知的值初始化全局變量。(只能用常量來(lái)初始化全局變量)
  • 初始化發(fā)生在main()前。
<4>同名隱藏

如果函數(shù)內(nèi)部存在與全局變量同名的變量,則全局變量被隱藏。

全局變量有全局的生存周期和作用域。

(3)局部靜態(tài)變量

<1>定義

在本地變量定義時(shí)加上static變成靜態(tài)本地變量。(只初始化1次)

<2>特點(diǎn)

當(dāng)函數(shù)離開(kāi)時(shí),靜態(tài)局部變量會(huì)繼續(xù)存在并保存其值。

int inc(){
    static int n = 1;
    n = n + 1;
    return n;
}
int main(){
    printf("%d\n",inc());//2
    printf("%d\n",inc());//3
    printf("%d\n",inc());//4
}

(1)靜態(tài)本地變量的初始化在第一次進(jìn)入函數(shù)時(shí)執(zhí)行,以后進(jìn)入函數(shù)會(huì)保持離開(kāi)的值。
(2)靜態(tài)本地變量是特殊的全局變量,具有全局生存周期和局部作用域。

(4)全局靜態(tài)變量

<1>定義

在全局變量前加上關(guān)鍵字static。

<2>全局變量與全局靜態(tài)變量區(qū)別
  • 1.若程序由一個(gè)源文件構(gòu)成時(shí),全局變量與全局靜態(tài)變量沒(méi)有區(qū)別。
  • 2.若程序由多個(gè)源文件構(gòu)成時(shí):
    非靜態(tài)的全局變量的作用域是整個(gè)源程序,非靜態(tài)的全局變量在各個(gè)源文件中都是有效的,而靜態(tài)全局變量則限制了其作用域, 即只在定義該變量的源文件內(nèi)有效, 在同一源程序的其它源文件中不能使用它。
  • 3.非靜態(tài)全局變量具有外部鏈接的靜態(tài),可以在所有源文件里調(diào)用,除了本文件,其他文件可以通過(guò)extern的方式引用。
extern int a,b;//在.h文件中全局變量的聲明

(5)變量的作用域和生命期總結(jié)

  • 作用域
    變量或函數(shù)在運(yùn)行時(shí)候的有效作用范圍
  • 生命期
    變量或函數(shù)在運(yùn)行時(shí)候的沒(méi)被銷毀回收的存活時(shí)間。


(6)static關(guān)鍵字小結(jié)

static在C語(yǔ)言里面既可以修飾變量,也可以修飾函數(shù)。

<1>static變量
  • 靜態(tài)局部變量:在函數(shù)中定義的,生命周期是整個(gè)源程序,但是作用域和局部變量沒(méi)區(qū)別。只能在定義這個(gè)變量的函數(shù)范圍內(nèi)使用,而且只在第一次進(jìn)入這個(gè)函數(shù)時(shí)候被初始化,之后的初始化會(huì)跳過(guò),并保留原來(lái)的值。退出這個(gè)函數(shù)后,盡管這個(gè)變量還在,但是已經(jīng)不能使用了。
  • 靜態(tài)全局變量:全局變量本身就是靜態(tài)存儲(chǔ)的,但是靜態(tài)全局變量和非靜態(tài)全局變量又有區(qū)別:
    1.全局變量:變量的作用域是整個(gè)源程序,其他源文件也可以使用,生命周期整個(gè)源程序。
    2.靜態(tài)全局變量:變量的作用域范圍被限制在當(dāng)前文件內(nèi),其他源文件不可使用,生命周期整個(gè)源程序。

靜態(tài)變量的生命周期是整個(gè)源程序,而且只能被初始化一次,之后的初始化會(huì)被忽略。(如果不初始化,數(shù)值數(shù)據(jù)將被默認(rèn)初始化為0, 字符型數(shù)據(jù)默認(rèn)初始化為NULL)。

<2>static函數(shù)(內(nèi)部函數(shù))

只能被當(dāng)前文件內(nèi)的其他函數(shù)調(diào)用,不能被其他文件內(nèi)的函數(shù)調(diào)用,主要是區(qū)別非靜態(tài)函數(shù)(外部函數(shù))。

1.在函數(shù)前面加上static就使它成為只能所在編譯文件中使用的函數(shù)。
2.在全局變量前加上static使它成為只能所在編譯文件中使用的全局變量

(7)實(shí)踐經(jīng)驗(yàn)

<1>不要返回本地變量的指針
  • 返回本地變量的地址是危險(xiǎn)的。
  • 返回全局變量或靜態(tài)本地變量的地址是安全的。
  • 返回函數(shù)內(nèi)的動(dòng)態(tài)內(nèi)存是安全的,但注意要記得釋放。
  • 最好的做法是返回傳入的指針。(常用)
<2>慎用靜態(tài)變量
  • 不要使用全局變量在函數(shù)間傳遞參數(shù)和結(jié)果。
  • 盡量避免使用全局變量。
  • 使用全局變量和靜態(tài)本地變量的函數(shù)是不可重入的,是線程不安全的。

13.內(nèi)存

(1) 結(jié)構(gòu)體字節(jié)對(duì)齊

在C語(yǔ)言里,結(jié)構(gòu)體所占的內(nèi)存是連續(xù)的,但是各個(gè)成員之間的地址不一定是連續(xù)的。所以就出現(xiàn)了"字節(jié)對(duì)齊"。

字節(jié)對(duì)齊默認(rèn)原則
  • 結(jié)構(gòu)體變量的大小,一定是其最大的數(shù)據(jù)類型的大小的整數(shù)倍,如果某個(gè)數(shù)據(jù)類型大小不夠,就填充字節(jié)。
  • 結(jié)構(gòu)體變量的地址,一定和其第一個(gè)成員的地址是相同的。
struct Box{
    int height;
    char a[10];
    double width; 
    char type;
};
int main(void) {
    struct Box box;
    printf("box = %p\n", &box);
    printf("box.height = %p\n", &box.height);
    printf("box.a = %p\n", box.a);
    printf("box.width = %p\n", &box.width);
    printf("box.type = %p\n", &box.type);
    printf("box = %ld\n", sizeof(box));
}

(2)內(nèi)存四區(qū)

image.png

<1>棧區(qū)(stack)

由編譯器自動(dòng)分配和釋放,主要是存放函數(shù)參數(shù)的值,局部變量的值。
比如:int a; int *p; 這兒的a和p都存放在棧中

<2>堆區(qū)(heap)

由程序員自己申請(qǐng)分配和釋放,需要malloc()、calloc()、realloc()函數(shù)來(lái)申請(qǐng),用free()函數(shù)來(lái)釋放如果不釋放,可能出現(xiàn)指針懸空/野指針。

函數(shù)不能返回指向棧區(qū)的指針,但是可以返回指向堆區(qū)的指針。

<3> 數(shù)據(jù)區(qū)(data)

** 數(shù)據(jù)區(qū)在程序結(jié)束后由操作系統(tǒng)釋放**

  • 存放常量。
    包含字符串常量和其他常量。 char *p = "I love u"; 指針p指向的這塊內(nèi)存屬于常量區(qū)。
  • 存放全局變量和靜態(tài)變量。
    初始化的全局變量和靜態(tài)變量在一塊區(qū)域(data段);未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域,稱作BSS(Block Started by Symbol:以符號(hào)開(kāi)始的塊);
<4>代碼區(qū)(code)

用于存放編譯后的可執(zhí)行代碼,二進(jìn)制碼,機(jī)器碼。

  int a; //申請(qǐng)棧區(qū)內(nèi)存
  a = 4; //指向的代碼,放在代碼區(qū)。

(3)堆和棧的區(qū)別(重要)

No. 比較方面
1 管理方式 由系統(tǒng)自動(dòng)管理,以執(zhí)行函數(shù)為單位 由程序員手動(dòng)控制
2 空間大小 空間大小編譯時(shí)確定(參數(shù)+局部變量) 具有全局性,總體無(wú)大小限制。
3 分配方式 函數(shù)執(zhí)行,系統(tǒng)自動(dòng)分配;函數(shù)結(jié)束,系統(tǒng)立即自動(dòng)回收 使用new/malloc()手動(dòng)申請(qǐng);使用delete/free()手動(dòng)釋放
4 優(yōu)點(diǎn) 使用方便,不需要關(guān)心內(nèi)存申請(qǐng)釋放。 可以跨函數(shù)使用。(可以返回指向堆區(qū)的指針)
5 缺點(diǎn) 只能在函數(shù)內(nèi)部使用。 容易造成內(nèi)存泄露。

(4)顯示目標(biāo)文件區(qū)段大小

  • size命令:

dec與hex是前面三個(gè)區(qū)域的和,dec是十進(jìn)制,hex是十六進(jìn)制。

  • 各區(qū)段的含義
No. 區(qū)段 名稱 含義
1 text 代碼段(code segment/text segment) 存放程序執(zhí)行代碼的內(nèi)存區(qū)域。該區(qū)域的大小在運(yùn)行前已確定,且通常屬于只讀。可能包含一些只讀的常數(shù)變量,例如字符串常量等。
2 data 數(shù)據(jù)段(data segment) 存放程序中已初始化的全局變量的內(nèi)存區(qū)域。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配。
3 bss BSS段(bss segment) 存放程序中未初始化的全局變量的內(nèi)存區(qū)域。BSS是英文Block Started by Symbol的簡(jiǎn)稱。BSS段屬于靜態(tài)內(nèi)存分配。
  • 沒(méi)有顯示的區(qū)段
No. 區(qū)段 含義
1 棧(stack) 存放程序臨時(shí)創(chuàng)建的局部變量,也就是函數(shù)括弧{}中定義的變量(不包括static聲明的變量)。在函數(shù)被調(diào)用時(shí),參數(shù)也會(huì)被壓入發(fā)起調(diào)用的進(jìn)程棧中,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會(huì)被存放回棧中。
2 堆(heap) 存放程序動(dòng)態(tài)分配的內(nèi)存段,大小并不固定,可動(dòng)態(tài)擴(kuò)張或縮減。當(dāng)調(diào)用malloc()等函數(shù)分配內(nèi)存時(shí),堆被擴(kuò)張;當(dāng)調(diào)用free()等函數(shù)釋放內(nèi)存時(shí),堆被縮減。

14.二進(jìn)制

(1)位運(yùn)算

位運(yùn)算說(shuō)穿了,就是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作.

<1>按位運(yùn)算
No. 操作符 功能
1 & 按位與
2 | 按位或
3 ~ 按位取反
4 ^ 按位異或(相異為真)
<2>運(yùn)算規(guī)則
<3>按位與

1.讓某一位或某些位為0(清零)

int n = 0xFFFF;
n = n & 0x0010;//0x10

2.取一個(gè)數(shù)中某些指定位:
比如a=23,我想取a的二進(jìn)制的后面4位數(shù),那么可以找一個(gè)后4位是1其余位是0的數(shù)b,即b=0x0f(十六進(jìn)制,轉(zhuǎn)換為二進(jìn)制為00001111),a&b就得到了a的后四位。

a:00010111
b:00001111
a&b:00000111

3.保留指定位:
比如a=23(用8bit表示),我想保留其二進(jìn)制的第4和第6位(最左邊為第1位),其余位置0。那么可以找一個(gè)第4和第6位是1其余位是0的數(shù)b與a進(jìn)行按位與運(yùn)算.

a:00010111
b:00010100
a&b:00010100

4.應(yīng)用:

  • 判斷某一位是否為1;
    設(shè)置一個(gè)只有某一位是1的數(shù),其余為是0,然后和判斷的數(shù)進(jìn)行位與。
  • 判斷一個(gè)數(shù)是否是偶數(shù);
    與1進(jìn)行位與,如果其二進(jìn)制的最末尾是0表示偶數(shù),為1表示奇數(shù)。

結(jié)論:任何二進(jìn)制位與0能實(shí)現(xiàn)置0;與1保持原值不變.

<4>按位或

1.讓某一位或某些位為1,其余位不變。

int n = 0x0000;
n = n | 0x0010;

2.拼接兩個(gè)二進(jìn)制數(shù)。

int a = 0xab00;
int b = 0x0012;
int c = a|b;//0xab12
<5>按位取反

1,得到全部為1的數(shù)字~0

int n = ~0;// 等同于0xFFFF

2.使數(shù)字的部分清零x& ~7。

int n = 0xFFFF;
n = n & ~7;
<6>按位異或

1.兩個(gè)相等數(shù)異或結(jié)果為0。

int n = 0x1234;
n = n^n;

2.對(duì)同一個(gè)變量?jī)纱萎惢蛳嗤担兓卦怠?/strong>

int a = 0x1234;
int b = 0x1357;
a = a^b;//0x163
a = a^b;//0x1234

3.0和任何數(shù)字(或字符)異或都為任何數(shù)(或字符)

int n=21;
n = n^0;//21

4.應(yīng)用:

  • 把一個(gè)數(shù)據(jù)的某些位翻轉(zhuǎn),即1變?yōu)?,0變?yōu)?
    如要把a(bǔ)的奇數(shù)位翻轉(zhuǎn),可以對(duì)a和b進(jìn)行“按位異或”運(yùn)算,其中b的奇數(shù)位置為1,偶數(shù)位置為0。
  • 交換兩個(gè)值,不用臨時(shí)變量(***)
x ^= y;
y ^= x;
x ^= y;
  • 加密解密
    加密程序(a^b),解密程序是加密程序的逆過(guò)程,這里的加密和解密程序是完全相同的,原因是(a^b)^b=a。

5.例題

[136.只出現(xiàn)一次的數(shù)字]:
思路:每個(gè)數(shù)字全部異或,相同的會(huì)為0,直到最后一個(gè)數(shù)字。
[389.找不同]:
思路:字符也可以異或,相同字符異或?yàn)?.

邏輯運(yùn)算與按位運(yùn)算
1.邏輯運(yùn)算結(jié)果只有0和1兩種值,按位運(yùn)算有多種值。
2.邏輯運(yùn)算相當(dāng)于把所有的非零值都變成1,再按位運(yùn)算。

(2)移位運(yùn)算

在移動(dòng)過(guò)程中相當(dāng)于操作二進(jìn)制數(shù)

No. 操作符 功能
1 << 左移
2 >> 右移
<1>左移

i<<j表示i中所有位向左移動(dòng)j個(gè)位置,右邊填入0
左移一位相當(dāng)于乘以2,兩位乘以4。

1<<1;//2
1<<2;//4
1<<3;//8
<2>右移

i>>j表示i中所有位向右移動(dòng)j個(gè)位置,對(duì)于unsigned類型,左邊填入0;對(duì)于signed類型,左邊填入符號(hào)位。
右移一位相當(dāng)于除以2,再取整。

14>>1;//7
14>>2;//3
14>>3;//1
14>>4;//0

1.應(yīng)用:

  • 循環(huán)移位的實(shí)現(xiàn):
    將一個(gè)無(wú)符號(hào)整數(shù)x的各位進(jìn)行循環(huán)左移n位的運(yùn)算,即把移出的高位填補(bǔ)在空出的低位處。

    b=(a<<n) | (a>>(16-n)) ;
    
  • 求x的絕對(duì)值

 y = x >> 31 ;//二進(jìn)制最高位
 return (x^y)-y ; //or: (x+y)^y 
<3>位移運(yùn)算與乘除運(yùn)算
<4>綜合

求一個(gè)無(wú)符號(hào)數(shù)的二進(jìn)制中1的個(gè)數(shù)。

//就是判斷最低位是否為1(最右邊)
int numof1(int n){
    int count=0;
    while(n){
        if(n & 1){
            ++count;
        }
        n = n >> 1;
    }
    return count;
}

(3)位域

<1>定義

位域是又稱作位段,是把一個(gè)字節(jié)中的二進(jìn)位劃分為幾個(gè)不同的區(qū)域。

<2>作用

節(jié)省空間,有些信息不需要占用一個(gè)完整的字節(jié)。

<3>使用
  • 1.定義位域
    定義位域與結(jié)構(gòu)定義相仿。
struct 位域結(jié)構(gòu)名{ 
    類型 位域名:位域長(zhǎng)度;
};

為了保證位域的可以移植性,成員類型通常為unsigned intint,C99可以使用bool
示例:

struct Byte{
  unsigned int b1:1;
  unsigned int b2:1;
  unsigned int b3:1;
  unsigned int b4:1;
  unsigned int b5:1;
  unsigned int b6:1;
  unsigned int b7:1;
  unsigned int b8:1;
 };
  • 2.位域變量
    定義和使用位域變量與結(jié)構(gòu)體相同。每個(gè)域有一個(gè)域名,允許在程序中按域名進(jìn)行操作。

    struct Byte a;
    
  • 3.位域大小
    整個(gè)結(jié)構(gòu)體的總大小為最寬基本類型成員大小的整數(shù)倍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。