☆const top
const表明程序運行期間的值都不會變化,const變量是存放在只讀內存中(一般說來,我們的程序指令也被放置在只讀內存中)。
例如定義一個常量∏:const double pi=3.1415926;如果后面去修改pi的值會報錯,比如pi=pi+1;這樣是編譯通不過的。
深入討論一下
1)const int a=10;和int const a=10;是等價的。
2)const int * p;//(*p)是一個常量,但是p的值是可以變化的;
比如:int a=10,b=20;p=&a;……;p=&b;這樣是對的,但是*p=20;這樣是不行的。
int a=10;int * const p=&a;*p=20;//p是一個常量,而*p的值是可以變化的,這時必須在聲明p的時候就給p賦值,因為以后不能改變p的值。
用途:
const的用途比較明顯,聲明一些自然數據,比如圓周率的值,因為這些值是一個定值。另外還可以放置程序誤修改變量的值,比如把某個值傳入某個函數,為了放置值被修改,可以把參數用const聲明。
☆定義多維數組 top
int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int a[3][3]={1,2,3,4,5,6,7,8,9};//和上面的等價
另外c允許我們只申明部分值,未申明的值都初始化為0,例如:
int a[3][3]={{1,2,3},{4},{5,6}};
int a[3][3]={1,2,3,4,0,0,5,6};
int b[3][3]={[0,0]=1,[0,1]=2,[0,2]=3,[1,0]=4,[2,0]=5,[2,1]=6};
//上面三句和int a[3][3]]={{1,2,3},{4,0,0},{5,6,0}};等價。
求數組長度:
#define ArraySize(ARR) (sizeof(ARR) / sizeof(ARR[0]))
☆結構體的初始化 top
1)結構體變量的初始化和數組的初始化很類似,可以用逗號將結構體成員的值分割,并用一對大括號將其包圍起來。例如:
struct mydate {int year;int month;int day;};
struct mydate date1 ={2007,7,23};
2)也可以只申明部分值,未申明的值可以為0,也可能為其他值(由編譯器決定),例如:
struct mydate date2={2007,7};//相當于{2007,7,0};
3)初始化結構體數組,例如:
struct mydate dates[2]={{2007,7,23},{2007,7,24}};
☆字符串的初始化 top
char a[]="hello";
char b[]={"hello"};
char c[]={'h','e','l','l','o','\0'};
char d[6]="hello";
char e[]="he""llo";//編譯器會自動將相鄰的字符串連接起來
char f[]="he\
llo";//如果要跨行的時候,可以使用反斜線,但是注意,續行一定要從開始寫,不能有空格等。
以上六種方法等價,但是如果寫成char d[5]="hello";這樣程序不報錯,但是明顯可能導致災難。
☆指針與數組 top
數組名對應著(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。指針可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作動態內存。當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。
int *p;int a[10];
p=a;等價于p=&a[0];
*(p+1);等價于a[1];
使用指針訪問數組比使用下標的速度快。
☆字符串常量與指針 top
char a[10];
a="hello";//這樣是錯誤的,因為在申明數組a時,a已經分配了空間,a指向一個固定地址,而"hello"是一個常量,地址也是固定了的,常量不能賦值給常量。但是下面那樣是可以的:
char *a;a="hello";
☆宏定義 top
一般定義,后面不要加分號
定義:#define pt1(s) printf(s)
調用:pt1("woxingwosu");
結果:woxingwosu
接受可變參數個數的宏(C99規范中新增的,目前似乎只有gcc支持)
定義:#define pt1(
) printf(s)
調用:char * p="2007-07-24";pt1("woxingwosu %s",p);
結果:woxingwosu 2007-07-24
#操作符(將參數轉變成字符串)
定義:#define str(s) #s
調用:printf(str(woxingwosu));
結果:woxingwosu
##操作符(連接參數)
定義:#define myvar(s) woxingwosu##s
調用:int woxingwosu10=100;printf("value=%d",myvar(10));
結果:100
☆
條件編譯 top
(#ifdef,#endif,#else,#ifndef,#if,#elif,#undef)
例如:
//考慮平臺的移植性
#ifdef UNIX
#define PATH "/home/root"
#else
#define PATH "c:\windows"
#endif
//避免多次宏定義(一般會報錯)
#ifndef PATH
#define PATH "c:\windows"
#endif
//加入判斷條件
#define OS 1
……
#if OS==1
#define PATH "c:\windows"
#elif OS==0
#define PATH "/homw/root"
#endif
//取消定義的宏定義
#undef PATH
☆
枚舉類型 top
enum Week {sun,mon,tue,wed,thu,fri,sat};//定義一個枚舉類型,其實sun~sat的值分別是0~6;
enum Week week1=mon;//賦值
//如果指定其中一個值,后面的值會遞增
enum Week {sun,mon=5,tue,wed,thu,fri,sat};
//sun=0,mon~sat=5~10
☆
typedef語句(別名) top
typedef int count//等價于#define count int
count i=5;
_______________________________________________
typedef char Name[20];
Name name="hello";//等價于char name[20]="hello";
_______________________________________________
typedef char * P;
P p="hello";
_______________________________________________
typedef struct{int year;int month} Date;
Date date={2007,7};
☆
static/extern top
(1)靜態局部變量在函數內定義,但不象自動變量那樣,當調用時就存在,退出函數時就消失。靜態局部變量始終存在著,也就是說它的生存期為整個源程序。
(2)靜態局部變量的生存期雖然為整個源程序,但是其作用域仍與自動變量相同,即只能在定義該變量的函數內使用該變量。退出該函數后, 盡管該變量還繼續存在,但不能使用它。
(3)允許對構造類靜態局部量賦初值。若未賦以初值,則由系統自動賦以0值。(而對自動變量不賦初值,則其值是不定的。)
(4) 根據靜態局部變量的特點, 可以看出它是一種生存期為整個源程序的量。雖然離開定義它的函數后不能使用,但如再次調用定義它的函數時,它又可繼續使用, 而且保存了前次被調用后留下的值。 因此,當多次調用一個函數且要求在調用之間保留某些變量的值時,可考慮采用靜態局部變量。雖然用全局變量也可以達到上述目的,但全局變量有時會造成意外的副作用(不可重入問題),因此仍以采用局部靜態變量為宜。
(5)若全局變量僅在單個C文件中訪問,則可以將這個變量修改為靜態全局變量,以降低模塊間的耦合度。
(6)extern,申明某個變量在另外的一個文件中定義。extern int value;value這個變量在其他文件中定義,value是一個外部全局變量。
☆常用的指針類型 top
1)int *p;//指向整型數據的指針
普遍用法:(把指針與數組聯系起來)
int a[3];
p=a;
for(int i=0;i<3;i++)
*(p+i)=i;
2)char * p;//指向字符的指針
普遍用法:(方便操作字符數組或者字符串)
char * p="hello";
3)int **p;//指向指針的指針(非常靈活)
普遍用法:(尤其在函數參數時,相當傳引用/傳地址)
int a[3];
p=&a;
int i=0;
for(;i<3;i++)
*(*p+i)=i;
也可以實現動態數組,例如
#define N 2
……
char **p;
int i=0;
p=(char**)malloc(sizeof(char *)*N);
*(p+0)=(char *)malloc(sizeof(char)*21);
*(p+1)=(char *)malloc(sizeof(char)*11);
sprintf(*(p+0),"The first One");
sprintf(*(p+1),"Second One");
4)int *p[3];//指針數組
普遍用法:
int a[5],b[5],c[5];
p[0]=a;p[1]=b;p[2]=c;
int i=0,j=0;
for(i=0;i<3;i++)
for(j=0;j<5;j++)
*(p[i]+j)=i+j;
5)int (*p)[3];//指向數組的指針
int a[2][3]={{0,1,2},{3,4,5}};
int (*p)[3];
int i=0,j=0;
p=&a[0];
for(i=0;i<2;i++)
for(j=0;j<3;j++)
printf("a[%d,%d]=%d",i,j,(*(p+i))[j]);
6)int (*p)(void);//指向函數的指針
int show(int arg){
printf("hello\n");
return arg+1;
}
int main(void){
int (*p)(int i);
int result;
p=show;
result=p(10);
printf("result=%d",result);
}
☆
操作文件
top
1)打開文件
語法: FILE *fopen(char *filename, *type);
返回值: 指針地址,如果找不到文件或者失敗的話返回空
注意:在打開文件后,需要判斷文件打開是否成功,否則NULL指針用于文件操作將產生不可預測的結果。
type的含義如下
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符 含義
────────────────────────────
"r" 打開文字文件只讀
"w" 創建文字文件只寫
"a" 增補, 如果文件不存在則創建一個
"r+" 打開一個文字文件讀/寫
"w+" 創建一個文字文件讀/寫
"a+" 打開或創建一個文件增補
"b" 二進制文件(可以和上面每一項合用)
"t" 文這文件(默認項)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2)關閉文件
語法: int fclose(FILE *stream);
返回值: 該函數返回一個整型數。當文件關閉成功時, 返回0, 否則返回一個非零值。
注意:一般在程序運行結束的時候,操作系統會自動關閉打開的文件。然而這樣卻可能導致嚴重的后果——丟失數據。因為向文件寫數據時,是先將數據輸出到緩沖區(在內存中),待數據充滿緩沖區后才將整個緩沖區的數據正式輸出到磁盤上的文件中,這樣做的目的是減少頻繁的磁盤讀寫,提高效率。所以,如果緩沖區內的數據未滿就結束程序的運行,就會造成最后留在緩沖區內的數據丟失。而使用fclose函數就可以把緩沖區內最后剩余的數據輸出到磁盤文件中,并釋放文件指針和有關的緩沖區。
3)常用的幾個文件操作函數:
int feof(FILE filePtr) 如果指針已經指向結尾,返回1,否則返回0
int ferror(FILE filePtr) 如果filePtr指向的文件在I/O操作中發生了錯誤,返回1,否則返回0
int fflush(FILE filePtr) 將內部緩沖區寫到文件中,如果成功返回0,否則返回EOF
int fgetc(FILE filePtr) 讀取文件中的下一個字符,如果到了結尾,返回EOF
int fputc(int c,FILE filePtr) 寫入一個字符c,如果成功返回c,否則返回EOF
char * fgets(char * buffer,int n ,FILE filePtr) 從filePtr指向的文件中讀取,直到讀入n-1個字符或者遇到換行符,讀到的數據存儲在字符數組buffer中,如果讀取發生錯誤或者到了文件結尾,返回NULL,否則返回buffer
int fprintf(FILE filePtr,char * format[,argument,]) 和printf差不多,區別在于printf輸出到stdout,而fprintf輸出到filePtr指向的文件
int fscanf(File filePtr,char * format[,argument,]) 和scanf差不多,和fprintf相對應
size_t fread(void *ptr, size_t size, size_t n, FILE *stream) 參數ptr是保存讀取的數據,void*的指針可用任何類型的指針來替換,如char*、int *等等來替換;size是每塊的字節數;n是讀取的塊數,如果成功,返回實際讀取的塊數(不是字節數),一般用于二進制模式打開的文件中。
FILE * freopen(char * filename,char * accessMode,FILE * filePtr) 關閉filePtr對應的文件,以accessMode中的模式打開filename對應的文件,并將該文件與filePtr指向的文件關聯起來。一般用于將標準輸入,標準輸出和標準錯誤重定向其他文件。如果操作成功,返回filePtr,否則返回NULL。例如將stderr重定向到文件error.logs,例如freopen("error.logs","w",stderr);
int fseek(FILE * filePtr,int offset,int mode) 重新設置文件指針的位置,offset是移動的字符數,負值表示往首部移動,mode有三種取值:SEEK_SET(從文件開始計算),SEEK_CUR(從當前位置計算),SEEK_END(從文件結束計算)。如果操作成功返回0,否則返回非0值。
void rewind(FILE * filePtr) 將文件指針重新定向到文件的開始
int remove(char * filename) 刪除文件名為filename的文件,如果成功返回0,否則返回非0值
int rename(char * oldname,char * newname) 文件重命名,如果成功返回0,否則返回非0值
FILE * tmpfile(void) 創建并以寫更新模式打開一個二進制形式的臨時文件。如果成功返回指向的文件指針,否則返回NULL。當程序結束時,該臨時文件自動刪除。
更詳細可以參考:http://hi.baidu.com/bhqiang/blog/item/b4caba51a3512f19377abe7f.html
4)另外文本文件和二進制文件的區別
按二進制寫文件指的是直接按照數據在內存中的表現形式寫入文件。例如,如果int型數據在內存中用4個字節表示,則寫這個int數據的時候直接把對應的內存中4個字節的內容寫入文件。在此過程中數據不需要做任何轉換,所以效率較高。數據有字符型和非字符型(數)兩種。按文本方式寫文件指的是將數據轉換為對應的字符型數據之后再寫入文件。對于字符型數據,由于其本身就是ASCII碼字符,一般不必轉換,直接寫入文件。但是,由于不同的系統對于換行符('\n')有不同的處理(轉換)方式,在有的系統(如Windows)下也會對'\n'作適當的轉換。
對于非字符型數據,都要進行轉換處理。例如:int k=11; 如果是文本文件則存儲為'1','1'。如果二進制文件,存儲為0000000 0000000 0000000 00001011。
5)一個簡單操作文件的例子:
#include <stdio.h>
void showContentByChar(FILE *file);
void showContentByLine(FILE *file);
int main(){
FILE * inputFile;
int c;
inputFile=fopen("woxingwosu.data","rb");
if(inputFile==NULL){
printf("找不到文件");
return -1;
}
printf("-----按字符讀:-------\n");
showContentByChar(inputFile);
rewind(inputFile);//指針回到文件首
printf("\n-----按行讀:-------\n");
showContentByLine(inputFile);
fclose(inputFile);
return 0;
}
//按字符讀文件
void showContentByChar(FILE *file){
int c;//最好定義成int,不要定義為char
if(file==NULL){
printf("文件指針為空!");
return ;
}
while((c=getc(file))!=EOF)
putchar(c);
}
//按行讀文件
void showContentByLine(FILE *file){
int N=512;
char buff[N+1];
if(file==NULL){
printf("文件指針為空!");
return ;
}
while((fgets(buff,N,file))!=NULL)
printf("%s",buff);
}
☆
系統預定義符號 top
有些符號是系統自定義了的
————————————————————————————————————————————————
__FILE__ 當前文件名
__LINE__ 當前行號
__DATE__ 當前日期(月日年)
__TIME__ 當前時間(時分秒)
__func__ 當前函數(注意是小寫)
————————————————————————————————————————————————
我們可以利用這些來記錄日志,例如:
#include <stdio.h>
#ifndef ERROR_LOG
#define ERROR_LOG printf("[%s %s][%s %s line:%d] ",__DATE__,__TIME__,__FILE__,__func__,__LINE__)
#endif
int main(){
printf("begin\n");
ERROR_LOG;
printf("end");
getchar();
}
☆
sizeof top
sizeof不是一個函數,而是一個一元運算符。
sizeof看似簡單,實際上有時讓人頭痛。先看下面的程序,猜猜程序的結果
#include <stdio.h>
int main(){
char a='a';
char * b="hello";
char c[10]="hello";
struct Data1{char m;int n};
struct Data2{char m;struct Data1 data1;int n};
printf("sizeof(a)=%ld\n",sizeof(a));
printf("sizeof(b)=%ld\n",sizeof(b));
printf("sizeof(* b)=%ld\n",sizeof(*b));
printf("sizeof(c)=%ld\n",sizeof(c));
printf("sizeof(Data1)=%ld\n",sizeof(struct Data1));
printf("sizeof(Data2)=%ld\n",sizeof(struct Data2));
}
結果如下:
sizeof(a)=1
sizeof(b)=4
sizeof(* b)=1
sizeof(c)=10
sizeof(Data1)=8
sizeof(Data2)=16
解釋一下,sizeof(b),b是一個指針,長度為4字節;sizeof(* b),*b是一個字符,長度為1個字節;sizeof(c),c被分配了10個字節的空間;sizeof(struct Data1),結構體會字節對齊,即按照最長的基本數據類型來遞增空間,這樣每個基本數據數據類型的偏移地址都是最長基本數據類型的n倍,這樣可以加快計算機的取數速度,減少指令周期。對于長度比最長基本數據類型短的類型,在后面填充字節。這樣sizeof(struct Date1)就為size(int)*2;sizeof(struct Data2),其實Data2會把Data1打算,最后還是按int填充,所有為sizeof(int)*4。
☆
malloc與calloc的區別 top
函數malloc()和calloc()都可以用來動態分配內存空間,但兩者稍有區別。
語法格式:
void*malloc(size_tsize);
void*calloc(size_tnumElements,size_tsizeOfElement);
malloc()函數有一個參數,即要分配的內存空間的大小:
calloc()函數有兩個參數,分別為元素的數目和每個元素的大小,這兩個參數的乘積就是要分配的內存空間的大小。
如果調用成功,函數malloc()和函數calloc()都將返回所分配的內存空間的首地址。
函數malloc()和函數calloc() 的主要區別是前者不能初始化所分配的內存空間,而后者能。如果由malloc()函數分配的內存空間原來沒有被使用過,則其中的每一位可能都是0;反之, 如果這部分內存曾經被分配過,則其中可能遺留有各種各樣的數據。也就是說,使用malloc()函數的程序開始時(內存空間還沒有被重新分配)能正常進行,但經過一段時間(內存空間還已經被重新分配)可能會出現問題。
函數calloc() 會將所分配的內存空間中的每一位都初始化為零,也就是說,如果你是為字符類型或整數類型的元素分配內存,那麼這些元素將保證會被初始化為0;如果你是為指 針類型的元素分配內存,那麼這些元素通常會被初始化為空指針;如果你為實型數據分配內存,則這些元素會被初始化為浮點型的零。
calloc(m, n) 本質上等價于p = malloc(m * n); memset(p,0, m * n);顯然calloc的效率要比malloc低,如果沒有初始為0的要求就用malloc。
總之,當你在calloc()函數和malloc()函數之間作選擇時,你只需考慮是否要初始化所分配的內存空間。
另外還有一個重新分配內存的函數realloc
語法格式:void * realloc(void * p,size_t size); 改變指針p分配的空間,重新設置為size字節。返回新分配空間的地址,如果失敗返回NULL。原則上是先釋放再重新分配的。但是好像跟系統有關系。如果縮小空間,可能會復制空間。如果變大空間,那么返回的指針不一定就是原來的指針了。
☆
內存函數 top
頭文件:<string.h>
void *memchr(void *s, char ch, unsigned n);在數組的前n個字節中搜索字符ch。如果找到,返回指向該位置的指針,否則返回NULL
int memcmp (const void *s1,const void *s2,size_t n);比較s1和s2所指的內存區間前n個字符。字符串大小的比較是以ASCII碼表上的順序來決定,次順序亦為字符的值。memcmp()首先將s1第一個字符值減去s2第一個字符的值,若差為0則再繼續比較下個字符,若差值不為0則將差值返回。若參數s1和s2所指的內存內容都完全相同則返回0值。s1若大于s2則返回大于0的值。s1若小于s2則返回小于0的值。
void * memcpy (void * dest ,const void *src, size_t n);拷貝src所指的內存內容前n個字節到dest所指的內存地址上。與strcpy()不同的是,memcpy()會完整的復制n個字節,不會因為遇到字符串結束\0而結束。返回指向dest的指針。注意:指針src和dest所指的內存區域不可重疊。
void * memmove(void *dest,const void *src,size_t n);memmove()與memcpy()一樣都是用來拷貝src所指的內存內容前n個字節到dest所指的地址上。不同的是,當src和dest所指的內存區域重疊時,memmove()仍然可以正確的處理,不過執行效率上會比使用memcpy()略慢些。返回指向dest的指針。
void * memset (void *s ,int c, size_t n);將參數s所指的內存區域前n個字節以參數c填入,然后返回指向s的指針。在編寫程序時,若需要將某一數組作初始化,memset()會相當方便。返回指向s的指針。注意:參數c雖聲明為int, 但必須是unsigned char ,所以范圍在0到255之間。
int sprintf(char * buffer,char * format[,argument]); 跟printf差不多,可以參考前面的fprintf(),常用于類型轉換。轉換結束后會自動加上空字符\0。返回放置在buffer中的字符數(不包括結束的空字符)。頭文件。
int sscanf(char * buffer,char * format[,argument]); 跟scanf()差不多,返回成功轉換參數的個數。例如:char buffer[]="woxingwosu 666";char username[15];int value;sscanf(buffer,"%s %d",username,&value)頭文件
☆符號重載
static:
在函數內部,表示該變量的值在各個調用間都一直保持延續性(注意不可重入的問題)
用于函數,表示該函數只對本文件可見
extern
用于變量定義,表示該變量在其他地方定義
用于函數定義,表示全局可見(屬于多余)
void
作為函數的返回類型,表示不返回任何值
在指針聲明中,表示通用指針的類型
位于參數列表中,表示沒有參數
☆volatile
volatile的本意是“易變的”
由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:
static int i=0;
int main(void)
{

while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中斷產生時,在main當中調用dosomething函數,但是,由于編譯器判斷在main函數里面沒有修改過i,因此可能只執行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致dosomething永遠也不會被調用。如果將將變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
一般說來,volatile用在如下的幾個地方:
1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2、多任務環境下各任務間共享的標志應該加volatile;
3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能有不同意義;
【http://www.laogu.com/wz_692.htm】
posted on 2007-08-17 14:48
破繭而出 閱讀(1079)
評論(1) 編輯 收藏 所屬分類:
C/C++