黑马程序员C语言基础(第六天)指针
https://www.bilibili.com/video/BV1jW411K7v2/?p=70&spm_id_from=pageDriver
文章目錄
- 指針
- 概述
- 內存
- 物理存儲器和存儲地址空間
- 內存地址
- 指針和指針變量
- 指針基礎知識
- 指針變量的定義和使用
- 通過指針間接修改變量的值
- 指針大小
- 野指針和空指針(wild pointer & null pointer)
- 空指針的作用(用于防止對野指針指向的內存操作導致報錯)
- 萬能指針void *(理解不同類型的指針能夠操作不同的內存大小)
- 指針類型和指針可操作的內存地址(為什么操作指針時必須指定指針類型)
- const修飾的指針變量(指向常量的指針【const int*或int const*】 和 指針常量【int* const】)
- 指針和數組
- 數組名
- 指針法操作數組元素
- 指針加減運算
- 1)加法運算
- 2)減法運算(注意:兩個指針相減結果為步長單位,不是其十六進制數值相減)
- 指針步長:一個地址操作一個字節,地址加一就代表操作的字節加一,步長由指針指向的地址數據類型大小決定,比如int類型指針+1,地址就要加四個字節(地址+4),char類型指針+1,地址就要加1個字節(地址+1)
- 指針數組
- 多級指針 (就是指針的指針【二級指針】,指針的指針的指針【三級指針】,等等)(注意區分指針的地址和值)(無論星多少,星多一個可以作為星少一個的指針)
- 指針和函數(指針的作用就是作為函數的參數來用)
- 函數形參改變實參的值
- 數組名做函數參數
- 指針做為函數的返回值(注意出現野指針情況)
- 注意,以下這種情況會出現野指針,除了linux下,其他編譯器都不會報錯
- 指針和字符串
- 字符指針
- 字符指針做函數參數
- const修飾的指針變量(注意:const char* p = "hello";中,"hello為字符串常量,不能更改",其生命周期與全局變量一樣頑強-->程序結束才釋放)("hello"; 字符串實為首字符的指針)
- 注意:64位系統下,指針占8個字節,int占4個字節;32位系統下,指針和int都是4個字節
- 形參中的數組是指針變量,非形參中的數組就是數組;字符串常量就是此字符串的首元素地址,能直接賦給字符指針(如char* p = "asdfgg";)
- 指針數組作為main函數的形參 int argc, char* argv[](重點)
- 項目開發常用字符串應用模型
- 1) strstr中的while和do-while模型(利用strstr標準庫函數找出一個字符串中子字符串substr出現的個數)
- a)while模型
- b)do-while模型
- 2) 兩頭堵模型(求非空字符串元素的個數)
- 3) 字符串反轉模型(逆置)
- 指針小結
- 作業1:將帶符號的數字字符串轉換為數字
指針
概述
內存
內存含義:
- 存儲器:計算機的組成中,用來存儲程序和數據,輔助CPU進行運算處理的重要部分。
- 內存:內部存貯器,暫存程序/數據——掉電丟失 SRAM、DRAM、DDR、DDR2、DDR3。
- 外存:外部存儲器,長時間保存程序/數據—掉電不丟ROM、ERRROM、FLASH(NAND、NOR)、硬盤、光盤。
內存是溝通CPU與硬盤的橋梁:
- 暫存放CPU中的運算數據
- 暫存與硬盤等外部存儲器交換的數據
物理存儲器和存儲地址空間
有關內存的兩個概念:物理存儲器和存儲地址空間。
物理存儲器:實際存在的具體存儲器芯片。
- 主板上裝插的內存條
- 顯示卡上的顯示RAM芯片
- 各種適配卡上的RAM芯片和ROM芯片
存儲地址空間:對存儲器編碼的范圍。我們在軟件上常說的內存是指這一層含義。
- 編碼:對每個物理存儲單元(一個字節)分配一個號碼
- 尋址:可以根據分配的號碼找到相應的存儲單元,完成數據的讀寫
內存地址
- 將內存抽象成一個很大的一維字符數組。
- 編碼就是對內存的每一個字節分配一個32位或64位的編號(與32位或者64位處理器相關)。
- 這個內存編號我們稱之為內存地址。
內存中的每一個數據都會分配相應的地址: - char:占一個字節分配一個地址
- int: 占四個字節分配四個地址
- float、struct、函數、數組等
指針和指針變量
- 內存區的每一個字節都有一個編號,這就是“地址”。
- 如果在程序中定義了一個變量,在對程序進行編譯或運行時,系統就會給這個變量分配內存單元,并確定它的內存地址(編號)
- 指針的實質就是內存“地址”。指針就是地址,地址就是指針。
- 指針是內存單元的編號,指針變量是存放地址的變量。
- 通常我們敘述時會把指針變量簡稱為指針,實際他們含義并不一樣。(指針是地址,指針變量是存放指針(地址)的變量)
指針基礎知識
指針變量的定義和使用
- 指針也是一種數據類型,指針變量也是一種變量
- 指針變量指向誰,就把誰的地址賦值給指針變量
- “*”操作符操作的是指針變量指向的內存空間
注意:對int類型變量取地址要用int*指針,對字符類型變量取地址要用字符類型指針char*,以此類推
注意:&可以取得一個變量在內存中的地址。但是,不能取寄存器變量,因為寄存器變量不在內存里,而在CPU里面,所以是沒有地址的。
通過指針間接修改變量的值
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {int a = 0;int b = 11;int* p = &a;*p = 100;printf("a = %d, *p = %d\n", a, *p);//a = 100, *p = 100p = &b;*p = 22;printf("b = %d, *p = %d\n", b, *p);//b = 22, *p = 22return 0; }指針大小
- 使用sizeof()測量指針的大小,得到的總是:4或8
- sizeof()測的是指針變量指向存儲地址的大小
- 在32位平臺,所有的指針(地址)都是32位(4字節)
- 在64位平臺,所有的指針(地址)都是64位(8字節)
野指針和空指針(wild pointer & null pointer)
指針變量也是變量,是變量就可以任意賦值,不要越界即可(32位為4字節,64位為8字節),但是,任意數值賦值給指針變量沒有意義,因為這樣的指針就成了野指針,此指針指向的區域是未知(操作系統不允許操作此指針指向的內存區域)。所以,野指針不會直接引發錯誤,操作野指針指向的內存區域才會出問題。
只有在程序中定義后的變量的內存空間才能被指針調用,不是隨隨便便的內存空間都能被指針調用的。所以給指針隨便寫個內存地址,如果這個地址所屬的變量沒有定義,則不合法。
int a = 100; int* p; p = a; //把a的值賦值給指針變量p,p為野指針, ok,不會有問題,但沒有意義p = 0x12345678; //給指針變量p賦值,p為野指針, ok,不會有問題,但沒有意義*p = 1000; //操作野指針指向未知區域,內存出問題,err但是,野指針和有效指針變量保存的都是數值,為了標志此指針變量沒有指向任何變量(空閑可用),C語言中,可以把NULL賦值給此指針,這樣就標志此指針為空指針,沒有任何指針。
int *p = NULL;NULL是一個值為0的宏常量:
#define NULL ((void *)0)空指針的作用(用于防止對野指針指向的內存操作導致報錯)
int* p = NULL; if(p != NULL){*p = 100; }萬能指針void *(理解不同類型的指針能夠操作不同的內存大小)
void *指針可以指向任意變量的內存空間:
void *p = NULL;int a = 10; p = (void *)&a; //指向變量時,最好轉換為void *//使用指針變量指向的內存時,轉換為int * *( (int *)p ) = 11; printf("a = %d\n", a);指針類型和指針可操作的內存地址(為什么操作指針時必須指定指針類型)
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {void* p=NULL;int a = 10;p = &a;*(int*)p = 222;//系統不知道操作多長地址,所以必須指定指針類型printf("%d\n", *(int*)p);//打印的時候也需要return 0;}結果:
222const修飾的指針變量(指向常量的指針【const int或int const】 和 指針常量【int* const】)
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {int a = 100;int b = 200;//指向常量的指針(表示指針類型是const int*或int const*)//修飾*,指針指向內存區域不能修改,指針指向可以變const int* p1 = &a; //等價于int const* p1 = &a;//int const* p1 = &a;//*p1 = 111; //errp1 = &b; //ok//指針常量//修飾p1,指針指向不能變,指針指向的內存可以修改int* const p2 = &a;//p2 = &b; //err*p2 = 222; //okreturn 0; }指針和數組
數組名
數組名字是數組的首元素地址,但它是一個常量:
#include <stdio.h> #include <String.h>int main() {int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };printf("a = %p\n", a);printf("&a[0] = %p\n", &a[0]);return 0; } //a = 10; //err, 數組名只是常量,不能修改結果:
a = 00EFFA84 &a[0] = 00EFFA84指針法操作數組元素
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {int a[] = {1,2,3,4,5,6,7,8,9};int* p = NULL;printf("%p %p %p\n", a, &a, &a[0]);//003CF8C8 003CF8C8 003CF8C8//p = &a;//err:不能將"int(*)[9]"類型的值分配到"int*"類型的實體//p =a;//rightp = &a[0];for (int i = 0; i < sizeof(a)/sizeof(a[0]);i++) {//三種寫法都可以,我還是覺得第一種靠譜,后面兩種看起來不太好理解//printf("%d, ", *(p+i));//1, 2, 3, 4, 5, 6, 7, 8, 9,//printf("%d, ", p[i]);//1, 2, 3, 4, 5, 6, 7, 8, 9,printf("%d, ", *(a+i));//1, 2, 3, 4, 5, 6, 7, 8, 9,}return 0; }指針加減運算
1)加法運算
- 指針計算不是簡單的整數相加
- 如果是一個int *,+1的結果是增加一個int的大小
- 如果是一個char *,+1的結果是增加一個char大小
通過改變指針指向操作數組元素:
#include <stdio.h>int main() {int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int i = 0;int n = sizeof(a) / sizeof(a[0]);int *p = a;for (i = 0; i < n; i++){printf("%d, ", *p);p++;}printf("\n");return 0; }結果:
1, 2, 3, 4, 5, 6, 7, 8, 9,2)減法運算(注意:兩個指針相減結果為步長單位,不是其十六進制數值相減)
示例1:
#include <stdio.h>int main() {int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int i = 0;int n = sizeof(a) / sizeof(a[0]);int* p = &a[n - 1];//int* p = a + n - 1;//兩種方法都可以,我不喜歡下面這種,有點不好理解 for (i = 0; i < n; i++){printf("%d, ", *p);p--;}printf("\n");return 0; }結果:
9, 8, 7, 6, 5, 4, 3, 2, 1,示例2:注意:兩個指針相減結果為步長單位
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int* p2 = &a[2]; //第3個元素地址int* p1 = &a[1]; //第2個元素地址printf("p1 = %p, p2 = %p\n", p1, p2);//p1 = 012FFE58, p2 = 012FFE5Cint n1 = p2 - p1;int n2 = (int)p2 - (int)p1; printf("n1 = %d, n2 = %d\n", n1, n2);//n1 = 1, n2 = 4//注意:兩個指針相減結果為步長單位return 0; }指針步長:一個地址操作一個字節,地址加一就代表操作的字節加一,步長由指針指向的地址數據類型大小決定,比如int類型指針+1,地址就要加四個字節(地址+4),char類型指針+1,地址就要加1個字節(地址+1)
#include <stdio.h> #include <stdlib.h> #include <String.h>int main() {int a = 1111111;int* p = &a;int* p2 = p + 1;*p2 = 2222222;printf("%p %p\n",p,p2);//0055FEA8 0055FEAC(相差4個字節)char b = 'a';char* q = &b;printf("%p %p\n", q, q+1);//00D5FD97 00D5FD98(相差一個字節)return 0;}指針數組
指針數組,它是數組,數組的每個元素都是指針類型。
#include <stdio.h>int main() {//指針數組int *p[3];int a = 1;int b = 2;int c = 3;int i = 0;p[0] = &a;p[1] = &b;p[2] = &c;for (i = 0; i < sizeof(p) / sizeof(p[0]); i++ ){printf("%d, ", *(p[i]));}printf("\n");return 0; }結果:
1, 2, 3,多級指針 (就是指針的指針【二級指針】,指針的指針的指針【三級指針】,等等)(注意區分指針的地址和值)(無論星多少,星多一個可以作為星少一個的指針)
- C語言允許有多級指針存在,在實際的程序中一級指針最常用,其次是二級指針。
- 二級指針就是指向一個一級指針變量地址的指針。
- 三級指針基本用不著,但考試會考。
指針和函數(指針的作用就是作為函數的參數來用)
函數形參改變實參的值
#include <stdio.h>void swap1(int x, int y) {int tmp;tmp = x;x = y;y = tmp;printf("x = %d, y = %d\n", x, y); }void swap2(int* x, int* y) {int tmp;tmp = *x;*x = *y;*y = tmp; }int main() {int a = 3;int b = 5;swap1(a, b); //值傳遞printf("a = %d, b = %d\n", a, b);a = 3;b = 5;swap2(&a, &b); //地址傳遞printf("a2 = %d, b2 = %d\n", a, b);return 0; }結果:
x = 5, y = 3 a = 3, b = 5 a2 = 5, b2 = 3數組名做函數參數
數組名做函數參數,函數的形參會退化為指針:
#include <stdio.h>//void printArrary(int a[10], int n) //void printArrary(int a[], int n) void printArrary(int *a, int n) {int i = 0;for (i = 0; i < n; i++){printf("%d, ", a[i]);}printf("\n"); }int main() {int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int n = sizeof(a) / sizeof(a[0]);//數組名做函數參數printArrary(a, n); return 0; }結果:
1, 2, 3, 4, 5, 6, 7, 8, 9,指針做為函數的返回值(注意出現野指針情況)
#include <stdio.h>int a = 10;int* getA() {return &a; }int main() {*( getA() ) = 111;printf("a = %d\n", a);return 0; } a = 111注意,以下這種情況會出現野指針,除了linux下,其他編譯器都不會報錯
#include <stdio.h> #include <stdlib.h> #include <string.h>int* fun() {int a;return &a; }int main(void) {int* p = NULL;p = fun();*p = 100;printf("%d\n",*p);return 0; }結果:
100因為fun函數返回后,局部變量a的內存會被釋放掉,p指向一個未被聲明的內存(野指針),雖說如此,整個運行都正常,不會報錯,結果也正確
而在linux下,編譯時會警告,返回了局部變量的地址:
dontla@dontla-virtual-machine:~/桌面/test$ gcc test.c -o test test.c: In function ‘fun’: test.c:7:11: warning: function returns address of local variable [-Wreturn-local-addr]7 | return &a;| ^~執行編譯后的可執行文件也會出錯:
dontla@dontla-virtual-machine:~/桌面/test$ ./test 段錯誤 (核心已轉儲)指針和字符串
字符指針
#include <stdio.h>int main() {char str[] = "hello world";char* p = str;*p = 'm';p++;*p = 'i';printf("%s\n", str);return 0; }字符指針做函數參數
#include <stdio.h>void mystrcat(char* dest, const char* src)//src指向的字符串的值不能變 {int len1 = 0;int len2 = 0;while (dest[len1]){len1++;}while (src[len2]){len2++;}int i;for (i = 0; i < len2; i++){dest[len1 + i] = src[i];} }int main() {char dst[100] = "hello mike";char src[] = "123456";mystrcat(dst, src);printf("dst = %s\n", dst);return 0; }結果:
dst = hello mike123456const修飾的指針變量(注意:const char* p = “hello”;中,“hello為字符串常量,不能更改”,其生命周期與全局變量一樣頑強–>程序結束才釋放)(“hello”; 字符串實為首字符的指針)
#include <stdio.h> #include <stdlib.h> #include <string.h>int main(void) {//const修飾一個變量為只讀const int a = 10;//a = 100; //err//指針變量, 指針指向的內存, 2個不同概念char buf[] = "aklgjdlsgjlkds";//從左往右看,跳過類型,看修飾哪個字符//如果是*, 說明指針指向的內存不能改變//如果是指針變量,說明指針的指向不能改變,指針的值不能修改const char *p = buf;// 等價于上面 char const *p1 = buf;//p[1] = '2'; //errp = "agdlsjaglkdsajgl"; //okchar * const p2 = buf;p2[1] = '3';//p2 = "salkjgldsjaglk"; //err//p3為只讀,指向不能變,指向的內存也不能變const char * const p3 = buf;return 0; }注意:64位系統下,指針占8個字節,int占4個字節;32位系統下,指針和int都是4個字節
形參中的數組是指針變量,非形參中的數組就是數組;字符串常量就是此字符串的首元素地址,能直接賦給字符指針(如char* p = “asdfgg”;)
指針數組作為main函數的形參 int argc, char* argv[](重點)
int main(int argc, char* argv[]);- main函數是操作系統調用的,第一個參數標明argv數組的成員數量,argv數組的每個成員都是char *類型
- argv是命令行參數的字符串數組
- argc代表命令行參數的數量,程序名字本身算一個參數
生成可執行文件后,在控制臺運行,這是它的運行結果:
D:\Dontla_small_project\20210525_address_list\vs_test\vs_test\Debug>vs_test.exe aaaaaaa bbbbbbbbbb ccccccc argc = 1 vs_test.exeD:\Dontla_small_project\20210525_address_list\vs_test\vs_test\Debug>vs_test.exe -a aaaaaaa bbbbbbbbbb ccccccc argc = 2 vs_test.exe -aD:\Dontla_small_project\20210525_address_list\vs_test\vs_test\Debug>vs_test.exe a d d gf aaaaaaa bbbbbbbbbb ccccccc argc = 5 vs_test.exe a d d gfD:\Dontla_small_project\20210525_address_list\vs_test\vs_test\Debug>項目開發常用字符串應用模型
1) strstr中的while和do-while模型(利用strstr標準庫函數找出一個字符串中子字符串substr出現的個數)
a)while模型
#include <stdio.h> #include <stdlib.h> #include <string.h>int main(void) {char *p = "11abcd111122abcd333abcd3322abcd3333322qqq";int n = 0;while ((p = strstr(p, "abcd")) != NULL){//能進來,肯定有匹配的子串//重新設置起點位置p = p + strlen("abcd");n++;if (*p == 0) //如果到結束符{break;}}printf("n = %d\n", n);return 0; }結果:
n = 4b)do-while模型
#include <stdio.h> #include <stdlib.h> #include <string.h>int main(void) {const char* p = "11abcd111122abcd333abcd3322abcd3333322qqq";int n = 0;do{p = strstr(p, "abcd");if (p != NULL){n++; //累計個數//重新設置查找的起點p = p + strlen("abcd");}else //如果沒有匹配的字符串,跳出循環{break;}} while (*p != 0); //如果沒有到結尾printf("n = %d\n", n);return 0; }結果:
n = 42) 兩頭堵模型(求非空字符串元素的個數)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h>int fun(const char* p, int* n) {if (p == NULL || n == NULL){return -1;}int begin = 0;int end = strlen(p) - 1;//從左邊開始//如果當前字符為空,而且沒有結束//while (p[begin] == ' ' && p[begin] != 0)while (*(p+begin) == ' ' && *(p + begin) != 0){begin++; //位置從右移動一位}//從右往左移動//while (p[end] == ' ' && end > 0)while (*(p + end) == ' ' && end > 0){end--; //往左移動}if (end == 0){return -2;}//非空元素個數*n = end - begin + 1;return 0; }int main(void) {const char* p = " abcddsgadsgefg ";int ret = 0;int n = 0;ret = fun(p, &n);if (ret != 0){return ret;}printf("非空字符串元素個數:%d\n", n);return 0; }結果:
非空字符串元素個數:143) 字符串反轉模型(逆置)
#include <stdio.h> #include <stdlib.h> #include <string.h>int inverse(char *p) {if (p == NULL){return -1;}char *str = p;int begin = 0;int end = strlen(str) - 1;char tmp;while (begin < end){//交換元素tmp = str[begin];str[begin] = str[end];str[end] = tmp;begin++; //往右移動位置end--; //往左移動位置}return 0; }int main(void) {//char *str = "abcdefg"; //文件常量區,內容不允許修改char str[] = "abcdef";int ret = inverse(str);if (ret != 0){return ret;}printf("str ========== %s\n", str);return 0; }結果:
str ========== fedcba指針小結
定義 說明 int i 定義整型變量 int *p 定義一個指向int的指針變量 int a[10] 定義一個有10個元素的數組,每個元素類型為int int *p[10] 定義一個有10個元素的數組,每個元素類型為int* int func() 定義一個函數,返回值為int型 int *func() 定義一個函數,返回值為int *型 int **p 定義一個指向int的指針的指針,二級指針作業1:將帶符號的數字字符串轉換為數字
如“-123”,從左到右一個一個判斷,可以用迭代10 × x + y的辦法
總結
以上是生活随笔為你收集整理的黑马程序员C语言基础(第六天)指针的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言函数怎么像python那样返回多个
- 下一篇: C语言编译链接生成可执行文件四大步骤:预