標准C編程-筆記全集


 

C語言的基本概念

 


編寫一個簡單的C程序,后綴名保存為c(本次文件名為a.c)

gcc:對c程序進行編譯和連接;gcc a.c

./a.out:運行程序,輸出程序的結果;其中a是c程序的文件名

 

說明:其實並不是簡單的在a.c文本中輸入幾行代碼就能運行的,其內部實現步驟如下:

1:gcc -E a.c    在c程序中有個#include的頭文件,其中#開始的命令都叫做預處理命令,這里是把文件通過預處理器進行處理

2:gcc -c a.c    對文件進行編譯成機器認識的二進制的格式的目標代碼,然后就出現一個a.o文件

3:gcc a.o       把目標代碼和其他的附加代碼整合在一起,這樣就有一個可以執行的程序a.out

4:執行a.out就能夠得到程序的結果

注意:上面的例子中只使用了gcc a.c  對c程序進行編譯和連接;然后./a.out來輸出,並沒有使用 1,2,3步驟;事實上gcc a.c 這一表達式已經完成編譯和連接兩個步驟,也就是說執行完gcc a.c之后,就已經生成out文件了,下面來了解gcc的命令參數就知道了;


gcc命令的參數講解

-c:只編譯,不連接成為可執行的文件,編譯器只是由輸入的.c等源代碼文件生成.o為后綴的目標文件,通常用於編譯不包含主程序的子程序文件

-o output_filename:確定輸出文件的名稱為output_filename,同時這個名稱不能和源文件同名。如果不給出這個選項,gcc就給出預設的可執行文件a.out,所以上邊例子的gcc a.c執行后就直接生成了out后綴文件,所以就省略了1-2-3步驟(gcc -o b.o a.c  //這里是把a.c文件生成b.o文件,如果不為b文件指定o為后綴,那就只生成b文件)

-g:產生符號調試工具(GNU的gdb)所必要的符號資訊,要想對源代碼進行調試,我們就必須加入這個選項,這個參數一樣可以生成out的后綴文件

-O:對程序進行優化編譯、連接,采用這個選項,整個源代碼會在編譯、連接過程中進行優化處理,這樣產生的可執行文件的執行效率可以提高,但是,編譯、連接的速度就相應地要慢一些;這個參數一樣可以生成out的后綴文件

-O2:比-O更好的優化編譯、連接,當然整個編譯、連接過程會更慢;這個參數一樣可以生成out的后綴文件


-std:選擇編譯程序所使用的c語言的標准,可以使用-std=c89 或者 -std=c99;這個參數一樣可以生成out的后綴文件

 

 

注釋

/**/:多行注釋,一般用來對描述參數,程序信息

//:單行注釋,一般用來描述語法的講解,每行語法對應一行單行注釋


例如:

/*
文件名:×××

作者:×××

年份:×××

版本信息:×××
*/

#include<stdio.h>   //頭文件

 

___________________________________________________________________________________________________________________________________________________________

 

基本類型

char:字符類型

char c1 = "a";    //輸出a

char c2 = "97";   //97是ASCII轉碼 代表a

char c3 = "3";    //輸出3

char c4 = "/"     //printf("%d",c4);  //輸出這個/號的ASCII轉碼

 

 


關鍵字sizeof:在printf中用來測量變量和類型在內存中所占的字節數

sizeof(c);     //變量
sizeof(char);  //類型
sizeof(表達式); //他只關心括號里的類型占的字節大小,不會對表達式進行運算:

int a
short o=9; 
a=sizeof(o=10);   //只輸出o類型占內存的大小,不會輸出o=10;所以a等於o占內存字節的大小,而不是10;


輸出的是字節數值

 

無符號整型:就是沒有負號的整型數,其永遠只有正數

 

___________________________________________________________________________________________________________________________________________________________

 

二進制

 

負十進制轉換為二進制

按位取反--加1

 

負二進制轉換為十進制

減1--按位取反

 


___________________________________________________________________________________________________________________________________________________________

 

判斷運算符


!=    //表示不等於

 


邏輯運算符

&&:與

||:或

!:非

 

短路特性:

int i=0,j=0;
if(++i||++j);   //當運算++i得出結果時,就不會再去運算++j,所以這里++j無效,j還是等於0
if(--i&&++j);     //當運算--i得出結果時,就不會再去運算++j,所以這里++j無效,j還是等於0
printf("%d %d",i,j);   //經過兩次運算,i最后還是0,j還是0

 

___________________________________________________________________________________________________________________________________________________________


位運算

~:按位取反;如果是整數,運算后就是負數,同樣負數運算后得到的是正數;~3=-4; ~4=-5  ;~(n+1)

&:按位與;兩個二進制數進行與運算時,如果兩個數都為真,其結果為真,如果兩位數中有一位為假,其結果為假

|: 按位或:兩個二進制數進行與運算時,只要其中一個數為正,其對應的位就為真

^:按位異或;一真一假為真,另外如果兩個都是真或都是假,其結果都為假


<<:左移

>>:右移

左移一位 相當於乘以2

右移一位 相當於除以2

 

 

 

 

逗號運算符


在C語言中,多個表達式可以用逗號分開,其中用逗號分開的表達式的值分別結算,但整個表達式的值是最后一個表達式的值

假設b=2,c=7,d=5,

a1=(++b,c--,d+3);    //有三個表達式,用逗號分開,所以最終的值應該是最后一個表達式的值,也就是d+3,為8,所以a1=8

a2=++b,c--,d+3;     //這時的三個表達式為a2=++b、c--、d+3,(這是因為賦值運算符比逗號運算符優先級高)所以最終表達式的值雖然也為8,但a2=3。

注意:上面兩個算法沒有連續關系,所以第一次的++b的值不能作為第二次的++b值繼續運算,因此第二次的++b結果是3


還有一種情況:

x=3,y=5;

a=(x+3,y++,x++);   //x+3的結果沒有指定賦值,會被丟掉,這里的a=3  ;x=4 ;y=5

a=(x++,x+3,x+7);  //x++的結果保存下來,x+3的結果丟掉,a=11; x=4

 


___________________________________________________________________________________________________________________________________________________________

 

緩沖區

 

scanf:


#include<stdio.h>
main()
{
int data1,data2;
printf("請輸入兩個數字\n");
scanf("%d",&data1);
scanf("%d",&data2);
printf("%d %d",data1,data2);
}


說明:如果輸入1 3  得到的結果是正確的,但是如果輸入的1 r  得到的是1和0,如果輸入的是r 1 那么兩個輸出都是0

因為,只有當第一個數據讀走時,才會清除這個數據,當第一個是0時,0就不會被清除,導致第二個還是0

這里的0是指錯誤碼。也是變量的初始值

 

解決方法:

scanf("%*[^\n]");   //*忽略讀到的內容,[^\n]任何非\n的字符

將\n之前的所有字符讀走到緩沖區,這樣你輸入的路七八糟的字符都會被讀入到緩沖區

scanf("%*c");   //從緩沖區讀取一個字符,這樣就把亂碼讀走了,不會影響第二個數據的正常輸入

 

 

實例:


#include<stdio.h>
main()
{
int data1=0,data2=0;
printf("請輸入兩個數字\n");
if(scanf("%d",&data1)==0)    //如果=0.就執行上面的解決方法,就能把字符讀走了
{
scanf("%*[^\n]");     //把\n之前的所有字符讀走,包含\n,之后就不能通過\n來完成輸入,每輸入一個數字都要按回車鍵了
scanf("%*c");         //讀取所有字符忽略掉

}
if(scanf("%d",&data2)==0)
{
scanf("%*[^\n]");
scanf("%*c");

}
printf("%d %d",data1,data2);

}

 

 


人工刷新輸出:

fflush(stdin)刷新標准輸入緩沖區,把輸入緩沖區里的東西丟棄[非標准]

fflush(stdout)刷新標准輸出緩沖區,把輸出緩沖區里的東西打印到標准輸出設備上

printf("......");后面加fflush(stdout);可提高打印效率

 

 

___________________________________________________________________________________________________________________________________________________________

 

數組


數組初始化

int a[5]={0};   //這樣就全部都初始化了

int a[30]={[10]=4, [9]=8, [23]=67};   //這樣就對指定的元素初始化,並且沒有初始化的都默認為0,這樣也不存在順序問題

 

 

sizeof:測試數組的大小

sizeof(數組字節)=sizeof(數組元素字節)*元素的個數  如:int a[10];  sizeof(a)=sizeof(a[0])*10

printf("%d",sizeof (a[0]))   //求數組元素的字節

printf("%d",sizeof (a))      //求數組的字節


如:int a[10]    //int占4個字節,a就是40字節,在除以每個元素的字節a[0]=4,就是數組的長度

 


二維數組

int a[3][4]={{0}};  //對所有的元素進行初始化為0

 


___________________________________________________________________________________________________________________________________________________________

 


函數


#include<stdio.h>
#include<stdbool.h>

bool prime(int data)
{
int i=2;

for(;i<data;i++)
{
if(data%i==0)
return false;   //假
}

return true;   //真
}

main()
{
int d=0;
printf("輸入一個數");
scanf("%d",&d);
printf("%s\n",prime(d)?"素數":"合數"); //為真選擇素數,為假選擇合數,輸出是字符串
}

 

 

exit:


函數名: exit()

所在頭文件:#include <stdlib.h>

功 能: 關閉所有文件,終止正在執行的程序。

exit(1)表示異常退出.這個1是返回給操作系統的不過在DOS好像不需要這個返回值

exit(x)(x不為0)都表示異常退出

exit(0)表示正常退出

用 法: void exit(int status);

 

exit()和return的區別:

按照ANSI C,在最初調用的main()中使用return和exit()的效果相同。

但要注意這里所說的是“最初調用”。如果main()在一個遞歸程序中,exit()仍然會終止程序;但return將控制權移交給遞歸的前一級,直到最初的那一級,此時return才會終止程序。return和exit()的另一個區別在於,即使在除main()之外的函數中調用exit(),它也將終止程序。

 

#include <stdlib.h>
#include <conio.h>
#include <stdio.h>
int main(void)
{
char status;
printf("Enter either 1 or 2\n");

status = getch();             //獲取一個字符

exit(status - '0');           //status減去字符0的ASCII碼

printf("%d\n",status);        //字符以%d形式輸出
return 0;
}

 

 

___________________________________________________________________________________________________________________________________________________________

 

變量的類別

 

局部變量:在函數體內定義的變量

生命周期:從定義這個局部變量的地方開始到函數的結束

作用范圍:在定義這個局部變量的函數內

 


static:靜態局部變量,其數值就是上一次函數調用結束之后的數值,還可以修飾函數:static int a(int a);

生命周期:整個程序

作用范圍:和普通局部變量一樣

 

 


全局變量:定義在整個程序中的變量稱為  全局變量

生命周期:整個程序的生命周期之內

作用范圍:整個程序的范圍內都可以訪問

全局變量在定義之后自動初始化為0

 

 

塊變量:定義在 程序塊 里面的變量叫做塊變量

程序塊:使用{ }括起來的一組語句,比如 if 語句里的或者是循環語句等使用到 { } 的地方

生命周期:定義變量的地方開始,程序塊結束的地方消失

作用范圍:程序塊內

 


rand();:隨機生成一個數

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

main()
{
int data = 0;

srand(time(0));   //隨機數發生器的初始化函數,為了防止隨機數每次重復常常使用系統時間來初始化;time(0)取得當前時間(秒的總和)

data = rand()%100;   //初始化隨機數,取100內的數賦值給data

while(t==time(0));  //延遲  讓程序等待這一秒過去

printf("%d\n",data);
 
}

 

 

time(0)%100        //當前秒總和


int t;
t=((time(0)%100)+5);    //當前時間+5賦值給t
printf("%d\n",t);       //打印t的值
while(t>(time(0)%100));   //延遲5秒。當表達式為真,執行循環,為假退出循環,剛好5秒

 

 

register:寄存器,register int a;

告訴編譯器這個變量會被頻繁的使用,請保存到寄存器中

不能對寄存器變量取地址&

有些系統不會把register修飾的變量放到寄存器中,由編譯器決定

 

 

 

const:只讀變量修飾符,變量的數值是不能被改變的

 

 


volatile:一個類型修飾符(type specifier)。它是被設計用來修飾被不同線程訪問和修改的變量。如果沒有volatile,基本上會導致這樣的結果:要么無法編寫多線程程序,要么編譯器失去大量優化的機會;確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值

XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;

對外部硬件而言,上述四條語句分別表示不同的操作,會產生四種不同的動作,但是編譯器就不能像對待純粹的程序那樣對上述四條語句進行優化,只認為XBYTE[2]=0x58(即忽略前三條語句,只產生一條機器代碼)。如果鍵入volatile,則編譯器會逐一的進行編譯並產生相應的機器代碼(四條).

定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:

1). 並行設備的硬件寄存器(如:狀態寄存器)
2). 一個中斷服務子程序中會訪問到的非自動變量
3). 多線程應用中被幾個任務共享的變量

 

 

