變量的聲明和定義
變量聲明(declaration) 可以declaration很多次,不占內(nèi)存空間,例如 extern int a;
變量定義(define) 定義只能定義一次 int a;(全局變量0;局部變量為任意值)-
數(shù)組
數(shù)組申請(qǐng)
int m ;
int a[m];
可以運(yùn)行,但在運(yùn)行的時(shí)候m必須確定特殊位置賦值
int f[5] = [[4]=5]; //f[4] = 5,其他位置為0
int i[] = {7,[6]=10,100,1000}; 一共有9個(gè)元素,第7,8,9位的值是10,100,1000.剩下的值為7數(shù)組傳參
int fun(int[] a)
int fun(int b[][5])//第一唯是大小,剩下的是類型。 傳遞一個(gè)類型為int[5]的數(shù)組b字符串賦值
char p1[5]={"Hello"}; 編譯器最后會(huì)自動(dòng)添加'\0'
char p2[5]={"H","e","l","l","o"}; 沒有‘\0’-
二維數(shù)組
-
二維數(shù)組初始化:
int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
int b[3][2] = {{1, 2}}; = {{1, 2}, {0, 0}, {0, 0}};
int c[3][2] = {{1}, {2, 3}}; = {{1, 0}, {2, 3}, {0, 0}};int d[3][2] = {1, 2, 3, 4, 5, 6}; = {{1, 2}, {3, 4}, {5, 6}};
int e[3][2] = {1, 2, 3}; = {{1, 2}, {3, 0}, {0, 0}};
int f[][3] = {1, 2, 3}; = {{1, 2,3}};
int g[3][] = {1, 2, 3}; 錯(cuò)誤
-
- 指針
為什么要用指針?
計(jì)算機(jī)是按字節(jié)編址的,使用指針是為了方便程序員操作者內(nèi)存打印指針地址 使用 p%
-
數(shù)組,指針,函數(shù)關(guān)注點(diǎn)
- 數(shù)組關(guān)注:大小+類型
- 指針:類型
- 硬件里面操作 基地址+偏移
- 函數(shù):參數(shù)+返回值
-
指針數(shù)組,數(shù)組指針,函數(shù)指針
int *p[3]; //p 往右讀是[],則類型是數(shù)組,吧p[3]看成一個(gè)整體,數(shù)組里面存的是int* int (*p)[3];//p 遇到‘)’ 向左讀 遇到‘*’,則類型是指針,數(shù)組指針,p指向一個(gè)一個(gè)int[3]的數(shù)組。 int (*p)(int a);//p 遇到‘)’ 向左讀 遇到‘*’,則類型是指針,往右讀遇到'()',p是一個(gè)指向參數(shù)為int a ,返回值類型為int 的函數(shù)。 int (*p[3])(int a);
解讀方法:首先從標(biāo)示符開始閱讀,然后往右讀,每遇到圓括號(hào)就調(diào)轉(zhuǎn)閱讀方向。重復(fù)這個(gè)過(guò)程直到整個(gè)聲明解析完畢。需要注意的是,已經(jīng)讀過(guò)的部分在后續(xù)作為一個(gè)整體來(lái)看。
函數(shù)指針: int echo(int a) { return a; } int main() { int (*p)(int a); p = echo; printf("echo(5)=%d\n", echo(5)); printf("p(5)=%d\n", p(5)); return 0; } 數(shù)組指針: int main() { int (*p)[3]; int d[2][3] = {1, 10, 1000, 2, 20, 2000}; p = d; for(int i=0; i<2; i++) { for(int j=0; j<3; j++) printf("%d\t", *(*(p+i)+j)); printf("\n"); } return 0; } 指針數(shù)組: int main() { int a, b,c; a=b=c=10; int *p1[3] = {&a, &b, &c}; for(int i=0; i < 3; i++) printf("%d\t", *(p1[i])); printf("\n"); } 函數(shù)指針數(shù)組: int function1(int a) { printf("int function1\n"); return 0; } int function2(int a) { printf("int function2\n"); return 0; } int function3(int a) { printf("int function3\n"); return 0; } int main() { int (*p[3])(int a); p[0] = function1; p[1] = function2; p[2] = function3; p[0](10); p[1](10); p[2](10); return 0; }
-
a,&a,&a[0]
-
假設(shè):int a[3] = {1, 2, 3};
- a代表什么?
a是整個(gè)數(shù)組的名字,當(dāng)a作為右值時(shí),它代表首元素的首地址,等價(jià)于&a[0] - &a代表什么?
&a取a的地址,即整個(gè)數(shù)組的地址 - &a[0]代表什么?
代表首元素的首地址 - a、&a、&a[0]的值相等嗎?
相等 - sizeof(a)、sizeof(&a)、sizeof(&a[0])相等嗎?分別是多少?
sizeof(a)=12
sizeof(&a)=4
sizeof(&a[0])=4
- a代表什么?
- sizeof(a[4])合法嗎?如果合法,值是多少?為什么?
sizeof(a[4])=4 - a+1、&a+1、&a[0]+1分別是多少?指向的值又是多少?
a+1 = &a[0]+1 指向 2
&a+1 指向 數(shù)組末尾 - a[-1]是否合法?如何理解?
-
程序驗(yàn)證
#include <stdio.h> int main(void) { int a[3] = {1, 2, 3}; printf("sizeof(int)=%lu\tsizeof(unsigned long)=%lu\n", sizeof(int), sizeof(unsigned long)); printf("a=%p\t&a=%p\t&a[0]=%p\n", a, &a, &a[0]); printf("sizeof(a)=%lu\tsizeof(&a)=%lu\tsizeof(&a[0])=%lu\n", sizeof(a), sizeof(&a), sizeof(&a[0])); printf("sizeof(a[4])=%lu\n", sizeof(a[4])); printf("a+1=%p\t&a+1=%p\t&a[0]+1=%p\n", a+1, &a+1, &a[0]+1); printf("*(a+1)=%d\t*(&a+1)=%d\t*(&a[0]+1)=%d\n", *(a+1), *(&a+1), *(&a[0]+1)); printf("a[-1]=%d\n", a[-1]); return 0; }
-
分析&&結(jié)論
a、&a、&a[0]雖然含義不同,但它們的值都是一樣的。
a是整個(gè)數(shù)組的名字,代表整個(gè)數(shù)組,所以sizeof(a)的值為整個(gè)數(shù)組的大小。但有一個(gè)例外,當(dāng)a作為右值時(shí),它代表首元素的首地址,即相當(dāng)于&a[0]。所以a+1和&a[0]+1等價(jià)。
&a取a的地址,即整個(gè)數(shù)組的地址。所以&a+1表示“下一個(gè)數(shù)組”的地址,而不是下一個(gè)數(shù)組元素的地址。
&a[0]代表數(shù)組第一個(gè)元素的地址。
a[-1]是什么含義呢?其實(shí)編譯器內(nèi)部是按照指針加偏移的方式來(lái)處理數(shù)組下標(biāo)的。比如a[i],編譯器會(huì)解析為* (a+i),所以a[-1]其實(shí)就是*(a-1)的值。根據(jù)這個(gè)規(guī)則我們還會(huì)發(fā)現(xiàn)其實(shí)a[i]和i[a]都是合法的,且指向的是同一個(gè)值
-
當(dāng)不知道數(shù)組大小時(shí),獲取數(shù)組最后一個(gè)元素。
int a[5] = {1,2,3,4,5}; int *p = (int*)(&a + 1); *(p-1) 的值是 5
-
-
指針和數(shù)組
-
指針和數(shù)組雖然很相近,我們經(jīng)常交換使用,但只有在下面兩種情況下,二者才是等價(jià)的:
-
在表達(dá)式中,對(duì)數(shù)組下標(biāo)的引用總是可以換成指向數(shù)組起始地址的指針加偏移量(而且編譯器一般都是這么做的),也就是說(shuō)用a[i]這樣的形式訪問數(shù)組時(shí)總是被編譯器改寫成像* (a+1)這樣的指針訪問。比如:
int a[] = {1, 3, 5, 7, 9}; int *p = a;a[2]等效于*(p+2)
-
在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當(dāng)做指向該數(shù)組第一個(gè)元素的指針。比如下面對(duì)于func函數(shù)的定義是完全等效的:
func(int *a) { … } func(int a[]) { … } func(int a[100]) { … }
除了上面這兩種情況外,數(shù)組就是數(shù)組,指針就是指針,不要交換使用。
-
-
-
指針和二維數(shù)組
- 假設(shè)定義了二維數(shù)組a[m][n],且int *p = a[0],則
a+1代表什么?
a+1 指向 a[1]不使用下標(biāo)的方式,如何通過(guò)a來(lái)訪問元素a[i][j]?
a[i][j] = *((p+i)+j)-
行數(shù)組指針:
定義:type (* var_name) [cols]
動(dòng)態(tài)分配內(nèi)存:int m, n; int (*p)[m];// 動(dòng)態(tài)初始化一個(gè)n*m的數(shù)組 p = (int(*)[m])malloc(n * m * sizeof(int));
-
const
1. 定義非指針類型的常量const int a; a = 5; // 錯(cuò)誤 const int b = 6;
-
函數(shù)參數(shù)
void print_array1(const int a[], int n) { for(int i = 0; i < n; i++) printf("%d\n", a[i]); } void print_array2(int a[], int n) { for(int i = 0; i < n; i++) printf("%d\n", a[i]++); } int main() { const int a[3] = {1, 2, 3}; int b[3] = {4, 5, 6}; print_array1(a, 3); print_array1(b, 3); // print_array2(a, 3); print_array2(b, 3); return 0; }
函數(shù)參數(shù)中使用const是為了防止函數(shù)內(nèi)部對(duì)函數(shù)進(jìn)行更改
-
指針與const
1. 指針常量:指針是常量,即指針只能指向某個(gè)固定地方,不能指向其他地方。int a[3] = {1, 2, 3}; int b[3] = {4, 5, 6}; int * const pi = a; pi[1] = 2; pi = b; // 錯(cuò)誤 2. 常量指針:指向常量的指針,指針?biāo)赶虻膬?nèi)容不可修改,但指針本身可修改(比如,指向其他地址) int a[3] = {1, 2, 3}; int b[3] = {4, 5, 6}; const int *pi = a; //或 int const *pi = a; a[1] = 10; pi[1] = 10; // 錯(cuò)誤 pi = b; 3. 區(qū)分方法 去掉類型名,右側(cè)是指針變量名,就是指針常量。
-
指針和引用
-
指針:指針是一個(gè)變量,只不過(guò)這個(gè)變量存儲(chǔ)的是一個(gè)地址,指向內(nèi)存的一個(gè)存儲(chǔ)單元;而引用跟原來(lái)的變量實(shí)質(zhì)上是同一個(gè)東西,只不過(guò)是原變量的一個(gè)別名而已。如:
int a=1;int *p=&a; int a=1;int &b=a;
上面定義了一個(gè)整形變量和一個(gè)指針變量p,該指針變量指向a的存儲(chǔ)單元,即p的值是a存儲(chǔ)單元的地址。
而下面2句定義了一個(gè)整形變量a和這個(gè)整形a的引用b,事實(shí)上a和b是同一個(gè)東西,在內(nèi)存占有同一個(gè)存儲(chǔ)單元。
可以有const指針,但是沒有const引用;
指針可以有多級(jí),但是引用只能是一級(jí)(int **p;合法 而 int &&a是不合法的)
指針的值可以為空,但是引用的值不能為NULL,并且引用在定義的時(shí)候必須初始化;
指針的值在初始化后可以改變,即指向其它的存儲(chǔ)單元,而引用在進(jìn)行初始化后就不會(huì)再改變了。
"sizeof引用"得到的是所指向的變量(對(duì)象)的大小,而"sizeof指針"得到的是指針本身的大小;
指針和引用的自增(++)運(yùn)算意義不一樣;
-
指針和引用作為函數(shù)參數(shù)進(jìn)行傳遞時(shí)的區(qū)別
-
指針作為參數(shù)進(jìn)行傳遞
#include<iostream> using namespace std; void swap(int *a,int *b) { int temp=*a; *a=*b; *b=temp; } int main(void) { int a=1,b=2; swap(&a,&b); cout<<a<<" "<<b<<endl; system("pause"); return 0; }
輸出結(jié)果:2 1
#include<iostream> using namespace std; void test(int *p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl; } int main(void) { int *p=NULL; test(p); if(p==NULL) cout<<"指針p為NULL"<<endl; system("pause"); return 0; }
運(yùn)行結(jié)果為:
0x22ff44 1
指針p為NULL在main函數(shù)中聲明了一個(gè)指針p,并賦值為NULL,當(dāng)調(diào)用test函數(shù)時(shí),事實(shí)上傳遞的也是地址,只不過(guò)傳遞的是指地址。也就是說(shuō)將指針作為參數(shù)進(jìn)行傳遞時(shí),事實(shí)上也是值傳遞,只不過(guò)傳遞的是地址。當(dāng)把指針作為參數(shù)進(jìn)行傳遞時(shí),也是將實(shí)參的一個(gè)拷貝傳遞給形參,即上面程序main函數(shù)中的p何test函數(shù)中使用的p不是同一個(gè)變量,存儲(chǔ)2個(gè)變量p的單元也不相同(只是2個(gè)p指向同一個(gè)存儲(chǔ)單元),那么在test函數(shù)中對(duì)p進(jìn)行修改,并不會(huì)影響到main函數(shù)中的p的值。
3.將引用作為函數(shù)的參數(shù)進(jìn)行傳遞。
#include<iostream> using namespace std; void test(int &a) { cout<<&a<<" "<<a<<endl; } int main(void) { int a=1; cout<<&a<<" "<<a<<endl; test(a); system("pause"); return 0; }
輸出結(jié)果為:
0x22ff44 1
0x22ff44 1
引用作為函數(shù)參數(shù)進(jìn)行傳遞時(shí),實(shí)質(zhì)上傳遞的是實(shí)參本身,即傳遞進(jìn)來(lái)的不是實(shí)參的一個(gè)拷貝,因此對(duì)形參的修改其實(shí)是對(duì)實(shí)參的修改,所以在用引用進(jìn)行參數(shù)傳遞時(shí),不僅節(jié)約時(shí)間,而且可以節(jié)約空間。 -
-
-
-
預(yù)處理
預(yù)處理器指令從#開始,到其后第一個(gè)換行符“\n”為止。也就是說(shuō),指令的長(zhǎng)度僅限于一行代碼。但是,我們經(jīng)常會(huì)看到我們可以使用反斜線“\”將指令擴(kuò)展到多個(gè)物理行,由多個(gè)物理行組成一個(gè)邏輯行(但是這個(gè)并不是C預(yù)處理器的特性,而是C編譯器的特性:在預(yù)處理開始前,編譯器會(huì)查找反斜線和換行符的組合,并將其刪掉)。
-
#define——“函數(shù)”
#define SQUARE(X) ((X)(X))- 使用時(shí)必須要使用足夠多的括號(hào)來(lái)保證宏展開后以正確的順序進(jìn)行結(jié)合和運(yùn)算。
- 不要在宏中使用增量或減量運(yùn)算符。比如++和--。
-
typedef用途總結(jié)
-
用途1:定義一種類型的別名,而不只是簡(jiǎn)單的宏替換。可以用作同時(shí)聲明指針型的多個(gè)對(duì)象。比如注意一下三種定義的區(qū)別與聯(lián)系:
char* pa,pb; //等價(jià)于 char *pa ; char pb; typedef char* PCHAR; PCHAR pa,pb; //等價(jià)于char *pa,*pb;
用途2:定義與平臺(tái)無(wú)關(guān)的類型。比如定義一個(gè)叫 REAL 的浮點(diǎn)類型,在目標(biāo)平臺(tái)一上,讓它表示最高精度的類型為:typedef long double REAL; 在不支持long double的平臺(tái)二上,改為:typedef double REAL; 在連double都不支持的平臺(tái)三上,改為:typedef float REAL; 也就是說(shuō),當(dāng)跨平臺(tái)時(shí),只要改下typedef本身就行,不用對(duì)其他源碼做任何修改。標(biāo)準(zhǔn)庫(kù)就廣泛使用了這個(gè)技巧,比如size_t。另外,因?yàn)閠ypedef是定義了一種類型的新別名,不是簡(jiǎn)單的字符串替換,所以它比宏來(lái)得穩(wěn)健(雖然用宏有時(shí)也可以完成以上的用途)。
用途3:在舊的C代碼中,定義一個(gè)結(jié)構(gòu)體類型變量的時(shí)候,需要寫上struct,即struct 結(jié)構(gòu)名 變量名 。而在C++或新的C中,可以省去struct關(guān)鍵字,直接定義變量。所以在舊的C標(biāo)準(zhǔn)中,我們可以用typedef來(lái)實(shí)現(xiàn)新的C中的功能。
-
-
typedef注意點(diǎn)
-
陷阱1:記住,typedef是定義了一種類型的新別名,不同于宏,它不是簡(jiǎn)單的字符串替換。
- 比如:先定義:typedef char* PSTR; 然后:int mystrcmp(const PSTR, const PSTR);const PSTR實(shí)際上相當(dāng)于
const char*
嗎
不是的,它實(shí)際上相當(dāng)于 char* const。原因在于const給予了整個(gè)指針本身以常量性,也就是形成了常量指針char* const。簡(jiǎn)單來(lái)說(shuō),記住當(dāng)const和typedef一起出現(xiàn)時(shí),typedef不會(huì)是簡(jiǎn)單的字符串替換就行。
- 比如:先定義:typedef char* PSTR; 然后:int mystrcmp(const PSTR, const PSTR);const PSTR實(shí)際上相當(dāng)于
-
陷阱2:typedef在語(yǔ)法上是一個(gè)存儲(chǔ)類的關(guān)鍵字(如auto、extern、mutable、static、register等一樣),雖然它并不真正影響對(duì)象的存儲(chǔ)特性。比如:
typedet static int INT2;//不可行
-
-
define vs typedef
- typedef只是給類型定義別名(準(zhǔn)確說(shuō)是鏈接),但是#define還可以給常量“定義”別名;
- typedef的定義是由編譯器處理的,而#define是在預(yù)處理器處理的
- typedef是定義了一種新的類型,而#define只是文本替換;
- typedef遵循作用域規(guī)則,而#define沒有;
- typedef定義需要以封號(hào)結(jié)尾,而#define不需要;
-
結(jié)構(gòu)體
字節(jié)對(duì)齊struct A { //假設(shè)內(nèi)存地址從0開始... int a; //0-3 char b; //4 short c;//6-7 } //由于0-7的相加的結(jié)果為8...為自對(duì)齊4的倍數(shù)... //所以結(jié)果:sizeof(A) = 8 struct B { //假設(shè)內(nèi)存地址從0開始... char a;//0 int b; //4-7 short c;//8-10 }
使用#pragma pack更改了字節(jié)對(duì)齊值
#pragma pack(2)
struct C
{
//假設(shè)從0開始
char a;//0
int b;//2-5
short c;//6-7
};
sizeof(C)的答案為8
字節(jié)對(duì)齊 按最長(zhǎng)的對(duì)齊 ,如果剩下的不夠放 再開辟按最長(zhǎng)空間
struct tagS1
{
char a;
int n;
long l;
double t1;
char sz[22];
};
//結(jié)果為48
下圖為struct tagS1在內(nèi)存中的分布圖: