指针、数组、函数阶段小结
引子:數據在內存中是如何存儲的,又是如何讀取的?內存編號就是內存的地址(內存中每個字節都有一個編號,即地址)
1.概念:
地址:內部存儲器的編號,稱為地址。如變量a的位置編號,變量b的位置都是指針。
指針變量:專門存放地址的變量稱為指針變量。
地址、指針、指針變量都稱為指針。
一、變量的地址(指針)和指向變量的地址變量(指針)
概念:
變量的指針: 就是變量的地址。
指針變量: 是用來存放地址的變量,普通變量是用來存放數據的,指針變量是存放地址的。
2.定義地址變量:
1)?格式:[ 存儲類型 ] ?數據類型 ??*指針變量名;
int i, j;
int * pointer1, *pointer2;
存儲類型是這個地址變量的存儲位置
數據類型指的是這個地址變量指向的目標變量類型,不代表本身的類型大小。
2)?指針變量的賦值:
方法一:
int ?a = 5; ?int *p = &a; //定義并初始化。
方法二:
int ?a = 5; int *p; ?p = &a;// ?先定義后賦值。
| PS: 定義時,int *p中*是為了說明該p是地址變量,用來存放地址; 定義地址變量時必須指定數據類型,不同類型指針不可相互賦值; 指針變量的數據類型不表示變量的類型,是表示該變量指向的目標的數據類型,訪問內存時讀取的內存空間大小。 |
3.指針變量引用
*和&符號
??* 定義指針變量/取地址對應的變量的內容(間接訪問);//i = 3直接,*p=3間接
??& 取變量的地址。
??
*和&互為逆運算。自右向左
3)?引用
??對指針變量賦值
p = &a;
??引用地址變量指向的內容
printf( “a=%d\n”, ?*p );
??引用變量本身的內容(即存儲的地址)
printf(“%x\n”, ?p);
| eg: int i = 188; int *p = &i; p ?指針變量,內部存放的是目標的地址; *p 目標,目標內存數據; &p 指針變量的內存地址; p = &i = &(*p) i = *p = *(&i) |
4.指針運算
指針運算就是地址運算,即指針變量中的地址作為運算量。
地址只能做算術、關系、賦值運算。
算術運算
px + n 代表指針向地址大的方向移動 n 個 數據。
移動后的地址量是: (px) + sizeof(px的類型) * n
px++ ?指針變量向地址大的方向移動一個數據。
? px - py 表示兩個相同類型指針間相差數據的個數,而不是一個地址量。
px - py的結果是 ?(px - py) /sizeof(數據類型)
px + py 的結果? 沒有任何意義
指針加發運算加的數值是增加地址本身類型的N倍大小(這在數組中訪問經常用到)
?
4)?關系運算
?
指針關系表示兩個指針在內存位置的高低關系。
不同數據區域間的指針,關系運算沒有意義。
指針和除0外的整數比較沒有意義,和0比較可以判定指針是否為空。(標準寫法為if (NULL == p) ).
5)?賦值運算
向指針變量傳遞一個地址值。這個值是地址常量或指針變量(同類型),不能是普通整數(0可以表示空值)。
6)?const/void指針
const 表示的使變量常量化,即不可修改。
int ?const a = 9;
a = 10; //報錯,a為const修飾不可改變。
const 在遇到指針時會發生一些變化
const int a 與 int const a, const可以在int的左右位置。
| ???int a = 9; ???int b = 12; ???const ?int ?*p = &a;?// const 修飾的是*p , ?pa 指向變量a, int ?const ?*p ????= &a; ?//和上面效果相同, 都表示地址變量pa指向a,且*pa不可變 ??*p ?= 10 ; // 通過p改變a的值,但*p是const類型,不可改變。 ???p = &b;?//可以改變p的值,(即指向)。 ???*p = ?11;// 同樣不可以 ???int *const q = &a; ?//const 修飾的是 q, 所以q是不能改變的,即不能改變q的指向 ???*q = 111; ????q = &b; // 將q指向b,報錯。 |
?
void 型指針
???指針變量指向不確定數據類型的變量的時候,可以定義為void型指針,
因為void類型指針可以賦值給其他任意類型的指針,而其他類型不能相互賦值.
如:malloc函數
void * malloc(size_t size);
malloc 函數因為不知道分配空間的具體用途,所以返回void型地址。
?
7)?多級指針
指向地址變量的地址變量,稱為多級指針(畫圖表示);
定義一個二級指針
int *p = &a;
int **q = &p;
?
8)?小結:指針自增與自減
ü?p++(或 p+=1): 使p指向下一個元素
ü?*p++: ?++與* 具有相同優先級且結合方向自右向左, 等價于*(p++), 先取*p的值,然后p再自加,指向下一個元素。
ü?*(p++) 與 *(++p) 作用不同。 前者是先取*p的值,再使p自加。后者先使p自加,再取自加后指向的內容。
ü?++(*p): 表示將p指向的元素的值加1.
?
二、指針與數組
指針與一維數組
數組的指針是指數組在內存中的起始地址,即第一個數組元素的地址.
一維數組的數組名代表一維數組的指針(起始地址)
[ ] 又叫做變址運算符
a[i] <=> *(a+i) 在計算機內部實現的時候,數組下標都會轉化為地址。
?
若地址變量px的地址值等于數組指針x(指針變量px指向數組的首地址),則:
x[i]、*(px+i)、 *(x+i) 和px[i]具有相同功能的功能:訪問數組第i+1個數組元素。
?
數組元素訪問過程中,數組地址與指針變量具有相同的訪問效果
不同:?地址變量是變量。
數組地址(數組名)是常量,不能自加或自減
地址變量與數組的賦值
? 1. ?int ?*p = &a[0];
2.??int *p;
p = &a[0];
?? 3. ?int *p = a;
小結:
1. p+i和a + i就是a[i]的地址, 指向a數組的第i個元素。
2. *(p+i) 或*(a+i) 是取a[i]元素的值。
3. 指向數組的地址變量也可以帶下標 p[i]和*(p+i)和*(a+i) 等效。
?
9)?指針與數組常見操作
| 數組 | 指針表示 | 含義 |
| array | &array[0] | ?數組名是第一個元素的地址 |
| *array | array[0] | ?數組的第一個元素 |
| array + i | &array[i] | ?數組第i個元素的地址 |
| *(array + i ) | *(&array[i]) == array[i] | ?數組第i個元素 |
| *array + m | array[0] + m | ?數組第一個元素加m |
| *array++ | error | error |
?
經典例子:
一維字符 指針數組ps[5] 里面存放著字符串首地址
char ?*ps[5] = {“beijing city”, ?“New York”, “London”, “Paris city”, “Moscow city”};
???????定義一個指針變量,并指向數組首地址;
???????char **pps = ps; 那么ps指向 指針數組的首地址
?
5.指針與二維數組
定義一個二維數組a,有3行4列
int a[3][4] = { {1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23} };
a 是數組名, a數組包含3行,即3個元素,分別是a[0]、a[1]、a[2]。每個元素同樣是一個一維數組,包含4個元素,a[0][0], a[0][1], a[0][2], a[0][3].
a[0] 是一維數組名,代表的是第一行的第一個元素的首地址,a[0] = &a[0][0].
a[1] 是一維數組名, 代表的是第二行的第一個元素的首地址, a[1] = &a[1][0].
| a[0][0] 是一個元素 &a[0][0] ==> a[0] 取一維數組的首地址,即一維數組名a[0], (a[0] 為第一行首地址) &a[0] ==> a 取a[0], a[1], a[2[ 三個元素中的首地址,即數組名a. &a == &a 取數組a的地址,即取數組的位置。 ? a 指向? (指向第0行首地址) *(a+0) == a[0]; a+1 指向? (指向第一行的首地址) *(a+1) == a[1]; *(a+0) + 1 是a[0][1]的地址, *(*(a+0) +1) == ?a[0][1] *(a+1) + 1 是a[1][1]的地址, *(*(a+1) + 1) == a[1][1] == *(a[1] + 1) |
| PS: ??對數組名取值就得到數組元素; ??對數組元素取址就得到當前數組地址; ??a[0][0] =(&a[0][0]) => a[0], 對第0行首元素取地址得到地址; ??a[0] ?=(&a[0]) => a, 對3個元素的第0個元素取地址得到數組名。 ??a ?=(&a) => ?&a, 對數組名取地址,得到數組地址。 思考: ??&a +1 指向? ?(指向下一個數組,增加一整個數組); ??a+1 ?指向? ??(指向下一行首地址, *(a+1) == a[1] ); ??*(a+1) + 1 指向? (指向a[1][1], *(*(a+1) + 1) == a[1][1] ); |
注意點:
a + 1 表示的是第一行首地址, ?a表示是行數組的地址變化。
a[1] ?表示的是第一行0列的地址 &a[1][0], a[1] = * (a + 1); a表示的是當前行的首地址變化
?
二維數組的行地址、列地址
行地址:二維數組名是個特殊的地址,參與運算時以行為單位移動,因此被稱為是行地址。如int a[2][3], ?a代表的是第一行的首地址,a + 1代表第二行的首地址。
列數組:int *p = a;
printf(“%d”, *(p+i) );p+i 相當于移動了i列, 因此指針p為列指針。
綜上:
???a+1與a[0] + 1表示的地址值是相同的, 但含義是不同的, a+1 是序號為第一行的首地址, a[1] 或 *(a+1)或 *a[1] + 1指向1行0列元素。
???二維數組名是指向行的,在行指針前面加上*, 就可以轉換為指向列的指針。
eg:
a和a+1是指向行的地址;
*a和*(a+1)是指向列的指針。
反之,在列指針前面加上&,就可以轉換為行指針。
eg:
a[0] 指向0行0列的列元素指針, &a[0] 與*(a + 0)
| int ?main() { ?? ???int ??a []={5,8,7,6,2,7,3}; ???int y,*p=&a[1]; ???y=(*--p)++; ???printf(“%d ?”,y); ?// 5 ???printf(“%d”,a[0]); ?//6 ?} |
?
10)?指向元素的指針變量和指向數組的指針變量(數組指針)比較
ü?指向數組元素的指針變量定義和普通指針變量相同
?
?
ü?指向由m個元素組成的一維數組的指針變量:
???
對數組指針賦值:a為二維數組名:
p = a; 或 p = &a[0];
int (*p)[4],4表示一維數組長度,且*p兩端的括號不可省略, 方括號的優先級高, int *p [4], p會先和[ ] 組成數組 p[4], 然后再和*組成 指針數組(地址數組);
p指向的是行的起始地址, *p 相當于p[0]。
6.字符串與字符指針
C中沒有字符串數據類型。通常借助字符數組來存儲字符串。
字符指針可以存儲字符串的起始地址,即指針會指向字符串第一個字符。
字符串指針初始化,可直接把內存中字符串的首地址賦予指針變量。
eg:
char *s = “Welcom!”; //相當于const char *s = “welcome!”。字符串常量是存放在常量區的,不可修改。
7.指針數組
指針數組即這個數組元素里存放的是地址(相同存儲類型和數據類型的地址集合)
說明形式:
<存儲類型> ?<數據類型> ?*<指針變量數組名> [<長度>]
int *p[2];
char ?*c[9];
11)?指針數組名含義:
表示這個數組的存儲首地址,即指針數組名為數組的地址。(和常規的數組相同,不同的是這個數組存儲地址)。
指針數組中的元素就是地址,要取得元素對應的值,直接對數組元素取值即可,或對數組名**(p+i)取值。
?
指針數組元素就是地址,數組名代表數組的起始地址, 數組名是地址的地址,即二級指針。
| 指針數組與數組指針的比較: 數組指針: ???int (*p)[n] , p是一個指針,指向整個一維數組,這個一維數組長度為n, p+1時,p增加整個數據的長度。 數組指針也稱為指向一維數組的指針, 或叫行指針。 指針數組: ???int *p[n], ?p是一個數組,這個數組元素是地址,p+1,指向下一個數組元素。 p = a屬于賦值錯誤,因為p是常量。只能對元素賦值a[0] = a, *p = a。 ? |
?
三、函數
函數定義、聲明和調用
定義:
形式:
<數據類型> ?<函數名稱> (<形式參數說明>)
{
語句集;
return (<表達式>)
}
無參函數。 有參函數,空函數
參數類型:變量,指針,數組名,數組指針,函數指針。。。。
| 聲明:函數在聲明中可以省略形參,但這樣編譯器就不能檢查實參和形參是否匹配, 所以不建議省略。 ? ? | ? |
?
8.函數傳參與返回值
??函數未調用時,形參沒有分配空間。調用時,系統為形參分配空間。調用結束后,所占內存單元被釋放。
??實參可以是常量、變量或表達式。max(a, a+b);
??函數定義時要指定數據類型。
??實參與形參的類型相同或兼容。
??C語言中,實參向形參傳遞參數是單向的,只能由實參傳遞給形參,反之不可以,在內存中,實參和形參占用的是不同的內存單元。
值傳遞與址傳遞
值傳遞
?
(分析值傳遞過程,值傳遞過程相當于隱含動作int a = x, int ?b = y)
?
址傳遞
?
典型strcpy實現
char * strcpy(char *dest, const char *source)
{
asssert(dest != NULL && source != NULL);
char * r = dest;
while ((*dest++ = *source++) != ‘\0’)
;
return dest;
}
?
12)?返回值
ü?函數的返回值由return語句返回,如:return ?z; return (z); return (表達式)三種形式都可以,括號可以省略,保持簡潔。
ü?函數值在類型不指定時,系統按整型處理。
ü?函數定義類型和返回類型保持一致。
ü?void 定義的函數表示空類型;
9.函數調用
調用形式
??函數語句:printf(“”);
??函數表達式: a = read(buff, ?fd, n);
??函數參數: m = max(a, max(a, b));
??函數調用函數的幾點說明:
被調用函數必須是存在的庫函數或自定義函數;如果使用庫函數,要在文件開頭進行#include <> 進行頭文件包含,對函數進行聲明。
調用自定義函數時,被調用函數應該在調用函數前面,在主函數中進行被調用函數的聲明。
聲明可以函數聲明可以在主函數內部或在主函數前面,也可以在主函數前面進行函數定義。函數類型省略的情況下,系統默認是int型.(思考:兩個函數相互調用怎么解決先后問題?)
?
總結
以上是生活随笔為你收集整理的指针、数组、函数阶段小结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数组、字符串及字符串相关函数
- 下一篇: 指针与数组总结