___________________________________________________________________________________________________________________________________________________________

 

指針 

int *q=NULL;  //指針初始化

注意: 不要返回一個局部變量的指針

char a[10];  a="add";   //這種賦值是錯的

char a[10];  a[0]='b';  //這種是對的,意思為第一個賦值一個字符b

 


___________________________________________________________________________________________________________________________________________________________

 

程序段:

就是一段程序(可以是一個子過程SUB,一個函數FUNCTION(用面向對象的觀點或稱為方法))
程序都是從上到下施行的,那應該什么時候用到程序段

 

 

數據段:

在采用段式內存管理的架構中,數據段(data segment)通常是指用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。

 

 

bss段:

通常是指用來存放程序中未初始化的全局變量和靜態變量的一塊內存區域

 

 


___________________________________________________________________________________________________________________________________________________________

 


堆和棧


堆棧二者特性不同,各有適用場合。首先,最重要的一點,對象生存期不同。棧上的空間,是自動回收的,雖然省事,但如果不想讓它自動回收,就要使用堆來創建對象;在一個函數內部創建一個對象,然后把它的地址傳給函數外層用,就不能在棧上創建這個對象,因為當函數一結束,此對象就被銷毀了,外面訪問它會出錯。而堆的話由於是完全手工創建手工回收,在碰到delete之前這個對象是不會被銷毀的,就可以隨意傳遞。堆上申請空間可以很大,但是棧的空間卻很有限,根據操作系統不同而不同,一般只有1~4MB的大小,如果在棧上申請過大的空間就會出錯。最后,棧上申請空間的速度比堆上快得多,所以如果是函數內部臨時使用的小對象,一般用棧來分配。


棧:它是一種具有后進先出性質的數據結構,也就是說后存放的先取,先存放的后取

堆:堆的存取是隨意,如同我們在圖書館的書架上取書


char *p1;     //全局未初始化區

main()
{
int b;        //棧
char s[] = "abc";    //棧
char *p2;     //棧
char *p3 = "123456";     // 123456\0在常量區,p3在棧上。 
p1 = (char *)malloc(10);  堆
p2 = (char *)malloc(20);  堆

}

棧是系統自動分配空間的,棧上的數據的生存周期只是在函數的運行過程中,運行后就釋放掉,不可以再訪問。

