回顧
/*************************************************************************> File Name: ? demo01.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 09時07分52秒************************************************************************/?#include <stdio.h>?/***指向一維數組指針**/int t_p1(){// 創建一個一維數組int arr[] = {10,20,30,40,50};// 計算數組大小int len = sizeof(arr) / sizeof(arr[0]); ??// 創建一個數組指針指向一維數組arrint (*p)[len] = &arr;// 借助數組指針遍歷數組for (int i = 0; i < len; i++){// p 指向 arr這個數組,p存儲了arr這個數組的地址// 如果通過指針訪問數組:*p *和[]在一起,[]的優先級大于*printf("%-4d", (*p)[i]);}printf("\n");printf("==============一維===============\n");}?/*二維*/int t_p2(){// 創建一個二維數組int arr[][3] = {{10,20,30},{100,200,300},{1000,2000,3000}};// 獲取行和列的容量int row_len = sizeof(arr) / sizeof(arr[0]);int col_len = sizeof(arr[0]) / sizeof(arr[0][0]);?// 方式1,二維數組指針指向二維數組 ? 不推薦int (*p)[][3] = &arr;?// 遍歷數組for (int i = 0; i < row_len; i++){for (int j = 0; j < col_len; j++){printf("%-6d", (*p)[i][j]);}}printf("\n");printf("===============二維===============\n");//方式2,一維數組指針指向二維數組,本質上是一體維數組指針指向二維數組的行(默認首行)推薦// &arr:獲取該二維數組的地址,范圍作用于整個數組// arr:數組名默認指向第一個元素,這里就是行,默認首行,范圍作用于整個行,等價于 &arr[0]// 數組參與指針運算,會降級為指針int (*p1)[3] = arr;for(int i = 0;i < row_len;i++){for(int j=0;j < col_len;j++){printf("%-6d",p1[i][j]);printf("%-6d",(*(p1+i))[j]);//數組參與指針運算,會降級為指針printf("%-6d",*(p1[i]+j));//列偏移printf("%-6d",*(*(p1+i)+j));//列偏移??}}printf("\n");printf("================方式2==============\n");}?int main(int argc,char *argv[]){t_p1();t_p2();return 0;}
指針
數組指針與指針數組
數組指針
指針和數組中符號優先級
?()> [] > *
通過指針引用二維數組
表示形式 | 含義 | 地址/值 |
---|---|---|
arr | 二維數組名,指向一維數組arr[0],0行首地址 | 2000 |
arr[0],*(arr+0),*arr | 0行0列元素地址,數組降級為指針 | 2000 |
arr + 1 ,?&arr[1] | 1行首地址 | 2008 |
arr[1],*(arr + 1) | 1行0列元素arr[1][0] 的地址 | 2008 |
arr[1]+2,*(arr+1)+2,&arr[1][2] | 1行2列元素arr[1][2] 的地址 | 2012 |
*(arr[1]+2),*(*(arr+1)+2),arr[1][2] | 1行2列元素arr[1][2] 的值 | 元素值為13 |
注意:二維數組中,數組整體的地址值 == 數組中0行元素的地址值 == 數組中0行0列元素的地址值
案例
案例1
需求:用指向元素的指針變量輸出二維數組元素的值
代碼:
/*************************************************************************> File Name: ? demo02.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 10時46分40秒************************************************************************/?#include <stdio.h>?int main(int argc,char *argv[]){// 定義一個二維數組int arr[3][4] = {10,20,30,40,100,200,300,400,1000,2000,3000,4000};?// 定義一個指針變量,用來指向數組中的元素int *p = /* *(arr+0) |*/ *arr /* | arr[0] */;?// 獲取元素個數 = 行容量 * 列容量int len = (sizeof(arr)/sizeof(arr[0])) * (sizeof(arr[0])/sizeof(arr[0][0]));?// 使用單層for循環遍歷二維數組,列for (; p < arr[0] + len; p++){// 每四個一行 p1 - p2 = (p1地址 - p2地址) / sizeof(type)if ((p - arr[0]) % 4 == 0 && p != arr[0]) printf("\n");?printf("%-6d", *p);}?printf("\n");?return 0;}
案例2:
需求:數組指針- 輸出二維數組任意行任意列的元素的值
代碼:
/*************************************************************************> File Name: ? demo03.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期三 10時57分26秒************************************************************************/?#include <stdio.h>?int main(int argc,char *argv[]){// 定義一個二維數組int arr[3][4] = {1,3,5,7,11,33,55,77,111,333,555,777};?// 創建一個數組指針指向二維數組int (*p)[4] = arr; // 二維數組中的第一個元素就是首行 int (*p)[3][4]?// 創建兩個變量,接收控制臺輸入的行和列int row, col;?printf("請輸入行號和列號:\n");scanf("%d,%d",&row,&col);?printf("arr[%d][%d]=%d,%d,%d,%d\n",row,col,*(*(p+row)+col),(*(p+row))[col],*(p[row]+col),p[row][col]);?return 0;}
指針數組
定義:指針數組是一個數組,數組中每一個元素都是一個指針。
特點:
先有指針,后有數組
指針數組的本質是一個數組,只是數組中的每一個元素是指針。
語法:
?數據類型 *數組名[容量];
案例:
/*************************************************************************> File Name: ? demo03.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 11時31分18秒************************************************************************/?#include <stdio.h>?int main(int argc,char *argv[]){// 定義三個變量int a = 10, b = 20, c = 30;?// 定義指針數組:先有指針,后有數組int *arr[3] = {&a, &b, &c};?// 獲取大小int len = sizeof(arr) / sizeof(arr[0]);?// 遍歷數組for (int i = 0; i < len; i++){printf("%-3d", *arr[i]);}?printf("\n");?return 0;}
建議:我們一般使用指針數組處理字符串,后續專門講解。
數組指針與指針數組的區別
對比項 | 指針數組 | 數組指針 |
---|---|---|
定義 | 數組元素均為指針的數組 | 指向一個完整數組的指針 |
存儲內容 | 存儲多個指針,每個元素指向不同內存地址 | 存儲單個指針,指向一個完整的數組(首地址) |
內存分配 | 每個指針元素獨立分配內存,可能分散 | 指向的數組內存連續,指針本身存儲數組首地址 |
語法示例 | int *arr[5] (元素為5個int*指針) | int (*arr)[5] (指向5個int的數組的指針) |
訪問方式 | 通過下標訪問指針元素,再解引用: *arr[i] | 先解引用指針得到數組,再訪問元素: (*arr)[i] |
使用場景 | 管理多個獨立指針(如字符串數組、動態結構體數組) | 操作多維數組(如傳遞二維數組的行指針) |
內存布局 | [ptr1] → 數據1 [ptr2] → 數據2 ... | ptr → [數據1][數據2]... |
示例代碼 | int a=1, b=2;int *arr[] = {&a, &b}; | int arr[2][3] = {1,2,3,4,5,6};int (*ptr)[3] = arr; |
字符數組與字符指針
字符串實現
在C語言中,表示一個字符串有以下兩種方式:
① 數組形式:用字符數組存放一個字符串
② 指針形式:用字符指針指向一個字符串
案例
/*************************************************************************> File Name: ? demo04.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 14時15分31秒************************************************************************/?#include <stdio.h>?/*** 方式1:使用字符數組實現字符串*/ void str_test1(){// 定義一個偽字符串char str[] = "I LOVE YOU";// 數組名是一個常量,也就是不支持賦值// str = "YUE QIAN"; // 編譯報錯,常量不支持修改,替代方案:strcpy(str,"YUE QIAN");printf("%s\n", str); // 數組在傳參時,會被降級為指針}?/*** 方式2:使用字符指針指向字符串*/ void str_test2(){// 定義一個偽字符串char *str = "I LOVE YOU"; // 指針str指向一個字符串常量// 改變str的指向// str = "YUE QIAN";printf("%s\n", str);}?int main(int argc,char *argv[]){str_test1();str_test2();?char arr[200];char *str = arr; // 局部變量,如果未初始化,默認是隨機值,如果是指針變量,地址值就是隨機printf("請輸入一個字符串:\n");scanf("%s", str);printf("%s\n", str);?return 0;}
??
注意:字符數組和字符指針變量都能實現字符串的存儲與運算。(字符指針---> 字符類型的指針變量)
字符數組和字符指針的聯系
概念
字符數組由元素組成,每個元素中存放一個字符;而字符指針(指向char類型的指針變量)中存放的是地址;
只能對字符數組中的各個元素賦值,而不能用賦值語句對整個字符數組賦值。
char arr[3] = {}; ? // 等價于 {'\0'}; 等價于 {0};arr[2] = 'A'; ? ? ? // 正確,對字符數組的元素賦值?arr = {'E','D','F'};// 錯誤,數組名是常量,不能對其進行整體賦值
字符數組名雖然代表地址,但是數組名的值不能改變,因為數組名是常量。
char a = 'A';char arr[50] = {};?char *p = arr; // 指針p指向數組第一個元素,p存儲的是arr數組中第一個元素的地址?p = &a; ? // 改變指針p的指向,使其指向變量a,p存儲的是a的地址?arr = &a; // 錯誤:數組名雖然是指針,但是數組名同時也是常量,所以不能賦值?p++; // p指向arr的第2個元素arr+5; ?*p = 'W';printf("%c %c\n", *p, *arr);?return 0;}?
對于字符串中字符的存取,可以用下標法,也可以用指針法。
/*************************************************************************> File Name: ? demo05.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年05月21日 星期三 14時58分00秒************************************************************************/?#include <stdio.h>?int main(int argc,char *argv[]){// 使用兩種方式創建字符串char str1[] = "你好,雙哥哥!";char *str2 = ?"你好,豪哥哥!";// 0x11?// 賦值測試// str1 = "你好,強哥哥!"; // 錯誤,數組一旦創建,就無法改變其值。str2 = "你好,帥哥哥!";// 0x12?// 打印測試printf("%s,%s\n", str1, str2);?// 測試從控制臺獲取一個字符串// char *str;// 此時默認值是NULL,NULL對應的空間地址0x00000000,這塊空間拒絕訪問// printf("請輸入一個字符串:\n");// scanf("%s",str);// printf("輸出-%s\n",str);// 輸出NULL?// 注意:從控制臺接收一個字符串只能用字符數組char a[] = "I LOVE YOU!";char *b ?= "I LOVE YOU!";?printf("%c,%c\n%c,%c\n%s,%s\n",a[2],*(a+2),b[2],*(b+2),a+2,b+2);// L,L L,L 1 2return 0;}
字符串作為形參
定義
實參與形參都可以是字符數組
void fun(char str[], int len) {..}void main(){char str[] = "hello";int len = sizeof(str) / sizeof(str[0]);fun(str, len)}
實參用字符數組,形參用字符指針
void fun(char *str, int len) {..}void main(){char str[] = "hello";int len = sizeof(str) / sizeof(str[0]);fun(str, len)}
形參和實參都是字符指針。(在函數內部不能對字符串常量中的字符做修改)
?/*************************************************************************> File Name: ? demo06.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 15時26分14秒************************************************************************/?#include <stdio.h>?void fun(char *str, int len) {printf("%s\n", str);// 0x1000 str依然指向“hello"這個常量空間?// *(str+1) = 'E';// 編譯錯誤 不能修改"hello"這個常量空間的數據?// str[2] = 'L'; // 編譯錯誤?str = "zhangsanfeng"; // 0x2000 此時并沒有改變常量空間的數據,只是改變了指針的指向?printf("%s\n", str);}?void main(){char *str = "hello"; // 0x1000 str指向的"hello"是一個常量空間,常量空間不支持修改int len = sizeof(str) / sizeof(str[0]);fun(str, len); // 0x1000}
實參是字符指針,形參是字符數組。(在函數內部不能對字符串常量中的字符做修改)
/*************************************************************************> File Name: ? demo06.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 15時26分14秒************************************************************************/?#include <stdio.h>?void fun(char str[], int len) {printf("%s\n", str);// 0x1000 str依然指向“hello"這個常量空間?// *(str+1) = 'E';// 編譯錯誤 不能修改"hello"這個常量空間的數據?// str[2] = 'L'; // 編譯錯誤?str = "zhangsanfeng"; // 此時并沒有改變常量空間的數據,只是改變了指針的指向?printf("%s\n", str);}?void main(){// char str[] = {'h','e','l','l','o','\0'}; // 這個可以看做是字符串變量,這個是支持修改元素char *str = "hello"; // 0x1000 str指向的"hello"是一個常量空間,常量空間不支持修改int len = sizeof(str) / sizeof(str[0]);fun(str, len);// 0x1000}
注意
字符數組在創建的時候,會在內存中開辟內存空間,內存空間可以存放字符數據;字符指針在創建的時候,需要依賴于字符數組,字符指針在內存開辟的內存空間中,存放的是數組元素的地址。字符指針的創建依賴于字符數組,字符數組可以獨立存在,而字符指針不能獨立存在。
字符數組可以初始化,但是不能賦值;字符指針可以初始化,也可以賦值。
char str1[] = "hello"; // 對數組初始化str1 = "hi"; // 對數組賦值,此時錯誤str1[0] = 'H'; ?// 對數組中的元素賦值,此時正確
案例
案例1
字符指針作為函數參數:用函數調用實現字符串的復制以及長度計算
代碼:
/*************************************************************************> File Name: ? demo07.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 15時50分22秒************************************************************************/?#include <stdio.h>?/*** 定義一個函數,實現字符串拷貝* @param source 拷貝的源字符串,該字符串不能被修改* @param dest 需要拷貝的目標數組* @return 字符串的長度*/ int _str_cpy(const char *source, char *dest){// 定義一個循環變量register int i = 0;?// 遍歷循環while (source[i] != '\0'){// 實現拷貝*(dest + i) = *(source + i); // 等價于 dest[i] = source[i];i++;}?// 拷貝結束,一定要給dest中插入\0*(dest + i) = '\0';?return i;}?int main(int argc,char *argv[]){char source[20],dest[20];?printf("請輸入一個字符串:\n");scanf("%s", source);?int size = _str_cpy(source, dest);?printf("字符串:%s的長度是%d\n", dest, size);?return 0;}
案例2
需求:字符指針作為函數的參數-給定一個字符串,截取start到end之間的字符串,含頭不含尾
代碼:
?/*************************************************************************> File Name: ? demo08.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 16時07分40秒************************************************************************/?#include <stdio.h>?/*** 定義一個函數,實現字符串的截取* @param source 源字符串(字符數組、字符串常量、字符指針)* @param start 開始位置* @param end ? 結束位置* @param dest ? 目標數組(字符數組)* @return 目標字符串長度*/?int str_substr(char *source,int start,int end,char *dest){register int i = 0,k=0;// 遍歷字符串while(source[i] != '\0'){// 根據start和end截取if(i >= start && i < end)// 含頭不含尾{*(dest + k) = *(source + i);k++;}i++;}*(dest + k) = '\0';?return k;}?int main(int argc,char *argv[]){char *str = "abcdefg";char dest[26];int len = str_substr(str,2,5,dest);?printf("%s,%s,%d\n",str,dest,len);return 0;}
函數指針與指針函數
指針函數
定義:
本質上是上函數,這個函數的返回值類型是指針,這個函數稱之為指針函數。(返回值是指針的函數叫做指針函數)
語法:
?//寫法1返回類型* ?函數名(形參列表){函數體;return 指針;}?//寫法2返回類型 ?*函數名(形參列表){函數體;return 指針;}
舉例:
?int *get(int a){int *p = &a;return p;}?int main(){int *a = get(5);printf("%d\n", *a);}
注意:
在函數中不要直接返回一個局部變量的地址。因為函數調用完畢后,隨著棧幀的回收,變量空間會銷毀,使得返回的地址就不明確,此時返回的指針叫做野指針。
解決方案:
如果非要訪問,可以給這個局部變量添加(定義的時候添加)static
,可以延長它的生命周期,從而避免野指針(盡量少用,因為存在內存泄漏)
演示案例:
/*************************************************************************> File Name: ? demo0.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期四 14時22分07秒************************************************************************/#include <stdio.h>int *add(int a, int b){static int sum;sum = a + b;return ∑// 執行完return 作為函數作用域的布局變量sum的空間被釋放}int main(int argc,char *argv[]){int *res = add(5,3); ?// 接收到了地址,但是地址對應的空間已經釋放printf("%d\n", *res);return 0;}
案例
需求:有若干個學生,每個學生有4門成績,要求在用戶輸入學號(int id)后,能輸出該學生的全部成績(float scores[4]),用指針函數實現。
代碼:
?/*************************************************************************> File Name: ? demo09.c> Author: ? ? ? 阮> Description: ?> Created Time: 2025年07月28日 星期一 17時11分38秒************************************************************************/?#include <stdio.h>?/*** 定義一個函數,要求輸入學號,返回該學號對應學生的4門成績* @param all:所有人的成績傳進來 查找源頭* @param id:要檢索學生的學號* @return id對應學生的4門成績*/ float* search(float (*all)[4], int id){// 定義一個指針變量,用來接收查詢到的學生的所有成績float *pt;?pt = *(all + id);// 行偏移 {10,20,30,40}?return pt;}?int main(int argc,char *argv[]){// 準備一個二維數組,存儲3個學生的成績float scores[3][4] = {{60,70,80,90}, // 0x2000 0x2004 0x2008 0x200C{66,77,88,99}, // 0x2010 0x2014 0x2018 0x201C{61,71,81,91}};?// 定義一個變量,用來接收學生學號int id;printf("請輸入學生學號(0~2):\n");scanf("%d", &id);printf("第%d個學生的成績:\n", id);?// 創建一個指針,用來接收成績float *p;// 0x11 --> 0x2000p = search(scores, id);// 0x21 --> 0x2000?// 遍歷for (; p < scores[id] + 4; ?p++){printf("%5.2f\t", *p);}// *(p + i)printf("\n");?return 0;}
章節練習
利用指針變量將一個數組中的數據反向輸出。
利用指針變量計算下標為奇數的數組的和;
確認整型,字符型,浮點型指針變量的大小;
利用指針變量輸出字符數組中的所有字符。
編寫一個函數,用指針變量做參數,用于求出一個浮點型數組元素的平均值。
編寫函數,要求用指針做形參,分別實現以下功能: (1)求一個字符串長度 (2)在一個字符串中統計大寫字母的個數 (3)在一個字符串中統計數字字符的個數
編寫函數,要求用指針做形參,實現將二維數組(行列相同)的進行轉置(行列數據互換): int (*p)[N]
編寫函數,要求用指針做形參,實現統計二維數組上三角中的0 的數量:
編寫一個指針函數,返回二維數組中最大元素的地址。
面試題
1)定義整形變量i; 2)p為指向整形變量的指針變量; 3)定義整形一維數組p,它有n 個整形元素; 4)定義一維指針數組p,它有n個指向整形變量的指針元素; 5)定義p為指向(含有n個整形元素的一維數組)的指針變量; 6)p為返回整形函數值的函數; 7)p為返回一個指針的函數,該指針指向整形數據; 8)p為指向函數的指針變量,該函數返回一個整形值; 9)p是一個指向整形指針變量的指針變量;
動態申請一個具有10個float類型元素的內存空間,從一個已有的數組中拷貝數據,并找出第一次出現 12.35 的下標位置,并輸出。
動態申請一個整型數組,并給每個元素賦值,要求刪除第3個元素;
動態申請一個整型數組,并給每個元素賦值,要求在第4個元素后插入100;
附加題【選做】: 編寫一個函數,實現 memmove 的功能。