1、變量
extern int a; // 聲明一個(gè)全局變量 a
int a; // 定義一個(gè)全局變量 a
extern int a = 0; // 定義一個(gè)全局變量 a 并給初值。一旦給予賦值,一定是定義,定義才會(huì)分配存儲(chǔ)空間
int a = 0; //定義一個(gè)全局變量 a,并給初值
聲明之后你不能直接使用這個(gè)變量,需要定義之后才能使用。
第四個(gè)等于第三個(gè),都是定義一個(gè)可以被外部使用的全局變量,并給初值。
糊涂了吧,他們看上去可真像。但是定義只能出現(xiàn)在一處。也就是說,不管是 int a 還是 int a=0 都只能出現(xiàn)一次,而那個(gè) extern int a 可以出現(xiàn)很多次。
當(dāng)你要引用一個(gè)全局變量的時(shí)候,你就要聲明 extern int a 這時(shí)候 extern 不能省略,因?yàn)槭÷粤?,就變?int a 這是一個(gè)定義,不是聲明。
const int a; a = 10; //報(bào)錯(cuò)
const int b = 0; // 正常
const 定義的是變量不是常量,只是這個(gè)變量的值不允許改變是常變量,是只讀類型的變量,編譯運(yùn)行的時(shí)候起作用存在類型檢查。define是預(yù)處理階段的簡單的字符替換。
2、static和extern
static 修飾局部變量可以在函數(shù)調(diào)用之間保持局部變量的值。
static 修飾全局變量時(shí),會(huì)使變量的作用域限制在聲明它的文件內(nèi)。任何跟 static 變量或方法同一個(gè)文件中函數(shù)或方法都可以引用。
/* 函數(shù)聲明 */
void func1(void);
static int count=10; /* 全局變量 - static 是默認(rèn)的 */
int main()
{
while (count--) {
func1();
}
return 0;
}
void func1(void)
{
// 'thingy' 是 'func1' 的局部變量 - 只初始化一次,每次調(diào)用函數(shù) 'func1' 'thingy' 值不會(huì)被重置。
static int thingy=5;
thingy++;
printf(" thingy 為 %d , count 為 %d\n", thingy, count);
}
當(dāng)您使用 extern 時(shí),對(duì)于無法初始化的變量,會(huì)把變量名指向一個(gè)之前定義過的存儲(chǔ)位置。
// main.c
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
// test.c
extern int count;
void write_extern(void)
{
printf("count is %d\n", count);
}
如果我想引用一個(gè)全局變量或函數(shù)a,我只要直接在源文件中包含#include (xxx.h包含了a的聲明)不就可以了么,為什么還要用extern呢?
include導(dǎo)入的是整個(gè)文件,可能會(huì)造成重復(fù)定義,extern是更精細(xì)化的include
3、數(shù)組
指針:也是一個(gè)變量,存儲(chǔ)的數(shù)據(jù)是地址。數(shù)組名:代表的是該數(shù)組最開始的一個(gè)元素的地址。區(qū)別:指針是一個(gè)變量,可以進(jìn)行數(shù)值運(yùn)算。數(shù)組名不是變量,不可以進(jìn)行數(shù)值運(yùn)算。
對(duì)數(shù)組來說,array和&array是等價(jià)的
int array[10]; printf("%p %p %p\n",array, &array,&(array[0])); //0x16fdff498 0x16fdff498 0x16fdff498
char *p=(char*)&array == char *p=array ;
想知道數(shù)組元素個(gè)數(shù)可以使用 sizeof(a)/sizeof(a[0]),但是數(shù)組類型做函數(shù)的參數(shù)的時(shí)候不能在函數(shù)內(nèi)部用此方法獲取元素個(gè)數(shù),要另加參數(shù)len把個(gè)數(shù)傳進(jìn)來。因?yàn)閿?shù)組做函數(shù)參數(shù)時(shí)是被當(dāng)做指針傳進(jìn)來的。
int a[] = {1,2,3,4,5};
void test2(int a[]){
int b = sizeof(a)/sizeof(a[0]);
printf("數(shù)組元素個(gè)數(shù)為:%d\n",b); //結(jié)果為2=sizeof(int *)/sizeof(int)=8/4
}
數(shù)組指針運(yùn)算:把數(shù)組當(dāng)成指針,就變成指針運(yùn)算了。
int a[2] = {1,2};
printf("a = %d\n",a[0]);
printf("*(a+0) = %d\n",*(a + 0));
printf("a[1] = %d\n",a[1]);
printf("*a = %d\n",*a);
printf("*(a+1) = %d\n",*(a + 1));
printf("\n");
printf("a 的地址:%p\n",a); //0x16fdff4c0
printf("(a+0)的地址:%p\n",(a + 0)); //0x16fdff4c0
printf("(a+1)的地址:%p\n",(a + 1)); //0x16fdff4c4
4、指針
指針運(yùn)算有:指針加減、指針比較。數(shù)組可以看成一個(gè)指針常量,不能遞增,我們可以使用指針代替數(shù)組,因?yàn)樽兞恐羔樋梢赃f增。
int var[] = {10, 100, 200};
int i, *ptr;
ptr = var; //數(shù)組變?yōu)橹羔樂奖氵M(jìn)行指針操作
for ( i = 0; i < 3; i++) // 通過count遍歷,也可通過ptr比較地址大小進(jìn)行遍歷
{
printf("var[%d]:存儲(chǔ)地址 = %p,存儲(chǔ)值 = %d\n", i, ptr,*ptr);
ptr++;
}
函數(shù)指針和指針函數(shù)等以此類推
指針做參數(shù):傳遞指針給函數(shù),可直接對(duì)地址賦值。避免了返回值或數(shù)據(jù)修改的同步,但是更發(fā)散了
void getSeconds(unsigned long *par)
{
*par = time(NULL);//獲取當(dāng)前的秒數(shù)
}
unsigned long sec;
getSeconds(&sec);
printf("Number of seconds: %ld\n", sec );
函數(shù)與指針的復(fù)雜聲明:
int *f(); // f是一個(gè)函數(shù),返回一個(gè)指向int類型的指針
int (*pf)(); // pf是一個(gè)函數(shù)指針,返回一個(gè)int類型的對(duì)象
// void*是通用指針類型,任何類型的指針都可以轉(zhuǎn)換為void*,并且從void*反轉(zhuǎn)回來時(shí)不會(huì)丟失信息。
void *(*malloc_fn)(size_t sz); //malloc_fn指向一個(gè)函數(shù),函數(shù)類型是void *()(size_t)
void (*free_fn) (void *ptr); //free_fn指向一個(gè)函數(shù),函數(shù)類型是void ()(void *)
//*用來聲明指針,()用來聲明函數(shù)指針,[]用來聲明數(shù)組。優(yōu)先級(jí)是()>[]>*
int (*(*x)(int *,char *))(int);
首先找到x,然后根據(jù)優(yōu)先級(jí)找到*x,則證明整條語句聲明了一個(gè)指針。再繼續(xù)看到(*x)右邊是一個(gè)()符,則證明x是一個(gè)指向函數(shù)的指針,既然是函數(shù)指針那剩下的部分就是描述函數(shù)返回值類型和參數(shù)類型;再看()里則可以解讀出函數(shù)的輸入是一個(gè)整型指針和一個(gè)字符指針。然后再以“(*x)(int *,char *)”為整體看,找到優(yōu)先級(jí)最高的是*符號(hào),則證明返回值是一個(gè)指針;再繼續(xù)看找到了(int)則證明這個(gè)指針是指向一個(gè)函數(shù)的,函數(shù)的參數(shù)是整型;再繼續(xù)找到最后一個(gè)int,則看出這個(gè)函數(shù)的返回值是一個(gè)整型。到此解讀完畢。
簡要的說就是:x指向一個(gè)函數(shù),函數(shù)類型是入?yún)?int *,char *),出參為int (*)(int)
int *(*(*fp)(int)) [10];
fp指向一個(gè)函數(shù),函數(shù)類型是入?yún)?int),出參為int *(*) [10]
int p; -- 這是一個(gè)普通的整型變量
int *p; -- 首先從 p 處開始,先與 * 結(jié)合,所以說明 p 是一個(gè)指針, 然后再與 int 結(jié)合, 說明指針?biāo)赶虻膬?nèi)容的類型為 int 型。所以 p 是一個(gè)返回整型數(shù)據(jù)的指針。
int p[3] -- 首先從 p 處開始,先與 [] 結(jié)合,說明 p 是一個(gè)數(shù)組, 然后與 int 結(jié)合, 說明數(shù)組里的元素是整型的, 所以 p 是一個(gè)由整型數(shù)據(jù)組成的數(shù)組。
int *p[3]; -- 首先從 p 處開始, 先與 [] 結(jié)合,因?yàn)槠鋬?yōu)先級(jí)比 * 高,所以 p 是一個(gè)數(shù)組, 然后再與 * 結(jié)合, 說明數(shù)組里的元素是指針類型, 然后再與 int 結(jié)合, 說明指針?biāo)赶虻膬?nèi)容的類型是整型的, 所以 p 是一個(gè)由返回整型數(shù)據(jù)的指針?biāo)M成的數(shù)組。
int (*p)[3]; -- 首先從 p 處開始, 先與 * 結(jié)合,說明 p 是一個(gè)指針然后再與 [] 結(jié)合(與"()"這步可以忽略,只是為了改變優(yōu)先級(jí)), 說明指針?biāo)赶虻膬?nèi)容是一個(gè)數(shù)組, 然后再與int 結(jié)合, 說明數(shù)組里的元素是整型的。所以 p 是一個(gè)指向由整型數(shù)據(jù)組成的數(shù)組的指針。
int **p; -- 首先從 p 開始, 先與 * 結(jié)合, 說是 p 是一個(gè)指針, 然后再與 * 結(jié)合, 說明指針?biāo)赶虻脑厥侵羔? 然后再與 int 結(jié)合, 說明該指針?biāo)赶虻脑厥钦蛿?shù)據(jù)。由于二級(jí)指針以及更高級(jí)的指針極少用在復(fù)雜的類型中, 所以后面更復(fù)雜的類型我們就不考慮多級(jí)指針了, 最多只考慮一級(jí)指針。
int p(int); -- 從 p 處起,先與 () 結(jié)合, 說明 p 是一個(gè)函數(shù), 然后進(jìn)入 () 里分析, 說明該函數(shù)有一個(gè)整型變量的參數(shù), 然后再與外面的 int 結(jié)合, 說明函數(shù)的返回值是一個(gè)整型數(shù)據(jù)。
int (*p)(int); -- 從 p 處開始, 先與指針結(jié)合, 說明 p 是一個(gè)指針, 然后與()結(jié)合, 說明指針指向的是一個(gè)函數(shù), 然后再與()里的 int 結(jié)合, 說明函數(shù)有一個(gè)int 型的參數(shù), 再與最外層的 int 結(jié)合, 說明函數(shù)的返回類型是整型, 所以 p 是一個(gè)指向有一個(gè)整型參數(shù)且返回類型為整型的函數(shù)的指針。
int *(*p(int))[3]; -- 可以先跳過, 不看這個(gè)類型, 過于復(fù)雜從 p 開始,先與 () 結(jié)合, 說明 p 是一個(gè)函數(shù), 然后進(jìn)入 () 里面, 與 int 結(jié)合, 說明函數(shù)有一個(gè)整型變量參數(shù), 然后再與外面的 * 結(jié)合, 說明函數(shù)返回的是一個(gè)指針, 然后到最外面一層, 先與[]結(jié)合, 說明返回的指針指向的是一個(gè)數(shù)組, 然后再與 * 結(jié)合, 說明數(shù)組里的元素是指針, 然后再與 int 結(jié)合, 說明指針指向的內(nèi)容是整型數(shù)據(jù)。所以 p 是一個(gè)參數(shù)為一個(gè)整數(shù)據(jù)且返回一個(gè)指向由整型指針變量組成的數(shù)組的指針變量的函數(shù)。
詳細(xì)可參考:C 指針詳解
5、字符串
字符串實(shí)際上是使用空字符 \0 結(jié)尾的一維字符數(shù)組。
char *str1 = "asdfgh"; //len=6,size=8
char str2[] = "asdfgh"; //len=6,size=7
char str3[8] = {'a', 's', 'd'}; //len=3,size=8
char str4[] = "as\0df"; //len=2,size=6
6、結(jié)構(gòu)體
結(jié)構(gòu)體聲明在一般情況下,tag、member-list、variable-list 這 3 部分至少要出現(xiàn) 2 個(gè)。
結(jié)構(gòu)體的成員可以包含其他結(jié)構(gòu)體,也可以包含指向自己結(jié)構(gòu)體類型的指針,而通常這種指針的應(yīng)用是為了實(shí)現(xiàn)一些更高級(jí)的數(shù)據(jù)結(jié)構(gòu)如鏈表和樹等。
訪問結(jié)構(gòu)體變量用.運(yùn)算符,訪問結(jié)構(gòu)體指針用->運(yùn)算符。
// 結(jié)構(gòu)體大小運(yùn)算:結(jié)構(gòu)體變量所占內(nèi)存長度是其中最大成員大小的整數(shù)倍
struct A
{
char a1;
short int a2;
int a3;
double d;
};
struct B
{
long int b2;
short int b1;
struct A a;
};
// A=16,成員類型大小依次為1 2 4 8,以最大8位對(duì)齊字節(jié),1 2 4占7個(gè),補(bǔ)齊1個(gè)為8個(gè),再加上8。
// B=8,去掉a算出其大小為4+2(+2)=8,直接加上A的大小=24。
7、共用體
特點(diǎn):只有一個(gè)內(nèi)存,是最大成員長度。值覆蓋。
union Data
{
int i;
char str;
short d;
} data;
union Data1
{
int i;
float f;
char str[7];
short d;
} data1;
union Data2
{
int i;
float f;
char str[9];
short d;
} data2;
union Data3
{
int i;
float f;
char str[9];
double d;
} data3;
// 4 8 12 16
8、位域
針對(duì)整形int/unsigned int/signed int的,限制內(nèi)存占用位數(shù)。如:開關(guān)變量中,我們只存儲(chǔ) 0 或 1,只需要1位,這時(shí)可以使用位域以節(jié)省內(nèi)存空間。賦值超限會(huì)警告,且值會(huì)異常(溢出)。
struct
{
unsigned int var;
} status1;
struct
{
unsigned int var1 : 1;
unsigned int var2 : 1;
...
unsigned int var32 : 1;
} status2;
// 同樣4字節(jié)的空間,status1存儲(chǔ)1個(gè)變量,status2能存儲(chǔ)32個(gè)變量。
9、輸入輸出
#include <stdio.h>
//stdio.h是standard input output的縮寫,常用printf() 和 scanf(),其他還有g(shù)etchar/putchar、gets/puts。gets/puts方法會(huì)報(bào)安全警告,因?yàn)闆]有指定輸入字符大小,會(huì)無限讀取,一旦輸入的字符大于數(shù)組長度,就會(huì)發(fā)生內(nèi)存越界錯(cuò)誤,可用fgets代替。
printf("%20.15f\n",1.0/3); //指定輸出的數(shù)據(jù)占 20 列,其中包括 15 位小數(shù)
/*
short/int : %d
long: %ld (long 是 int 得修飾,不能算是一種單獨(dú)的數(shù)據(jù)類型,只是比 int 多了四個(gè)字節(jié)的存儲(chǔ)空間)
long long : %lld
char : %c
float/double : %f float 默認(rèn)是 6 位小數(shù)輸出;可以在 %f 中控制;例如:%.2f:輸出兩位小數(shù)。
char *s(字符串) :%s
unsigned: %u (signed:有符號(hào)類型, unsigned:無符號(hào)類型;默認(rèn)都是有符號(hào)的)
八進(jìn)制:%o 以 0 開頭
十六進(jìn)制:%x 以 0x 開頭
*/
int a, b, c;int x = scanf("%d%d%d",&a,&b,&c);printf("%d\n%d\n",a,x);
//scanf() 函數(shù)有int返回值,錯(cuò)誤時(shí)立刻返回 EOF,正確時(shí)返回接收到值的變量個(gè)數(shù)。
scanf("%d%d",&a,&b) //輸入數(shù)字以空格分隔
scanf("c=%c",&c); // 必須輸入c=3才能正常接收到值,格式和數(shù)據(jù)類型要一致
10、頭文件
只引用一次頭文件:如果一個(gè)頭文件被引用兩次,編譯器會(huì)處理兩次頭文件的內(nèi)容,這將產(chǎn)生錯(cuò)誤。為了防止這種情況,標(biāo)準(zhǔn)的做法是把文件的整個(gè)內(nèi)容放在條件編譯語句中,如下:
#ifndef FILENAME_H
#define FILENAME_H
the entire header file file
#endif
11、遞歸
注意:循環(huán)體、初始/終止條件
// 階乘
unsigned int factorial(unsigned int i){
if (i <= 1) {
return 1;
}
return i * factorial(i-1);
}
// 斐波那契
unsigned int fibonaci(unsigned int i){
if (i <= 1) {
return i;
}
return fibonaci(i-1) + fibonaci(i-2);
}
12、可變參數(shù)
// 固定參數(shù)-地址訪問
void fix_test(int a, double b, char c)
{
void *p = &a;//void * =&a 不需要轉(zhuǎn)換但是使用時(shí)要轉(zhuǎn)換
printf("%p %p %p\n", &a, &b, &c); //0x16fdff49c 0x16fdff490 0x16fdff48f
printf("%d %f %c\n", *((int*)p), *((double*)((int*)p-3)), *((char*)((char*)p-13)));
//在*((double*)((int*)p-3))中,double*是用來指明最外層運(yùn)算符*的指針類型的,若缺省*運(yùn)算符取到的值會(huì)是nil。int*是用來指明指針(地址)運(yùn)算的指針類型的(int*)p-3也可以寫成(char*)p-12
return;
}
// 可變參數(shù)-va_arg訪問
double average(int num,...) {
va_list valist;
double sum = 0.0;
va_start(valist, num);
for (int i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
va_end(valist);
return sum / num;
}
// 可變參數(shù)-地址訪問
// (3, 2, 3, 4);
void debug_arg_int(unsigned int num, ...){
char *p = #
printf("%d %d %d\n", *(p+4+8+8), *(p+20+8), *(p+28+8)); //*(p+20)的完整格式為*(int*)((char*)p+20)
// 打印地址0x16fdff48e中內(nèi)容:x/32 0x16fdff48e
}
void debug_arg_short(unsigned short num, ...){
char *p = #
printf("%d %d %d\n", *(p+2+8+8), *(p+18+8), *(p+26+8));
}
13、內(nèi)存管理
description = (char *)malloc( 200 * sizeof(char) );
description = (char *)calloc(200, sizeof(char));
description = (char *) realloc( description, 300 * sizeof(char) );
free(description);
// 直接使用原來的指針變量接收 realloc 的返回值是可能存在內(nèi)存泄漏的。若 realloc 函數(shù)執(zhí)行失敗,description 原先所指向的空間不變,realloc 函數(shù)返回 NULL。此時(shí) description 的值被賦為 NULL, 但原先指向的空間未被釋放,造成了內(nèi)存泄漏。
14、命令行參數(shù)
從外部(命令行)給程序傳遞參數(shù)控制程序
int main( int argc, char *argv[] )
//argv[0] 存儲(chǔ)程序的名稱,argv[1] 是一個(gè)指向第一個(gè)命令行參數(shù)的指針,如果傳遞了一個(gè)參數(shù),argc 將被設(shè)置為 2
//兩個(gè)參數(shù)名只是約定俗成,可以任意修改
getopt()用來分析命令行參數(shù),詳見:C語言之getopt函數(shù)
int getopt(int argc, char * const argv[], const char *optstring);
//前倆參數(shù)和main函數(shù)一樣,optstring:選項(xiàng)字符串,一個(gè)字母表示不帶值的參數(shù),如果字母后帶有一個(gè):,表示必須帶值的參數(shù)。如果帶有兩個(gè):,表示是可選的參數(shù)。
//例如"ab:c::",程序運(yùn)行時(shí)可接受的參數(shù)為"-a -b 1 -c"或者"-a -b 1 -c2"