堆則是程序員根據需要自己申請的空間,例如malloc(10);開辟十個字節的空間。而堆上的數據只要程序員不釋放空間,就一直可以訪問到,不過缺點是一旦忘記釋放會造成內存泄露。

 

 

堆內存申請:

new:申請堆內存

malloc:申請堆內存:a=(int*)malloc(100*sizeof(int));

delete []:釋放堆內存空間,指向堆內存首元素的指針;如果申請的是一個堆內存變量,則delete后的[]可以省略;如果申請的是一個堆內存數組,則該[]不能省略

 
#include "iostream.h"
int main()
{
   int size;
   float sum=0;
   int *heapArray;
   cout <<"請輸入元素個數:";
   cin >>size;
   heapArray=new int[size];      //申請堆內存
   cout <<"請輸入各元素:" <<endl;
   for (int i=0;i<size;i++)
   {
      cin >>heapArray[i];
      sum=sum+heapArray[i];
   }
   cout <<"這些數的平均值為" <<sum/size <<endl;
   delete [] heapArray;    //釋放堆內存
   return 0;
}


注意:這里使用到了兩個輸入輸出關鍵字:cout,cin

 


輸出輸入


#include "iostream.h"    //使用頭文件

cout<< "result="<<"result"<<endl;     //endl是輸出結束,后加一個換行符

cin >>size;

___________________________________________________________________________________________________________________________________________________________

 

讀寫一個字符串

puts:輸出一個字符串,可以指定一個文件指針,把字符串輸出到文件中,也可以輸出到顯示設備上

fputs


#include<stdio.h>
main()
{
char a[]="1a3456";/*字符數組也可以定義數字,但輸出的是ascll,並非數字*/
printf("%d\n",++a[0]);/*輸出的是字符1的ASCII碼,1的ASCII 碼為49,++a[0]后等於50*/
printf("%d\n",++a[1]);/*a在ASCII碼中等於97*/
printf("%c\n",++a[1]);/*這里指的字符是可以自身++的,無論是字母還是數字都可以*/
puts(a);              /*定義好的字符數組,在puts中只需寫上數組名就可以輸出*/
puts("123456");        /*沒有定義的字符或字符串就要用“”來括上*/
puts("asdf");/*puts里不允許++,如puts(++b);*/
}

 

 


gets:輸入帶有空格的字符串可以使用函數來進行輸入,但是函數不會計算緩沖區的大小,會造成錯誤,溢出

fgets:這個和上面的一樣,但是卻多了一個可以計算緩沖區大小,保證不會溢出的功能

這個函數也可以把文本的字符串輸入到程序的字符數組中來:、

fgets(str,n,fp);

str:存放字符的變量

n:指定讀取字符的個數

fp:文件指針

 

#include<stdio.h>
main()
{
char a[10];       /*不可以把定義長度的步驟省略*/
fgets(a);         /*類似於puts的使用方法*/
printf("%s\n",a); /*gets函數讀入字符時,包括空格和Entre符,依次存放在數組中*/
}

 


___________________________________________________________________________________________________________________________________________________________

 

字符串操作函數(不支持二維字符數組或二維數組)

 

strlen:求實際長度
strupr:小寫變大寫
strlwr:大寫變小寫


#include<string.h>/*頭文件指定文件包含的庫函數(和include<stdio.h>指定的輸出輸入,函數變量道理一樣)*/

printf("%d\n",strlen(str1));/*求字符串的實際長度,包括空格(也可以這樣(strlen("asdf asd"))*/
printf("%s\n",strupr(str1));/*把小寫換成大寫*/
printf("%s\n",strlwr(str2));/* 把大寫換成小寫。不能小寫換成小寫或大寫換大寫,只能對換,否則輸出結果不變*/

 

 

 

strcpy:復制

strcpy(str1,str1);    //復制規則是由后邊的復制給前邊的字符數組

strcpy(str1,"123");   //如果是定義好的數組給前邊的復制就不用加

 

 

strcat:鏈接)
strcat(str1,str3);    //把后邊的字符或字符數組鏈接到前邊,並放在前邊的字符數組中輸出,輸出順序是(前11后22:1122)
strcat(str1,"llo");   //前邊必須寫成數組名形式,且之前必須定義....這里同樣可以定義字符輸入

 

 

strcmp:比較

#include<stdio.h>
#include<string.h>
main()
{
char str1[]="zzz";    //字母大於空格
char str2[]="zz zz";
printf("%d\n",strcmp(str1,str2));     //前邊大,輸出為正1,后邊大,輸出為負1
char str3[]="zz\0";
char str4[]="zz  ";      //空格大於\0所以str3大
printf("%d\n",strcmp(str3,str4));
char str5[]="zz\0";
char str6[]="zz\0z";       //由於兩個\0相遇所以認為比較結束*/
printf("%d\n",strcmp(str5,str6));
}


說明:這個函數是比較兩個字符串,比較規則是按ascll碼值大小比較,順序是從左至右逐個字符相比較,直到兩組字符的\0同時出現時結束,或遇到不相同的字符時停止。並不是比較兩個字符串誰的長的意思

 

 

___________________________________________________________________________________________________________________________________________________________

 

main()函數的形式


int main(int argc,const char* argv[])    //帶參數的函數

int main(viod);    //無參數形式

main()    //C90標准允許這種形式,但是C99標准不允許。因此即使你當前的編譯器允許,也不要這么寫


堅持使用標准的意義在於:當你把程序從一個編譯器移到另一個編譯器時,照樣能正常運行。


C編譯器允許main()函數沒有參數,或者有兩個參數(有些實現允許更多的參數,但這只是對標准的擴展)。這兩個參數,一個是int類型,一個是字符串類型。第一個參數是命令行中的字符串數。按照慣例(但不是必須的),這個int參數被稱為argc(argument count)。大家或許現在才明白這個形參為什么要取這么個奇怪的名字吧,呵呵!至於英文的意思,自己查字典吧。第二個參數是一個指向字符串的指針數組。命令行中的每個字符串被存儲到內存中,並且分配一個指針指向它。按照慣例,這個指針數組被稱為argv(argument value)。系統使用空格把各個字符串格開。一般情況下,把程序本身的名字賦值給argv[0],接着,把最后的第一個字符串賦給argv[1],等等。
現在我們來看一個例子:


   #include "stdio.h"
    int main(int argc, char *argv[])
    {
        int count;
        printf("The command line has %d arguments: /n",argc-1);
        for(count=1;count<argc;count++)
            printf("%d: %s/n",count,argv[count]);
        return 0;
    }


編譯運行,在命令行輸入c I love you 回車,下面是從命令行運行該程序的結果:

    The command line has 3 arguments: 
    1:I
    2:love
    3:you
    從本例可以看出,程序從命令行中接受到4個字符串(包括程序名),並將它們存放在字符串數組中,其對應關系:
    argv[0]  ------>    c(程序名) 
    argv[1]  ------>    I 
    argv[2]  ------>    love
    argv[3]  ------>    you
    至於argc的值,也即是參數的個數,程序在運行時會自動統計,不必我們操心。

 

說明:目前我使用的編譯器不支持運行代碼后,能輸入main的參數,只需要理解就行了

 


___________________________________________________________________________________________________________________________________________________________

 


指向數組的指針變量的運算“++,--”


*p++等價於*(P++),先得到p所指向的變量,再使指針p+1;

*(++p)等價於*++p,先使p加1,再取出*p的值。

(*p)++,表示p所指向的元素值加1,即a[0]++;

 


___________________________________________________________________________________________________________________________________________________________


宏定義

 

1:也可以對符號進行宏定義


