指針(續)
main函數原型
定義
main函數有多種定義格式,main函數也是函數,函數相關的結論對main函數也有效。
main函數的完整寫法:
int main(int argc, char *argv[]){..}int main(int argc, char **argv){..}
擴展寫法:
main(){} 等價 int main(){} // C11之后不再支持 缺省 返回類型int main(void){} 等價 int main(){}void main(void){} 等價 void main(){}int main(int a){}int main(int a, int b, int c){}...
說明
① argc,argv是形參,他們倆可以修改
② main函數的擴展寫法有些編譯器不支持,編譯報警告
③ argc和argv的常規寫法
- argc:存儲了參數的個數,默認是1個,也就是運行程序的名字
- argv:存儲了所有參數的字符串形式
④ main函數是系統通過函數指針的回調調用。
演示
代碼:
#include <stdio.h>int main(int argc, char **argv) // {"abc","aaa"} 對行地址解引用,得到首列地址
{// 訪問參數個數 argcprintf("argc=%d\n", argc);// 遍歷參數(每一個參數都是一個字符串常量)for(int i=0;i< argc; i++){printf("%s,%s\n", argv[i], *(argv+i));}printf("\n");
}
運行結果:
常量指針與指針常量
常量類型
① 字面量:直接使用固定值(如:12,hello,orange, 楊家輝三角),符號常量和枚舉在編譯器轉換為了字面量
② 只讀常量:用const
修飾的變量,初始化之后不可修改。
const int a = 10; // 只讀常量
a = 21; // 編譯報錯
常量指針
-
本質:指向常量數據的指針
-
語法:
const 數據類型 *變量名; const 數據類型* 變量名;
-
舉例:
const int *p; // p是常量指針
-
特性:
- 指向對象的數據不可改變(
int a = 10; const int *p = &a; *p = 20;
,非法) - 指針本身的指向可以改變(
int a = 10, b = 20; const int *p = &a; p = &b;
,合法)
- 指向對象的數據不可改變(
-
案例:
#include <stdio.h>int main() {int a = 10; // 變量const int *p = &a; // 常量指針// *p = 100; // 錯誤,指針指向的數據不可改變printf("%d\n", *p);// 10int b = 20; // 變量p = &b; // 正確,指針指向可以改變printf("%d\n", *p);// 20 }
指針常量
-
本質:指針本身是常量,指向固定地址
-
語法:
數據類型* const 變量名; 數據類型 *const 變量名;
-
特性:
- 指向對象的數據可以改變(
int a = 10; int* const p = &a; *p = 20;
,合法) - 指針本身的指向不可改變(
int a = 10, b = 20; int* const p = &a; p = &b;
,非法)
- 指向對象的數據可以改變(
-
注意:
定義時必須初始化:
int a = 10; int* const p = &a; // 正確
-
案例:
#include <stdio.h>int main() {int a = 10; // 變量int* const p = &a; // 指針常量*p = 100; // 正確,指針指向的數據可以改變printf("%d\n", *p);// 100int b = 20; // 變量// p = &b; // 錯誤,指針指向不可改變printf("%d\n", *p);// 100 }
常量指針常量
-
本質:指針指向和指向對象的數據都不可改變
-
語法:
const 數據類型* const 變量名; const 數據類型 *const 變量名;
-
舉例:
const int* const p; // p是常量指針常量
-
特性:
- 指向對象的數據不可改變(
int a = 10; int* const p = &a; *p = 20;
,非法) - 指針本身的指向不可改變(
int a = 10, b = 20; int* const p = &a; p = &b;
,非法)
- 指向對象的數據不可改變(
-
注意:
定義時需要初始化:
int a = 10; const int *const p = &a; // 正確
簡單理解:不管是常量指針、指針常量還是常量指針常量,本質上都是一個賦值受到限制的指針變量。
總結對比
類型 | 語法 | 指向可變 | 數據可變 |
---|---|---|---|
常量指針 | const int *p | ?? | ? |
指針常量 | int *const p | ? | ?? |
常量指針常量 | const int *const p | ? | ? |
關鍵點
const
在*
左側:修飾數據(常量指針)const
在*
右側:修飾指針(指針常量)- 函數參數優先使用常量指針,提高代碼安全性
- 指針常量必須初始化,且不可重新指向
野指針、空指針、空懸指針
野指針
定義:
指向無效內存區域(比如未初始化、已釋放或者越界訪問)的指針稱之為野指針。野指針會導致未定義(UB)行為。
危害:
- 訪問野指針可能引發段錯誤(Segmentation Fault)
- 可能破壞關鍵內存數據,導致程序崩潰。
產生場景:
-
指針變量未初始化
int *p; // p未初始化,是野指針 printf("%d\n", *p); // 危險操作:p就是野指針
-
指針指向已釋放的內存
int *p = malloc(sizeof(int)); // 在堆區申請1個int大小的內存空間,將該空間地址賦值給指針變量p free(p); // 釋放指針p指向的空間內存 printf("%d\n", *p); // 危險操作:p就是野指針
-
返回局部變量的地址
int* fun(int a, int b) {int sum = a + b; // sum就是一個局部變量return ∑ // 將局部變量的地址返回給主調函數 }int main() {int *p = fun(2,3);printf("%d\n", *p); // 危險操作:p就是野指針 }
如何避免野指針:
-
初始化指針為NULL
-
釋放內存后立即置指針為NULL
-
避免返回局部變量的地址
-
使用前檢查指針有效性(非空校驗,邊界檢查)。
int fun(int *pt) {int *p = pt;// 校驗指針if(p == NULL) // 結果為假 等價于 if(!p) 其實底層: if(p == 0){printf("錯誤!");return -1;}printf("%d\n", *p);return 0; }
空指針
**定義:**值為NULL
的指針,指向地址0x000000000000
(系統保留,不可訪問)
**作用:**明確表示指針當前不指向有效內存,一般用作指針的初始化。
示例:
int *p = NULL; // 初始化為空指針free(p); // 釋放后置空
p = NULL;
空懸指針
**定義:**指針指向的內存已經被釋放,但未重新賦值。空懸指針是野指針的一種特例。
示例:
char *p = malloc(100); // 在堆區分配100個char的空間給p
free(p); // 釋放指針p指向的內存空間
printf("%p,%d\n", p, *p); // p可以正常輸出,*p此時屬于危險操作
// p指向的內存空間被回收,但是p指向空間的地址依然保留,此時這個指針被稱作空懸指針
void與void*的區別
定義
-
void: 表示“無類型/空類型”,用于函數返回類型或者參數。
void func(void); // 沒有返回值也沒有參數,一般簡寫:void func();
-
*void:**通用指針類型(萬能指針),可指向任意類型數據,但需要強制類型轉換后才能解引用。
void* ptr = malloc(4); // ptr指向4個字節大小的堆內存空間 // 存放int類型數據 int *p = (int*)ptr; *p = 10;// 存放float類型數據 float* p1 = (float*)ptr; *p = 12.5f;// 存放char類型數組 char* p2 = (char*)ptr;// 以下寫法完全錯誤 float* ptr = malloc(4); int *p = (int*)ptr; // 此時編譯報錯,類型不兼容 float* int*
注意:只能是具體的類型(
int*,double*,float*,char*...
)和void*
之間轉換
注意事項
void
不能直接解引用(*ptr 會報錯
)- 函數返回
void*
需要外部接收的時候明確類型(不明確類型,就無法解引用)
示例
#include <stdio.h>/*** 定義一個返回類型為void*類型的指針函數*/
void* proces_data(void* p)
{return p;
}int main(int argc, char *argv[])
{// int類型int m = 10;int* p_int = &m;int* result_int = (int*)proces_data(p_int);printf("Integer value:%d\n", *result_int);// double類型double pi = 3.1415926;double* p_double = πdouble* result_double = (double*)proces_data(p_double);printf("Double value:%lf\n", *result_double);// void* p_void = proces_data(p_double);// printf("Void value:%lf\n", *p_void);// *p_void = 20;// 注意:void* 修飾的指針是可以進行賦值操作的,但是不能對其解引用return 0;
}
運行結果: