目錄
1 初始指針
2 獲取變量的地址
3?定義指針變量、取地址、取值
3.1 定義指針變量
3.2 取地址、取值
4 對指針變量進行讀寫操作
5 指針變量作為函數參數
6 數組與指針
6.1 指針元素指向數組
6.2?指針加減運算(了解)
6.2.1 指針加減具體數字?
6.2.2 指針加減指針
6.3?數組名與數組元素首地址的關系
6.4?數組作為函數參數
7?二維數組以及字符串與指針
7.1?二維數組的定義方法
7.1.1 定義一個二維數組?
7.1.2?訪問二維數組
7.2?定義字符串的幾種方法
7.3?字符串數組
7.4 strcat 連接字符串
7.5?strcpy 字符串復制(拷貝)函數
1 初始指針
通過前面的教程我們知道變量是用來存儲數據的,變量的本質是給存儲數據的內存地址起了一個好記的別名
比如我們定義了一個變量 int a= 10 ,這個時候可以直接通過a這個變量來讀取內存中保存的10 這個值,在計算機底層a這個變量其實對應了一個內存地址
指針也是一個變量,但它是一種特殊的變量,它存儲的數據不是一個普通的值,而是另一個變量的內存地址
每一個變量都有一個內存位置,每一個內存位置都定義了可使用連字號(&)運算符訪問的地址,它表 示了在內存中的一個地址
剛開始學C語言的指針操作,我們只需要記住兩個符號 :&(取地址) 和 *(根據地址取值 /定義指針變量)
2 獲取變量的地址
每個變量在運行時都擁有一個地址,這個地址代表變量在內存中的位置。C語言中使用&字符放在變量 前面對變量進行取地址操作
#include <stdio.h>
int main(void)
{int a = 10;printf("a的地址是:%p\n", &a);return 0;
}
3?定義指針變量、取地址、取值
3.1 定義指針變量
指針是一個變量,其值為另一個變量的地址,即,內存位置的直接地址。就像其他變量或常量一樣,必須在使用指針存儲其他變量地址之前,對其進行聲明。指針變量聲明的一般形式為:
type *var-name;
在這里,type 是指針的基類型,它必須是一個有效的 C 數據類型,var-name 是指針變量的名稱。用 來聲明指針的星號 * 與乘法中使用的星號是相同的。但是,在這個語句中,星號是用來指定一個變量是 指針
以下是有效的指針聲明:
int* ip; //一個整型的指針
double* dp; //一個 double 型的指針
float* fp; //一個浮點型的指針
char* ch //一個字符型的指針
所有指針的值都是一個地址,不管是整型、浮點型、字符型,還是其他的數據類型,都是一樣的,都是 一個代表內存地址的長的十六進制數。不同數據類型的指針之間唯一的不同是,指針所指向的變量或常量的數據類型不同
3.2 取地址、取值
#include <stdio.h>
int main(void)
{// 定義一個int變量aint a = 10;printf("a的地址是:%p\n", &a);// 定義int類型的指針變量pint* p_a = &a;printf("指針p_a的值:%p\n", p_a);printf("指針p_a的地址:%p\n", &p_a);printf("a的值:%d\n", a);printf("根據指針p_a的值去內存取值得到的結果為:%d", *p_a);return 0;
}
總結:
- 取地址操作符&和取值操作符是一對互補操作符, & 取出地址, 根據地址取出地址指向的值
- 對變量進行取地址(&)操作,可以獲得這個變量的地址
- 指針變量的值是地址
- 對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值
4 對指針變量進行讀寫操作
#include <stdio.h>
int main(void)
{int a = 10;int b = 20;int* p1, * p2; // 定義指針變量 p1、 p2p1 = &a;// p1 指向變量 ap2 = p1;// p2 指向變量 aprintf("&a=%p p1=%p p2=%p\n", &a, p1, p2);*p1 = 20;printf("a的值%d,取指針得到的值%d", a, *p1);return 0;
}
運行結果:
&a=000000612A17FA74 p1=000000612A17FA74 p2=000000612A17FA74
a的值20,取指針得到的值20
5 指針變量作為函數參數
在C語言中,函數參數不僅可以是字符型、整型、浮點型等,還可以是指針類型,作用是將變量地址傳遞給函數形參
#include <stdio.h>
void modify1(int *c)
{*c = 20;
}
int main(void) {int a = 10;modify1(&a);printf("%d", a);return 0;
}
6 數組與指針
6.1 指針元素指向數組
數組本質上是一片連續的內存空間,每個數組元素都對應一塊獨立的內存空間,它們都有相應的地址。
因此,指針變量既然可以指向變量,也就可以指向數組元素
數組的構成本質上是:數組名[偏移量]
數組名就代表第一個元素的地址,偏移量就是在此基礎上偏移
數組的元素本質上完全可以當做是單獨的變量對待,只不過沒有名稱而已
int a[5]={1,2,3,4,5}; //定義長度為5的int數組
int *p_a=&a[0]; //定義指向int變量的指針變量p_a,把a數組第0個元素地址賦給指針變量p_a
注意:&a[0]等價于&(a[0]),由于[ ]運算符比取地址運算符&優先級高,因此&(a[0])中的小括號可以省略,簡寫為:&a[0]
在計算機中內存的最小單位是字節,每個字節都對應一個地址
如果一個變量占用多個字節,就會占用 多個內存地址
例如: char 類型變量占1字節就對應1個地址,short 類型變量占2字節對應2個地址,int 類型變量占4 字節對應4個地址.....·其他類型依次類推
同理,數組元素類型不同占用的內存地址也不同
下面通過例子來驗證以上分析
#include<stdio.h>
int main(void)
{char c[5];short s[5];int i;for (i = 0; i < 5; i++){printf("&c[%d]=%p ", i, &c[i]);printf("&s[%d]=%p \n", i, &s[i]);}return 0;
}
運行結果:
&c[0]=000000000061FE17 &s[0]=000000000061FE0C
&c[1]=000000000061FE18 &s[1]=000000000061FE0E
&c[2]=000000000061FE19 &s[2]=000000000061FE10
&c[3]=000000000061FE1A &s[3]=000000000061FE12
&c[4]=000000000061FE1B &s[4]=000000000061FE14
說明:不同設備上面輸出的地址可能不一樣的,數組中每個元素地址都是連續的
6.2?指針加減運算(了解)
6.2.1 指針加減具體數字?
指針本質上就是內存地址,在32 位操作系統下,內存地址是 4 字節的整數。既然是整數就可以進行 加、減、乘、除等算術運算。不過需要注意的是,在 C 語言中一般只討論指針加、減運算,乘、除等其 他算術運算都沒有意義
在實際開發中,指針加、減運算多用于數組(或連續內存空間)。當指針變量p 指向數組元素時,p+1 表 示指向下一個數組元素,p-1 表示指向上一個數組元素
#include <stdio.h>
int main(void)
{int a[3] = { 1, 2, 3 };int* p = &a[0];// 指針 p 指向 a[0]printf("%p %d\n", p, *p); // 輸出 a[0] 的地址 和 a[0] 的值p = p + 1;// p 加 1printf("%p %d\n", p, *p); // 輸出 a[1] 的地址 和 a[1] 的值return 0;
}
運行結果:
000000811557F968 1
000000811557F96C 2
注意:實現指針加減的時候需要注意越界問題
6.2.2 指針加減指針
在C語言中,兩個指針相加是沒有意義的,而兩個指針相減卻有特殊的意義,不過只有當兩個指針都指向同一數組中的元素時才有意義
一個數組中的元素時,對它們進行減法運算,得到的結果是這兩個指針所指向元素之間相隔的元素個數 (不是之間有的個數,之間有的元素數要在此基礎上減一),而不是它們地址差值的字節數。其計算方式是用兩個指針的地址差值除以指針所指向數據類型的大小
用公式表示為:
(指針2的地址 - 指針1的地址) / sizeof(指針所指向的數據類型)
6.3?數組名與數組元素首地址的關系
C語言中,數組名與數組首元素地址等價。也就是說,在程序中,輸出數組名與輸出數組首元素地址是相同的
#include <stdio.h>
int main(void)
{int num[5];printf("%p\n", num); // 輸出數組名printf("%p\n", &num[0]); // 輸出數組首元素地址return 0;
}
輸出結果:
000000000061FE00
000000000061FE00
我們就可以通過把數組名復制給指針變量,來把數組首地址給指針變量
#include <stdio.h>int main(void){int num[5] = {1, 2, 3, 4, 5};int *p_num = num; // 把num的首地址賦值給指針變量printf("p_num指針的值=%p \n p_num對應數組元素的值%d", p_num, *p_num);return 0;}
我們也可以通過p_num來訪問數組里面的每個元素
#include <stdio.h>int main(void){int num[5] = {1, 2, 3, 4, 5};int *p_num = num; // 把num的首地址賦值給指針變量printf("p_num指針的值=%p ,num的地址是=%p p_num對應數組元素的值%d\n", p_num, num,
*p_num);printf("p_num訪問第一個元素=%d\n", p_num[0]);printf("p_num訪問第二個元素=%d\n", p_num[1]);return 0;}
運行結果:
p_num指針的值=000000000061FE00 ,num的地址是=000000000061FE00 ?p_num對應數組元素的值1
p_num訪問第一個元素=1 p_num訪問第二個元素=2
6.4?數組作為函數參數
函數參數不僅可以是變量,也可以是數組,數組作函數參數的作用是將數組首元素地址傳給函數作形參。 在 C 語言中,數組作函數參數時,是沒有副本機制的,只能傳遞地址。也可以認為,數組作函數參數時,會退化為指針
#include <stdio.h>
void getSize(int nums[10]) // 定義 getSize 函數
{int size = sizeof(nums); // 計算數組 nums 的總字節數printf("指針 size=%d\n", size);
}
int main(void)
{int nums[10] = { 1, 2, 3, 4, 5 };int size = sizeof(nums); // 計算數組 nums 的總字節數printf("外部 size=%d\n", size);getSize(nums); // 調用 getSize 函數return 0;
}
?運行結果:
外部 size=40
指針 size=8
所以直接把函數的形參的類型設置為指針,來接收數組的第一個地址:?
#include <stdio.h>
void getSize(int* nums) // 定義 getSize 函數
{int size = sizeof(nums); // 計算數組 nums 的總字節數printf("指針 size=%d\n", size);
}
int main(void)
{int nums[10] = { 1, 2, 3, 4, 5 };int size = sizeof(nums); // 計算數組 nums 的總字節數printf("外部 size=%d\n", size);getSize(nums); // 調用 getSize 函數return 0;
}
運行結果:
外部 size=40
指針 size=8
分析:
32位系統中指針變量占4個字節,64位系統中指針變量占8個字節,所以getSize中num的字節數是8。在 C 語言中,數組作函數參數時,是沒有副本機制的,只能傳遞地址,所以 的形參 int *nums應該是指針變量, void getSize方法 getSize(nums) 傳入的nums并不是數組的副本,而是數組首元素的地址?
注意:因為沒有副本機制,所以如果在函數內部修改數組內容,數組的內容會直接被修改,而不是修改副本
#include <stdio.h>
void getSize(int* nums) // 定義 getSize 函數
{nums[0] = 3;
}
int main(void)
{int nums[10] = { 1, 2, 3, 4, 5 };printf("%d\n", nums[0]); //加\n是為了換行getSize(nums); // 調用 getSize 函數printf("%d", nums[0]);return 0;
}
練習:封裝比較數組最大值函數
#include <stdio.h>
int getMax(int* nums, int length) // 定義函數 getMax
{int i;int max = nums[0]; // 默認 nums[0] 為最大值for (i = 1; i < length; i++) // 下標從 1 開始遍歷{if (max < nums[i]) // 比較大小{max = nums[i]; // 覆蓋最大值}}return max; // 返回最大值
}int main(void)
{int nums[10] = { 11, 22, 3, 24, 15, 8, 99, 21, 35, 0 };int length = sizeof(nums) / sizeof(nums[0]); // 計算數組長度int max = getMax(nums, length); // 返回最大值printf(" 最大值為 :%d\n", max);return 0;
}
7?二維數組以及字符串與指針
7.1?二維數組的定義方法
C語言中二維數組和一維數組類似,簡單理解就是:二維數組由多個一維數組構成
7.1.1 定義一個二維數組?
type arrayName[x][y];
例:
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
內部嵌套的括號是可選的,下面的初始化與上面是等同的:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
7.1.2?訪問二維數組
#include <stdio.h>
#include <string.h>
void main(void)
{int a[3][4] = { {1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23} };printf("a[0][2]=%d", a[0][2]);
}
運行代碼:
a[0][2]=5
7.2?定義字符串的幾種方法
我們已經學習的定義方法:
#include <stdio.h>
int main(void)
{char chs1[] = { 'i', 't', 'y', 'i', 'n', 'g', '\0' };char chs2[] = "itying";printf("%s\n", chs1);// 以 %s 格式輸出 strprintf("%c\n", chs1[2]); // 以 %c 格式輸出一個字符printf("%s\n", chs2); // 以 %s 格式輸出 strprintf("%c", chs2[2]); // 以 %c 格式輸出一個字符return 0;
}
我們也可以使用字符指針引用字符串:
#include <stdio.h>
int main(void)
{char chs1[] = "itying";char* str1 = chs1;printf("%s\n", str1); // 以 %s 格式輸出 strprintf("%c\n", str1[2]); // 以 %c 格式輸出一個字符
//---------------------------------------------------------------------------char* str2 = "this is str";printf("%s\n", str2); // 以 %s 格式輸出 strprintf("%c\n", str2[2]); // 以 %c 格式輸出一個字符return 0;
}
通過上面示例我們可以通過三種方法定義字符串了
char chs1[] = {'i', 't', 'y', 'i', 'n', 'g', '\0'};
char chs2[] = "itying";
char *chs3 = "itying";
7.3?字符串數組
定義了一個指針數組,即數組中的每個元素都是一個 char 類型的指針:
char *arr[]?
例:
char *s[]={ "馬總", "張總", "王麻子" };
當使用?{ "馬總", "張總", "王麻子" }?對這個數組進行初始化時,會把每個字符串常量的首字符地址分別賦給數組?arr?的各個元素:
- arr[0]?被賦值為字符串?"馬總"?的首字符地址,通過這個指針,就可以訪問該字符串的所有字符
- arr[1]?被賦值為字符串?"張總"?的首字符地址
- arr[2]?被賦值為字符串?"王麻子"?的首字符地址
在實際處理中文時,通常會使用多字節字符編碼,比如常見的 UTF - 8 編碼或 GBK 編碼
UTF - 8 是一種變長編碼,一個中文字符通常用 3 個字節來表示。對于字符串?"張總",“張” 字在 UTF - 8 編碼下會占用 3 個字節的存儲空間,arr[1]?指向的就是這 3 個字節中第一個字節的存儲地址
7.4 strcat 連接字符串
前面我們已經學習了?strlen 計算字符串數組有效長度、sprintf 字符串格式化函數、字符串拼接、整型轉換成字符串等字符串知識,詳細見【C語言】第七期——字符數組、字符串、類型轉換
現在我們繼續學習相關知識
原型:
char *strcat(char *dest, const char *src); //使用前需先引入頭文件<string.h>
參數:
- dest:目標字符串的指針,拼接后的結果將存儲在這個字符串中。dest 必須有足夠的空間來容納 src 字符串的內容以及拼接后的結果
- src:源字符串的指針,它將被追加到 dest 字符串的末尾。src 字符串本身不會被修改
返回值:
函數返回一個指向目標字符串 dest 的指針
工作原理:
strcat 函數會從 dest 字符串的第一個空字符 '\0' 開始,將 src 字符串的內容復制到 dest 的末尾,直到遇到 src 字符串的空字符 '\0' 為止,最后,dest 字符串會以空字符 '\0' 結尾
#include <stdio.h>
#include <string.h>int main() {// 定義目標字符串和源字符串char dest[50] = "Hello, ";const char src[] = "World!";// 使用 strcat 函數將 src 追加到 dest 的末尾strcat(dest, src);// 輸出結果printf("拼接后的字符串: %s\n", dest);return 0;
}
注意事項:
- 內存空間:dest 數組必須有足夠的空間來容納 src 字符串的內容,否則會導致緩沖區溢出,這可能會引發程序崩潰或安全漏洞
- 空字符:dest 字符串必須以空字符 '\0' 結尾,否則 strcat 函數無法確定從哪里開始追加
- 字符串重疊:dest 和 src 所指向的字符串不能重疊,否則會導致未定義行為
7.5?strcpy 字符串復制(拷貝)函數
原型:
char *strcpy(char *dest, const char *src);
參數:
- dest:指向目標字符數組的指針,用于存儲復制后的字符串。目標數組必須有足夠的空間來容納源字符串及其終止的空字符 '\0'
- src:指向源字符串的指針,即要被復制的字符串。該參數被聲明為 const,表示在函數內部不會修改源字符串
返回值:
strcpy 函數返回指向目標字符數組 dest 的指針
工作原理:
strcpy 函數會將 src 指向的字符串(包括終止的空字符 '\0')復制到 dest 指向的字符數組中
復制過程會一直進行,直到遇到源字符串的終止空字符 '\0',并將該空字符也復制到目標數組中
#include <stdio.h>
#include <string.h>int main() {// 定義源字符串char src[] = "Hello, World!";// 定義目標字符數組,確保有足夠的空間char dest[20];// 使用 strcpy 函數復制字符串strcpy(dest, src);// 輸出復制后的字符串printf("Copied string: %s\n", dest);return 0;
}
注意事項:
- 緩沖區溢出風險:使用 strcpy 時需要確保目標數組有足夠的空間來容納源字符串及其終止空字符,否則可能會導致緩沖區溢出,引發未定義行為,為了避免這種風險,可以使用更安全的 strncpy 函數
- 目標數組的初始化:在使用 strcpy 之前,不需要對目標數組進行初始化,因為 strcpy 會覆蓋目標數組中的原有內容