2:帶參數的宏:如下

#include<stdio.h>
#define b(x) (x)<='z'&&(x)>='a'?(x)-('a'-'A'):(x)

int main(void)
{
char c;

printf("請輸入一個字符\n");
scanf("%c",&c);

printf("%c\n",b(c));
return 0;
}

 

說明:帶參數的宏:b(x)  b是標識符,對應於宏體(x)<='z'&&(x)>='a'?(x)-('a'-'A'):(x);括號里的x是參數,可以有多個參數,意思是將來把宏體的x的參數替換

 


 
多個參數的宏

#include<stdio.h>
#define b(x,V) (V)<='z'&&(V)>='a'?(V)-('a'-'A'):(V)    //這里出現兩個宏參數

int main(void)
{
char c;

printf("請輸入一個字符\n");
scanf("%c",&c);

printf("%c\n",b(,c));      //要把宏參數的第二位V替換,所以第一位可以留空不填,但是第二位要填上實參
return 0;
}

 


___________________________________________________________________________________________________________________________________________________________

 

宏運算符


在C語言中相鄰的字符串字面量會被合並:

printf("i/j"" = %d/n", i/j);

printf("i/j = %d/n", i/j);

 

 

#:運算符將一個宏的參數轉換為字符串字面量:


#define PRINT_INT(x) printf(#x " = %d/n", x);   //x之前的#運算符通知預處理器根據PRINT_INT的參數創建一個字符串字面量,也就是說參數x代表字符,后面的x則是普通的宏參數


PRINT_INT(hi)    //調用宏hi就是參數

printf("hi" " = %d/n", x);   //在宏的參數中x代表字符串,現在把字符串hi傳到宏中就成了這樣,這是合法的,相同於 printf("i/j = %d/n", i/j);  //后面的i/j是一個公式

 

 

 

##:運算符可以將兩標識符“粘”在一起,成為一個標識符,被稱為粘合符。如果其中一個操作數是宏參數,“粘合”會在當前形式參數被相應的實際參數替換后發生。

#define STRCPY2(a, b)   strcpy(a##_p, b##_p)

STRCPY2(h, s);     //變成以下的

strcpy(h_p, s_p)    //結果

 

#define MAX(type)  type max_type##()

MAX(int);    //變成int max_int(),第一個正常被替換,第二個被粘合在一起,正常替換的時候不能 有其他參數,所以一旦有其他參數,就能合並,這時要使用##

 

 


終止宏定義的作用域

#undef IP  //終止PI作用域

 


----------------------------------------------

 

應用例子:


#include <stdio.h>
#include <string.h>
#define STRCPY1(a, b)   strcpy(a##_p, #b)
#define STRCPY2(a, b)   strcpy(a##_p, b##_p)
int main(void)
{
char var1_p[30];
char var2_p[30];
STRCPY1(var1, ok);   //第一個宏參數var1傳遞給a,變成var1_p;ok傳給b,變成ok;var1_p[30]=ok;
STRCPY2(var2, var1); //把var1_p賦值給var2_p

printf("var1_p= %s\n", var1_p);
printf("var2_p= %s\n", var2_p);    //結構兩個都是ok
return 0;
}

 


___________________________________________________________________________________________________________________________________________________________

 


預定義的宏

 

#include <stdio.h>
#include <stdlib.h>
void why_me();
int main()
{
    printf( "%s\n", __FILE__ );           //當前正在編譯的源程序的名稱
    printf( "%s\n", __DATE__ );           //編譯程序的日期
    printf( "%s\n", __TIME__ );           //編譯程序的時間
    printf( "%d\n", __LINE__ );           //當前正在編譯的程序的行號
    printf( "%s\n", __STDC__?"Y":"N" );   //判斷編譯器是否符合C標准
    return 0;
}


char *a=__FILE__;   //也可以


更多請訪問網絡:


___________________________________________________________________________________________________________________________________________________________

 

條件編譯

 


說明:條件編譯就是根據預處理器的執行結果 來包含或者排除某一段程序

 

#ifdef DEBUG:如果宏定義中定義了DEBUG,就對這段程序編譯。如果沒有宏定義,或者宏定義了別的字符,這段程序將不被執行。

#endif:由於程序中出現了#ifdef,  所以必須有endif作為程序結束。

 

#ifndef DEBUG:如果宏未定義DEBUG,則執行這段程序,

#endif:由於程序中出現了#ifndef,  所以必須有#endif作為程序結束。

 

#if M:如果宏定義的M非0則為真,-1也為真,為真時則執行這段程序

#endif:到這個位置結束,#endif下面可以有輸出也可以沒有,但endif一定要寫,否則無結束條件則會出錯。

 

#else:可以用在#ifndef或#ifdef中,如果#ifndef或#ifdef條件不成立則執行#else

 

___________________________________________________________________________________________________________________________________________________________

 

多文件程序的編寫


常見的程序都是由多個源文件和多個頭文件組成


程序分為多個源文件好處:

相關的變量和函數放在一個源文件中,程序的結構更加清晰

可以對每一個源文件分別進行編譯,如果修改一個源文件只需要編譯這個源文件,節約了時間

函數放在多個文件中有利於代碼的復用

 

多個源文件編譯:首先對每一個源文件只編譯不連接 gcc -c  生成  .o  文件,如果要連接:gcc *.o;

也可以使多個源文件編程一個out執行文件:a.c   b.c ; gcc a.c  b.c   ; 生成  a.out ,只需要運行a.out就能使兩個源文件一起使用  //a包含了頭文件和函數,b只包含函數,


___________________________________________________________________________________________________________________________________________________________

 

共享全局變量聲明:

 


extern:原理很簡單,就是告訴編譯器:“你現在編譯的文件中,有一個標識符雖然沒有在本文件中定義,但是它是在別的文件中定義的全局變量,你要放行!”

//A.cpp
extern int i;
int main()
{
i = 100; //試圖使用B中定義的全局變量
}


//B.cpp
int i;

順利通過編譯,鏈接。

 

 

___________________________________________________________________________________________________________________________________________________________

 

自定義頭文件

 

可以共享全局變量,共享宏,變量,函數,等:但是只聲明不定義;宏 #define U  123  這類型不屬於定義,應該是聲明


先創建一個頭文件 a.h

#ifndef TEE    //防止頭文件再次被本頭文件引用,或者是其他源文件中如果定義了宏TEE,也不可以再次引用這個頭文件,否則錯誤
#define TEE

#include<stdio.h>

#define U  123

#endif        //如果應用已經包含,就不能再引用上面,要引用這里的頭文件

#include<stdio.h>

#define B  123

int a;    //不能省略 ; 號

int b()
{
//說明這個函數的作用

};

把頭文件和源文件放在當前(同一個目錄)目錄中,源文件在引用頭文件時只需要引用#include "a.h";

頭文件可以聲明變量:int a; 但是不能定義變量:int a=5; 錯誤的

也可以聲明函數等,但都只能聲明,不能對其定義算法,如果要實現算法,必須要一個mian函數入口,並且關聯頭文件,建議實現算法單獨放在另一個 .c 文件
___________________________________________________________________________________________________________________________________________________________

 

文件嵌套包含

 


#include<stdio.h>
#include"22_2f3.cpp"
#include"22_2F2.cpp"
main()
{int a=2,m;
 m=b(a);
 printf("\nm=%d",m);
}

 


22_2F2://由於文件類型不是頭文件,所以這個文件的尾疊是.cpp
int b(int i)
{
int j,k;
j=i+4;
k=c(j);
k=2*k;
printf("\nj=%d,k=%d",j,k);
return k;
}

 


22_2f3://由於文件類型不是頭文件,所以這個文件的尾疊是.cpp
int c(int x)
{int y;
y=x*x;
printf("\ny=%d",y);
return y;
}

 

