引子:數據在內存中是如何存儲的,又是如何讀取的?內存編號就是內存的地址(內存中每個字節都有一個編號,即地址)
1.概念:e
地址:內部存儲器的編號,稱爲地址。如變量a的位置編號,變量b的位置都是指針。
指針變量:專門存放地址的變量稱爲指針變量。
地址、指針、指針變量都稱爲指針。
一、變量的地址(指針)和指向變量的地址變量(指針)
1.概念:
變量的指針:
就是變量的地址。
指針變量:
是用來存放地址的變量,普通變量是用來存放數據的,指針變量是存放地址的。
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.指針變量引用
1) *和&符號
*
定義指針變量/取地址對應的變量的內容(間接訪問);//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.指針運算
指針運算就是地址運算,即指針變量中的地址作爲運算量。
地址只能做算術、關係、賦值運算。
1) 算術運算
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.
二、指針與數組
1.指針與一維數組
數組的指針是指數組在內存中的起始地址,即第一個數組元素的地址.
一維數組的數組名代表一維數組的指針(起始地址)
[ ] 又叫做變址運算符
a[i] <=> *(a+i) 在計算機內部實現的時候,數組下標都會轉化爲地址。
若地址變量px的地址值等於數組指針x(指針變量px指向數組的首地址),則:
x[i]、*(px+i)、 *(x+i)和px[i]具有相同功能的功能:訪問數組第i+1個數組元素。
數組元素訪問過程中,數組地址與指針變量具有相同的訪問效果
不同:
地址變量是變量。
數組地址(數組名)是常量,不能自加或自減
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表示的是當前行的首地址變化
1) 二維數組的行地址、列地址
行地址:二維數組名是個特殊的地址,參與運算時以行爲單位移動,因此被稱爲是行地址。如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.指針數組
指針數組即這個數組元素裏存放的是地址(相同存儲類型和數據類型的地址集合)
1) 說明形式:
<存儲類型> <數據類型> *<指針變量數組名> [<長度>]
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。 |
三、函數
1.函數定義、聲明和調用
定義:
形式:
<數據類型> <函數名稱> (<形式參數說明>)
{
語句集;
return (<表達式>)
}
無參函數。
有參函數,空函數
參數類型:變量,指針,數組名,數組指針,函數指針。。。。
聲明:函數在聲明中可以省略形參,但這樣編譯器就不能檢查實參和形參是否匹配, 所以不建議省略。 |
|
8.函數傳參與返回值
Ø 函數未調用時,形參沒有分配空間。調用時,系統爲形參分配空間。調用結束後,所佔內存單元被釋放。
Ø 實參可以是常量、變量或表達式。max(a, a+b);
Ø 函數定義時要指定數據類型。
Ø 實參與形參的類型相同或兼容。
Ø C語言中,實參向形參傳遞參數是單向的,只能由實參傳遞給形參,反之不可以,在內存中,實參和形參佔用的是不同的內存單元。
1) 值傳遞與址傳遞
值傳遞
(分析值傳遞過程,值傳遞過程相當於隱含動作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.函數調用
1) 調用形式
Ø 函數語句:printf(“”);
Ø 函數表達式: a = read(buff, fd, n);
Ø 函數參數: m = max(a, max(a, b));
Ø 函數調用函數的幾點說明:
被調用函數必須是存在的庫函數或自定義函數;如果使用庫函數,要在文件開頭進行#include <>進行頭文件包含,對函數進行聲明。
調用自定義函數時,被調用函數應該在調用函數前面,在主函數中進行被調用函數的聲明。
聲明可以函數聲明可以在主函數內部或在主函數前面,也可以在主函數前面進行函數定義。函數類型省略的情況下,系統默認是int型.(思考:兩個函數相互調用怎麼解決先後問題?)