第一個程序包含了2,但是2中包含了3.所以要先把3包含進來再把2包含,順序不能錯,因為2沒有3,2就沒有結果,2沒有結果,1就沒有結果,整個過程就會出錯。所以先是#include"22_2f3.cpp"再到#include"22_2F2.CPP"。

注意:三個文件都是在當前目錄才行,否則需要指明路徑,路徑的格式到網上查去

 

___________________________________________________________________________________________________________________________________________________________


makefile    條件編譯   組合編譯

 

一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作

makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令

make是一個命令工具,是一個解釋makefile中指令的命令工具

makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率

 

 

關於程序的編譯和鏈接
——————————

編譯:編譯器需要的是語法的正確,函數與變量的聲明的正確。對於后者,通常是你需要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),只要所有的語法正確,編譯器就可以編譯出中間目標文件。一般來說,每個源文件都應該對應於一個中間目標文件:在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File


鏈接:主要是鏈接函數和全局變量,所以,我們可以使用這些中間目標文件(O文件或是OBJ文件)來鏈接我們的應用程序。鏈接器並不管函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,由於源文件太多,編譯生成的中間目標文件太多,而在鏈接時需要明顯地指出中間目標文件名,這對於編譯很不方便,所以,我們要給中間目標文件打個包,在Windows下這種包叫“庫文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。


總結:源文件首先會生成中間目標文件,再由中間目標文件生成執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。如果函數未被聲明,編譯器會給出一個警告,但可以生成Object File。而在鏈接程序時,鏈接器會在所有的Object File中找尋函數的實現,如果找不到,那到就會報鏈接錯誤碼(Linker Error),在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,鏈接器未能找到函數的實現。你需要指定函數的Object File

 


Makefile的規則
——————————
target ... : prerequisites ...
command


target:一個目標文件,可以是Object File,也可以是執行文件

prerequisites:要生成target所需要的目標文件

command:make需要執行的命令


這是一個文件的依賴關系,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行

 

 


示例:如果一個工程有3個頭文件,和8個C文件


edit : main.o kbd.o command.o display.o /        //要生成一個edit文件,依賴文件都是 .o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /    //執行指令edit之后編譯這些 .o 文件生成一個edit文件
insert.o search.o files.o utils.o                //可以看到這些執行執行的命令和依賴文件都是 .o也就是說要下面的文件生成相應的 .o文件,段指令才有效

main.o : main.c defs.h     //要生成一個main.o,依賴文件是 main.c defs.h
cc -c main.c               //執行的指令是編譯main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit *.o
 

 

說明:反斜杠(/)是換行符的意思

在linx的shell中,我們可以把這個內容保存在文件為“Makefile”或“makefile”的文件中,然后在該目錄下直接輸入命令“make”就可以生成執行文件edit。如果要刪除執行文件和所有的中間目標文件,那么,只要簡單地執行一下“make clean”就可以了。

如果要生成目錄文件:

make main.o

make kbd.o

make files.o

make clean      //沒有被第一個目標文件直接或間接關聯,那么它后面所定義的命令將不會被自動執行,需要顯式執行make clean,以此來清除所有的目標文件,以便重編譯

 


注意:編譯的使用依賴文件需要有頭文件 .h ,但是編譯時只需要源文件 .c


在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號后面的那些 .c 文件和 .h文件。每一個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關系的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。

在定義好依賴關系后,后續的那一行定義了如何生成目標文件的操作系統命令,一定要以一個Tab鍵作為開頭然后換行。記住,make並不管命令是怎么工作的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那么,make就會執行后續定義的命令。

這里要說明一點的是,clean不是一個文件,它只不過是一個動作名字,有點像C語言中的lable一樣,其冒號后什么也沒有,那么,make就不會自動去找文件的依賴性,也就不會自動執行其后所定義的命令。要執行其后的命令,就要在make命令后明顯得指出這個lable的名字。這樣的方法非常有用,我們可以在一個makefile中定義不用的編譯或是和編譯無關的命令,比如程序的打包,程序的備份,等等。


2、如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,並把這個文件作為最終的目標文件。
3、如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執行后面所定義的命令來生成edit這個文件。
4、如果edit所依賴的.o文件也不存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
5、當然,你的C文件和H文件是存在的啦,於是make會生成 .o 文件,然后再用 .o 文件生命make的終極任務,也就是執行文件edit了。


於是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如file.c,那么根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關系后面所定義的命令),於是file.o的文件也是最新的啦,於是file.o的文件修改時間要比edit要新,所以edit也會被重新鏈接了


也就是說,只要執行命令“make”就可以生成執行文件edit,就會自動執行后面所定義的命令來生成edit這個文件,而命令使用到的 .o文件如果沒有,就會去自動執行下面的命令生成所有有關系的依賴文件,因此當生成一個edit文件后,便會跟着生成下面的main.o  kbd.o  files.o...文件


而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,並且,edit會被重鏈接
 


___________________________________________________________________________________________________________________________________________________________

 

靜態全局變量

 

static:它是在內存的靜態存儲區中占有永久的存儲單元

當使用 static 聲明符來說明全局變量時,稱這個全局變量為靜態全局變量,只能被本編譯單位使用,不能被其他編譯單位使用,即使其他文件使用extern也不行。

在定義全局變量時,前面的存儲類別定義為static,這就是靜態全局變量,即使在函數調用結束后,也不消失且保留原值(上一次的值),下次調用時,值不變(為上次的最后值)

 


靜態全局函數    //這個函數只能在本文件中使用,和靜態全局變量類型性質

 

___________________________________________________________________________________________________________________________________________________________

 

typedef:自定義類型


在計算機編程語言中用來為復雜的聲明定義簡單的別名,與宏定義有些差異。它本身是一種存儲類的關鍵字,與auto、extern、mutable、static、register等關鍵字不能出現在同一個表達式中。
 

它有助於創建平台無關類型,甚至能隱藏復雜和難以理解的語法 。使用typedef可編寫出更加美觀和可讀的代碼。所謂美觀,意指typedef能隱藏笨拙的語法構造以及平台相關的數據類型,從而增強可移植性以及未來的可維護性。


typedef char Line[81]; //為char起一個別名為line,長度是81

此時Line類型即代表了具有81個元素的字符數組,使用方法如下:

Line text, secondline;

 

 

以前學C++的時候,經常遇到這種情況:

typedef  struct
{
short level;
unsigned flags:
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned ar *curp;
unsigned istemp;
short token;
}FILE;

然后就能夠使用 FILE 定義一個文件類型 FILE file1;

 


其實就是給類型起一個別名,然后用這個別名去定義一個變量:

typedef int s_int; /*把int替換成s_int*/
s_int i,t;


typedef unsigned int u_int:
u_int k,m;

typedef int* number; /*定義指針類型*/
nuber n;

 


常量指針和指針常量


typedef char* const pstr       //指針常量,指針指向一個常量地址,用來存放常量的
typedef const char* pstr;      //常量指針,指針指向一個字符地址,用來存放字符地址,並且這樣只能存放一個常量地址

注意指向常量的指針所指向的內容不能改變,但是可以改變其所指向的地址。

 

___________________________________________________________________________________________________________________________________________________________

 


結構體


struct:結構體聲明修飾符

結構體是不同數據類型的集合,每個成員都有自己的名字,可以通過名字來訪問成員

 

結構體變量的定義

   struct stu  /*結構體名稱與int特性一樣*/
    {
      int num; /*成員*/
      char *name;
      char sex;
      float score;
    } ;
   
   struct stu boy1,boy2; /*變量名*/

------------------------------------------------

struct stu
    {
      int num;
      char *name;
      char sex;
      float score;
    } boy1,boy2;/*直接定義結構體變量名*/

 


   struct   /*這里沒有定義結構體名稱,這樣就意味着,除了boy1,boy2兩個變量名稱可以使用之外,就不可以再定義了,因為定義是要使用結構體名稱的*/
    {       
      int num;
      char *name;
      char sex;
      float score;
    } boy1,boy2;

 

------------------------------------------------


結構體嵌套


struct date
{
int month;  /*月*/
int day;    /*日*/
int year;   /*年*/
};/*這里並沒有定義結構體變量名*/

struct book
{
long num;           /*書的編號*/
char name[20];      /*書名*/
struct date time;   /*詳細見下*/
char author;        /*作者*/
}book1,book2;       /*結構體名稱*/


----------------------------


結構體初始化

 

{

struct date
{
int month;
int day;
int year; 
};


struct book
{
long num;      
char name[20]; 
struct date time;
char author[10];  
}book2,book1={50,"nihao",1,1,1,"wsii"};    //初始化

book2=book1;  //相同的結構體可以相互賦值


strcpy(book1.name,"nihao");   //復制

printf("%d   %d\n",book1.time.day, book1.num);

}

由於struct date time中的time是struct date的結構體變量,所以在調用book結構體變量time時,只需book1.time.day就行了

 

----------------------------

為結構體申請一個堆

pb=(TYPE*) malloc(LEN);  TYPE *student;

 

----------------------------


為結構體起別名:

typedef  struct
{


}別名;

 

typedef struct a
{

 
}別名;

注意:這里兩個意思都一樣,上邊的只能用別名來定義變量,下邊的可以用別名也可以用a來定義變量

 

___________________________________________________________________________________________________________________________________________________________

 

結構體作為函數的參數與返回值


結構是按值傳遞的。也就是說整個結構的內容都復制給了形參,即使某些成員數據是一個數組

#include "iostream.h"
struct student
{
   int idNumber;
   char name[15];
   int age;
   char department[20];
   float gpa;
};
void display(student arg);//結構作為參數
int main()
{
   student s1={428004, "Tomato",20, "ComputerScience",84.5};//聲明s1,並對s1初始化
   cout <<"s1.name的地址" <<&s1.name <<endl;
   display(s1);
   cout <<"形參被修改后……" <<endl;
   display(s1);
   return 0;
}
void display(student arg)
{
   cout <<"學號:" <<arg.idNumber <<"姓名:" <<arg.name <<"年齡:" <<arg.age <<endl <<"院系:" <<arg.department <<"成績:" <<arg.gpa <<endl;
   cout <<"arg.name的地址" <<&arg.name <<endl;
   for (int i=0;i<6;i++)//企圖修改參數的成員數據
   {
      arg.name[i]='A';
   }
   arg.age++;
   arg.gpa=99.9f;
}

通過上面這個程序,我們發現在函數中修改形參的值對實參是沒有影響的。並且通過輸出變量s1和參數arg的成員數據name所在地址,我們可以知道兩者是不相同的,即整個name數組也復制給了參數arg。

 

一般情況下,函數只能返回一個變量。如果要嘗試返回多個變量,那么就要通過在參數中使用引用,再把實參作為返回值。然而,這種方法會導致一大堆參數,程序的可讀性也較差。
當結構出現以后,我們可以把所有需要返回的變量整合到一個結構中來,問題就解決了:

#include "iostream.h"
struct student
{
   int idNumber;
   char name[15];
   int age;
   char department[20];
   float gpa;
};
student initial();          //初始化並返回一個結構,是一個構造函數,這里調用了構造函數的過程

void display(student arg);  //聲明一個函數

int main()
{
   display(initial());      //輸出返回的結構
   return 0;
}
void display(student arg)
{
   cout <<"學號:" <<arg.idNumber <<"姓名:" <<arg.name <<"年齡:" <<arg.age <<endl <<"院系:" <<arg.department <<"成績:" <<arg.gpa <<endl;
}
student initial()   //構造函數的實現方法,並且把結構返回給結構體
{
   student s1={428004, "Tomato",20, "ComputerScience",84.5};//初始化結構變量
   return s1;//返回結構
}

 


---------------------------------------------


結構體指針作為函數參數

 


#include "iostream.h"
#include<stdio.h>

struct student    //定義一個結構體
{
   int idNumber;
   char name[15];
   int age;
   char department[20];
   float gpa;
};


void display(student  *arg);//結構指針作為參數

void di(student a);  //結構體作為參數


int main()
{
   student s1={428004, "Tomato",20, "ComputerScience",84.5};    //聲明s1,並對s1初始化
   cout <<"s1.name的地址" <<&s1.name <<endl;
   cout <<"學號:" <<s1.idNumber <<"姓名:" <<s1.name <<"年齡:" <<s1.age <<endl <<"院系:" <<s1.department <<"成績:" <<s1.gpa <<endl;

   student *s2=&s1;    //定義一個結構體指針並賦值

   display(s2);        //結構體指針作為函數參數,這里的s2是一個指針,就不需要再使用&符,如果不是指針,就要使用&

   cout <<"形參被修改后……" <<endl;

   di(s1);
   return 0;
}
void display(student *arg)
{
for (int i=0;i<6;i++)//企圖修改參數的成員數據
   {
   arg->name[i]='A';         //結構體指針調用成員的時候只需要使用 -> 就可以調用;或者是(*arg).name[i] 也可以
   }
   arg->age++;
   arg->gpa=99.9f;
}

void di(student arg)
{
   cout <<"學號:" <<arg.idNumber <<"姓名:" <<arg.name <<"年齡:" <<arg.age <<endl <<"院系:" <<arg.department <<"成績:" <<arg.gpa <<endl;
   cout <<"arg.name的地址" <<&arg.name <<endl;

}

 

 

___________________________________________________________________________________________________________________________________________________________

 


結構體的對齊與互補

 

char  1字節

short 2字節

int   4字節

double  4字節


如果一個結構體里面有以下成員


char----    每個是4

int-----

char----

整個結構體的長度必須保持為內部最長成員的整數倍,可以使用sizeof來查看是不是這樣


#include<stdio.h>
struct student
{
char v;
int a;
char g;
}o;


int main()
{
printf("%d\n",sizeof(o));   
}

 


如果是以下排列:


int---------

char-----

char-----
看起來是這樣的,長度是8,每個char是2

 

___________________________________________________________________________________________________________________________________________________________

 

結構體位段


位段可以指定每一個成員的大小,節約內存

大小是指多少個二進制位  ;1字節占8二進制位;一個二進制位占0.125字節

但是當結構體的長度為0.75字節時,使用sizeof來查看是不能輸出0.75,得到的結果是1

 

如果是double 定義的類型小數,就可以輸出

double h=0.53;
printf("%0.2f\n",h);

輸出結果是0.530000

 

___________________________________________________________________________________________________________________________________________________________

 

union:聯合體/共用體


共用體是由用戶定義的數據類型。幾種不同類型的變量存放到同一段內存單元中,就構成一個共用體。共用體的幾個成員共占用同一個內存空間。一次給幾個成員賦值時,前面的成員值依次被后面的成員值覆蓋。共用體變量中起作用的成員是最后一次存放的成員。

 

共用體的定義和結構體形式一樣

union  data
{
int i;
float f;
char  ch;
};
data data1,data2;

 

union  data
{
...
}data1,data2;

 

union
{
...
}data1,data2;  /*不能再定義另外的共用體類型變量*/

 


---------------------

共用體對齊

 

char[9] int double;  字節數是9  4  4   //用運算符sizeof測試其大小為16.

這是因為這里存在字節對齊的問題,9既不能被4整除,也不能被8整除。因此補充字節到16,這樣就符合所有成員的自身對齊了。

 

 


共用體變量,指針的引用和結構體引用形式一樣


共用體的引用與結構體的引用形式相同;

data1.i;
data1.f;
data1.ch;


可以在定義共用體變量時對變量中的第一個成員賦值,且賦值要放在{}內。


union  data
{
int i;
float f;
char  ch;
}data1,data2={2008};

 

一個共用體變量不是同時存放多個成員的值,而是只能存放其中的一個值,也就是最后賦給他的值。例如:data1.i=200;data1.f=2.66;data1.ch='china';所以以下的輸出是錯誤的:printf("%d,%f,%c",data1.i,data1.f,data1.ch);因為此時data1中只有最后被賦給的ch的值'china'。

 


共用體指針


union data *pt,x;
pt=&x;
pt->i=200;
pt->f=2.66;
pt->='china';

兩個同類型的共用體變量之間賦值是可以的:data1=data2;這時兩者中的內容完全相同。

不能把共用體變量作為函數參數,也不能使函數返回共用體變量,但是可以使用指向共用體變量的指針(與結構體變量這種用法相仿)。共用體類型可以出現在結構體類型定義中,也可以定義共用體數組。反之,結構體也可以出現在共用體類型定義中,數組也可以作為共用體的成員。

 


#include<stdio.h>
#include<stdlib.h>
struct           /*定義結構體*/
{
char name[20];
int age;
char job;
union            /*結構體包含共用體*/
{
int clas;
char office[10];
}department;
}guy[3];

main()
{
int i,j;
for(i=0;i<3;i++)         /*為結構體和共用體賦值*/
{
printf("Input the name,age,job and department of everyone; \n");
scanf("%s %d %c",guy[i].name,&guy[i].age,&guy[i].job);    /*無論是結構體還是共用體,凡是數組的成員都不要用&運算符*/
if(guy[i].job=='s')
{
scanf("%d",&guy[i].department.clas);
}
else if(guy[i].job=='t')
{
scanf("%s",guy[i].department.office);
}
}
printf("name\tage job class/office\n");
for(i=0;i<3;i++)   /*輸出數據*/
{
if(guy[i].job=='s')  /*如果是學生,輸出的是部門是D否則是S*/
{
printf("%s\t%3d%3c\t%d\n",guy[i].name,guy[i].age,guy[i].job,guy[i].department.clas);
}
else
{
printf("%s\t%3d%3c\t%s\n",guy[i].name,guy[i].age,guy[i].job,guy[i].department.office);
}
}
}


共用體變量也可以定義成數組或指針。定義為指針時,可以用 ->符號,此時就可以用“共用體名->成員名”來訪問了。

 

___________________________________________________________________________________________________________________________________________________________


enum:枚舉類型

枚舉類型屬於常量

 


枚舉類型就是將變量或對象可能存在的情況,也可以說是可能的值一一例舉出來。


枚舉的定義

enum 枚舉名
{
標識符,
標識符,
標識符,
標識符
}枚舉變量;


每個標識符稱為枚舉常量,他們構成這個枚舉類型的所有可能的取值集合,該枚舉類型的變量的取值只能限於該集合,若省略“=整形常量”時則從第一個標識符開始,順次賦給標識符0,1,2...。但當枚舉中的某個成員賦值后,其后的成員按依次加1的規則確定其值,如下:


enum numbers{a,b,c,d,e,f}num1;  //因為枚舉變量num1中的枚舉常量都沒有被賦值,則從a開始,常量的值分別為1,2,3,4,5


enum numbers(類型名){a=2,b,c=8,d,e,f}num1;  //從a開始,常量的值分別為2,3,8,9,10,11

枚舉元素是常量,一旦定義就不可以在程序中通過語句更改。可將枚舉常量賦值給枚舉變量,但不能給枚舉元素常量賦值。而且枚舉常量也不是字符串,而是整形量。

 

 

 

直接給枚舉變量賦值,而非常量賦值給枚舉常量,便要強制轉換。

num1=(enum numbers)3;   相當於num1=3;   //不能夠再外部對枚舉成員賦值


進行比較:if(five!=4)  if(one<three);

 


 enum numbers{one=1,two,three,four,five};     //numbers相當於一個類型,{}里成員是numbers的特性組數值
 enum numbers num1,num2,num3;             //num1相當於numbers的變量,不能把numbers賦值給變量num1,可以把枚舉成員賦值給num1,前提是num1是numbers枚舉類型的變量

實際上numbers{one=1,two,three,four,five}成員就是用來為變量num1,num2...賦值的

 

枚舉類型的引用

輸出1-5的乘法列表
 #include<stdio.h>
 main()
 {
 enum numbers{one=1,two,three,four,five};  //定義枚舉常量
 enum numbers num1,num2,num3;              //定義枚舉變量
 int i,j,k;
 num1=one;               //把常量賦給變量
 for(i=num1;i<=five;i++)
  {
 for(num2=one,j=num2;j<=i;j++)
  {
  k=i*j;
  num3=(enum numbers)k;  /*強制轉換*/
  printf("%d*%d=%d\t",i,j,num3);
  }
  printf("\n");
  }
  }

 

___________________________________________________________________________________________________________________________________________________________

 

動態內存分配

動態內存是由用戶手動分配的,不同於靜態內存,是由系統自動分配的;動態內存=堆;靜態內存=棧

 

void *calloc(size_t nmemb,size_t size);     //分配內存塊,對內存進行清零

void *realloc(void *ptr,size_t size);   //調整先前已經分配的內存塊大小

void *malloc(size_t size);      //分配內存塊,不會對分配的內存進行清零

void *free(void *ptr);    //釋放動態分配的內存

 

void* 通用指針(無類型指針),本質就是地址、


分配成功:返回分配的內存的指針

分配失敗:返回NULL

 

___________________________________________________________________________________________________________________________________________________________

 

const 與指針


const int *p;    //指針指向的數據是只讀的,指針本身可以改變

int *const p;    //指針指向的數據是可以修改的,指針本身是只讀的

const int *const p;  //指針指向的數據是只讀的,指針本身是只讀的

 

___________________________________________________________________________________________________________________________________________________________


二級指針


int **q=&q;   //q指針,指向一個指針

任何值都有地址 ,一級指針的值雖然是地址,但這個地址做為一個值亦需要空間來存放,是空間就具有地址 ,這就是存放地址這一值的空間所具有的地址,二級指針就是為了獲取這個地址


指針交互可以用二級指針實現:

 

___________________________________________________________________________________________________________________________________________________________

 

指針數組

 

int *q[5];

本質上就是數組,數組中的每一個元素都是指針

 

 

 

數組指針


int (*q)[5];    // &q[5];數字指針在輸出時,需要取址符

本質上就是指針,這個指針指向一個數組

 

 

 

函數指針


int *q();   //返回一個指針

int (*f)()  //指向一個函數,函數返回值為int


本質上就是指向函數的指針

 

 


通用指針:void *

#include<stdio.h>
main()
{
void *z,*c;
int a=6;
char b='d';
z=&a;
c=&b;
printf("%d %c\n",*(int*)z,*(char*)c);  //輸入前需要強制轉換
}

 

 

注意以下賦值的差異


int a[3][4]; int (*q)[4];  q=&(a[0]);  q[0]::a[0][0]    q[1]::a[1][0]    q[2]::q[2][0]   //正確

int a[3][4]; int (*q)[4];  q=&a;       q[0]::a[0][0]    q[1]::a[0][1]    q[2]::a[0][2]   //未得到預期結果

int a[4];    int (*q)[4];  q=&a;       q[0]::a[0]       q[1]::a[8]       q[2]::a[12]     //輸出錯誤

 

___________________________________________________________________________________________________________________________________________________________


文件操作:

 

每一個打開的文件在內存里面,都有一個結構體來保存文件的信息:大小,位置,修改時間,權限等等

 

這個結構體是系統定義的:

typedef  struct
{
short level;
unsigned flags:
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned ar *curp;
unsigned istemp;
short token;
}FILE;


FILE類型是用來存放文件的信息;FILE f[5];  用來存放5個文件的信息。

FILE *fp; fp是一個指向FILE類型結構體的指針變量。

 

 

文件的操作函數:


fopen:打開文件

FILE *fp;  fp=fopen("c:\file.dat","r");(文件名,使用文件方式);

或者char f1[10]="c:\file.dat";  fp=fopen(f1,"r");

功能:按使用文件方式打開一個指定文件,函數返回一個指向FILE類型的指針,打開失敗返回NULL

 

fclose:關閉文件。

fclose(文件指針);

fclose(fp);   //返回值為0成功,否則為失敗

功能:關閉文件指針所指向的文件,使文件指針與文件脫離關系。

 

 

文件的使用方式:


"r" (只讀)   為輸入打開一個文本文件,要文件已經存在

"w" (只寫)   為輸出打開一個文本文件,文件可以不存在,存在會覆蓋

"a" (追加)   向文本文件尾增加數據,如果不存在就新建一個

"rb" (只讀)   為輸入打開一個二進制文件

"wb" (只寫)   為輸出打開一個二進制文件

"ab" (追加)   向二進制文件尾增加數據

"r+" (讀寫)   為讀/寫打開一個文本文件,文件的頭部開始

"w+" (讀寫)   為讀/寫建立一個新的文本文件,覆蓋

"a+" (讀寫)   為讀/寫打開一個文本文件,追加

"rb+" (讀寫)  為讀/寫打開一個二進制文件,文件的頭部開始

"wb+" (讀寫)  為讀/寫建立一個新的二進制文件,覆蓋

"ab+" (讀寫)  為讀/寫打開一個二進制文件,追加


成功:返回一個指向打開文件的文件指針

失敗:返回NULL

 

----------------------------------------------------------------------------------------------


讀寫一個字符


fputc(ch,fp);   可以使用fputc函數在內存中輸入一個字符到指定的文件。

功能:向一個打開的文件寫入一個字符。ch為待輸出的字符,可以是一個字符常量或一個字符變量。

 

fgetc:從文件中讀取一個字符出來

ch=fgetc(fp);   可以使用fgetc函數在指定的文件中輸出一個字符到顯示器。但文件必須是以只讀或讀寫方式打開。
如果在執行的過程中出錯,則返回EOF(其值為-1);成功返回讀到的字符

ch=fgetc(fp);
while=(ch!=EOP)/*判斷讀寫的字符是否出錯,或者是輸入結束*/
{
putchar(fp);
ch=fgetc(fp);
}

 

實例:

#include<stdio.h>
main()
{
FILE *fp=NULL;
char str[10]="weather";      //創建字符串
if((fp=fopen("a.txt","w"))==NULL)    //創建文件
{
printf("error\n");
return 0; 
}
printf("success\n");
int i=0;
while(str[i]!='\0')  
{
fputc(str[i],fp);  //向文件中寫入字符
i++;
}
fclose(fp);    //關閉文件
return 0;
}


----------------------------------------------------------------------------------------------------------

 

打開文件時可以使用main函數的參數:文件地址

int main(int argc, char *argv[]) 

直接引用:argv[1];   //就是程序的地址

例子:

在ulix的shell中指定運行一個程序時:/a.out  a.txt b.txt  :a.txt和b.txt的字符就傳到char *argv[],int argc的值就為2

argv[1]=a.txt;  argv[2]=b.txt;     這兩個字符就可以作為文件的地址使用

同樣也可以使用這個方法去傳遞main函數的參數

 

 

 

讀寫二進制文件


fread(buffer,size,count,fp);   //讀取
fwrite(buffer,size,count,fp);  //寫入

buffer:該參數是一個 void* 無類型指針,非數組參數注意使用&符,緩沖區,fread保存了讀出來的數據;fwrite保存即將寫入的數據

size:讀寫一次的字節數;struct a b;  sizeof(b);代表讀寫一次的字節數,如果結構體數組,可以指定一次讀取多少組結構體;另外int double等類型都可以使用sizeof關鍵字

count:進行多少次讀寫

fp:文件指針

例:float a[10];  fread(a,4.2,sp);

a是一個實型數組名,一個實型變量占4個字節。從sp所指向的文本文件中讀出2個占4個字節的數據,存儲到a[0],a[1]中,由於a是存儲對象,所以sp每給a存儲一個數據時,sp和a的文件指針位置都會向下一位移動直到存儲完2個數據為止

 


___________________________________________________________________________________________________________________________________________________________


文件的定位


文件中都有一個位置指針,指向當前讀寫的位置,順序的讀寫一個文件的時候,當操作完當前的元素之后,位置指針自動的指向下一個元素位置。

 


rewind(文件指針);   //使位置指針回到文件的開頭

ftell(文件指針);  //獲取文件位置指針的當前位置;long int k;  k= (fp);

fseek(文件指針,位移量,起始點);   //把指針調整到指定的位置

位移量:為正數,向前移動;為負數,向后移動

起始點參數:

文件開頭:0  SEEK_SET

當前位置: 1  SEEK_CUR

文件末尾: 2  SEEK_END

 

 

fseek(fp,20L,0);/*將位置指針移到離文件頭20個字節處*/
fseek(fp,10L,1);/*將位置指針移到離當前位置10個字節處*/
fseek(fp,-30L,2);/*將位置指針從文件末尾處回退30個字節*/

注意:"L"是字節的意思

 

 

___________________________________________________________________________________________________________________________________________________________

 

變長參數


#include<stdarg.h>    //變長參數所需頭文件


va_list v;   //定義一個可變長參數的列表

va_start(v,cnt);  //將所有的參數保存到va_list中,並把參數個記錄在cnt中;cnt是接收函數的第一形參

變量=va_arg(v,數據類型);   //從va_list中取出第一個參數賦值給變量,變量的數據類型必須和參數的數據類型一致

va_end(v);  //釋放可變長參數列表


-------------------------實例


#include<stdio.h>
#include<stdarg.h>

int max(int cnt,...);

int main()
{
printf("%d\n",max(3,15,24,18));
printf("%d\n",max(5,15,51,18,23,16));
printf("%d\n",max(10,15,24,18,52,123,14,57,48,62,31));
return 0; 
}

int max(int cnt,...)   // '...' 代表可變數目形參列表,也就是說調用該函數的時候可以傳遞不定數目的實參
{
va_list v;        //聲明一個va_list類型的變量,用來保存參數
va_start(v,cnt);  //將所有的參數保存到va_list中,並且參數的個數被cnt記錄
int i;
int maxvalue=va_arg(v,int);  //從va_list中取出第一個參數賦值給maxvalue
for(i=1; i<cnt; i++)
{
int data =va_arg(v,int);    //從va_list中取出一個參數賦值給data,int是說明參數的數據類型
if(data>maxvalue)        //進行判斷
maxvalue = data;
}
va_end(v);        //.釋放va_list
return maxvalue;  //返回一個值給max函數
}

說明:該函數作用是求最大值,所以要把最大值(比較的結果)返回給調用處

 

___________________________________________________________________________________________________________________________________________________________

 

標准庫


C語言提供了一個標准的庫文件,也就是頭文件,每種頭文件都支持不一樣的功能,詳細可以到網絡上檢索

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM