日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

五大板块(2)—— 指针

發布時間:2023/12/10 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 五大板块(2)—— 指针 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

參考:五大板塊(2)—— 指針
作者:丶PURSUING
發布時間: 2021-03-18 16:01:22
網址:https://blog.csdn.net/weixin_44742824/article/details/114981482

本文為學習筆記,整合課程內容及文章如下:

為什么要使用指針 板塊:
參考:原文鏈接
作者:Cloudkip

目錄

  • 一、地址的引入
    • 概念
    • 地址長啥樣?
  • 二、指針變量的引入
  • 三、指針分類型與指針偏移量
    • 整型指針,字符指針
    • 函數指針(重點)
      • 無參無返的函數指針
      • 有參有返的函數指針
      • 結構體中的函數指針
      • 規律總結
    • 數組指針(少用)
    • 指針數組(少用)
    • 結構體指針(重點)
      • 定義賦值調用與指針偏移
      • 實際應用例子
    • 野指針
      • 是什么
      • 野指針是怎樣生成的?
      • 有什么危害
      • 如何避免
      • malloc與內存泄漏
    • 指針類型小測試
  • 四、指針也可以作為數組傳入的形式參數
  • 五、為什么要使用指針?
    • (1)節省內存
    • (2)在子函數中修改被傳遞過來的對象
    • (3)動態分配內存
    • (4)在指定地址寫入數據(目前少用)
    • (5)函數多個返回值

指針,你說他難吧,其實都是在學校學的時候給勸退了,其實很簡單,而且學會了之后老是想用,也很好用,以后看到指針就不會有心理障礙啦。

一、地址的引入

概念

地址是一個十六進制表示的整數,用來映射一塊空間,是系統用來查找數據位置的依據。地址標識了存儲單元空間,而字節就是最小的存儲單位。

按字節的編址方式:每一個字節都有一個唯一的地址。例如:一個int型的變量是4個字節,就會對應4個地址,我們只需要取到這個變量的首地址就能得到完整的int型數據。

地址長啥樣?

用一個例子感受變量存放的地址:

#include <stdio.h>int main() {int a=10;int b=11;int* p=&a;int* p2=&b;printf("a的地址是:%p\n",p);printf("b的地址是:%p\n",p2);return 0; }

結果:可以發現兩者地址相差4個字節,說明int型變量用4個字節的空間存放

a的地址是:0x7ea5d1dc b的地址是:0x7ea5d1d8
  • 1
  • 2

大概可以表示為:

二、指針變量的引入

什么是指針?從根本上看,指針是一個值為內存地址的變量。

正如char型變量存放字符,int型變量存放整型數一樣,指針變量存放的是地址,沒有什么難理解的。

給指針賦值就是讓其指向一個地址。

三、指針分類型與指針偏移量

用sizeof發現linux下所有指針類型的大小均為8字節。

平臺:樹莓派

首先明白:指針所占用的空間與指針指向的內容和內容的大小無關。
其次明白:在不同的操作系統及編譯環境下,指針類型所占用的字節數是不同的
例如:
編譯生成16位的代碼時,指針占2個字節
編譯生成32位的代碼時,指針占4個字節
編譯生成64位的代碼時,指針占8個字節

整型指針,字符指針

#include <stdio.h>int main() {int a = 5;char b = 'A';int *pa = &a;//存放整型數的指針叫整型指針char *pb = &b;//而這叫字符型指針//printf("int型指針 pa 的地址是%p,指針偏移(++pa)的地址是:%p\n",pa,++pa);//printf("char型指針 pb 的地址是%p,指針偏移(++pb)的地址是:%p\n",pb,++pb);printf("int 型指針pa的地址是%p\n",pa);printf("int 型指針偏移(++pa)后的地址是:%p\n\n",++pa);printf("char 型指針pb的地址是%p\n",pb);printf("char 型指針偏移(++pb)后的地址是:%p\n",++pb);return 0;}

結果:可以看到指針類型不同,其每次偏移的地址量也不同。

pi@raspberrypi:~/Desktop $ ./a.out int 型指針pa的地址是0x7ead81ec int 型指針偏移(++pa)后的地址是:0x7ead81f0char 型指針pb的地址是0x7ead81eb char 型指針偏移(++pb)后的地址是:0x7ead81ec

不知道你會不會思考,為什么我不使用代碼中被注釋的兩條語句,簡短明了,而要使用4條printf。
你盡管試試,打印出來的pa和++pa是一樣的,好似是地址沒有偏移,這其實關系到了printf的出棧入棧問題,放在六、其他小知識點:printf 里的 a++,++a,真的有鬼!! 中詳細展開。

函數指針(重點)

顧名思義,指向函數地址的指針。

無參無返的函數指針

這是函數指針最簡單的一種形式

#include <stdio.h>void print()//要被指向的函數 {printf("hello world\n"); }int main() {void (*pprint)() = NULL;//定義函數指針pprint = print; //函數指針賦值:指向函數的首地址(就是函數名)//如同數組的首地址,是數組名pprint(); //調用方法1(*pprint)(); //調用方法2printf("函數指針pprint的地址是%p\n",pprint);printf("函數指針偏移(++pprint)后的地址是:%p\n",++pprint);return 0; }

結果:

hello world hello world 函數指針pprint的地址是0x1046c 函數指針偏移(++pprint)后的地址是:0x1046d
  • 1
  • 2
  • 3
  • 4

有參有返的函數指針

稍微上升點難度

#include <stdio.h>int sum(int a,int b)//要被指向的函數 {int c = 0;c = a+b;return c; }int main() {int total1;int total2;int (*psum)(int a,int b) = NULL;//定義函數指針psum = sum; //函數指針賦值,指向函數的首地址(就是函數名)//如同數組的首地址,是數組名total1 = psum(5,6); //調用方法1total2 = (*psum)(6,9);//調用方法2printf("total1:%d\ntotal2:%d\n",total1,total2);printf("%p\n",psum);printf("%p\n",++psum);return 0; }

結果:

total1:11 total2:15 0x10440 0x10441
  • 1
  • 2
  • 3
  • 4

結構體中的函數指針

比較常見的還是和結構體的結合,這個容易看花眼。

#include <stdio.h> #include <string.h>struct student {int english;int japanese;int math;int chinese;char name[128];int (*pLanguage)(int english,int japanese);//順便復習函數指針怎么使用};int Language(int eng,int jap)//函數指針所指向的函數 {int total;total = eng + jap;return total; }int main() {int lanSum;struct student stu1 = {.japanese = 90,.english = 100,.math = 90,.name = "華天朱",.pLanguage = Language,};lanSum = stu1.pLanguage(stu1.english,stu1.japanese);printf("stu1的名字是%s,他的語言綜合分數是%d\n",stu1.name,lanSum);printf("%p\n",stu1.pLanguage);printf("%p\n",++stu1.pLanguage);return 0; }

結果:

stu1的名字是華天朱,他的語言綜合分數是190 0x10470 0x10471
  • 1
  • 2
  • 3

規律總結

函數指針無非就三步走:

定義
類型 (*指針名)();

void (*pprint)() = NULL;
  • 1

兩個括號很好記

賦值
指針名 = 函數名

pprint = print;
  • 1

調用
如有參數則調用時傳

pprint(); //調用方法1 (*pprint)(); //調用方法2
  • 1
  • 2

數組指針(少用)

顧名思義,就是指向數組地址的指針。

目前還沒碰到數組指針的使用,涉及即更新

#include <stdio.h>int main() {int a[3] = {1,2,3};int (*p)[3] = NULL;p = a;printf("%p\n",p);printf("%p\n",++p);return 0; }

結果:偏移了整個數組的大小12字節

0x7ede61e8 0x7ede61f4
  • 1
  • 2

指針數組(少用)

存放一系列指針的數組,本質是數組。

#include <stdio.h>int main() {int a=1;int b=2;int c=3;int *parray[3] = {&a,&b,&c};int i;printf("指針數組的第一個元素是:%p,地址的內容是:%d\n",parray[0],*parray[0]);return 0; }

結果:

指針數組的第一個元素是:0x7ed501f4,地址的內容是:1
  • 1

結構體指針(重點)

定義賦值調用與指針偏移

#include <stdio.h> #include <stdlib.h> #include <string.h>struct STU {int score;char name[128]; };int main() {struct STU stu1 = {.score = 99,.name = "果粒臣",};//malloc為結構體指針開辟一塊空間struct STU *pstu = (struct STU *)malloc(sizeof(struct STU));//結構體指針的賦值1:直接賦值,在此之前要開辟空間pstu->score = 100;strcpy(pstu->name,"華天朱");printf("%s:%d\n",pstu->name,pstu->score);free(pstu);//釋放指針,重新指向一段地址//結構體指針的賦值2:指向結構體變量的地址pstu = &stu1;printf("%s:%d\n",pstu->name,pstu->score);//指針偏移printf("%p\n",pstu);printf("%p\n",++pstu);return 0; }

結果:結構體偏移了4+128個字節

華天朱:100 果粒臣:99 0x7e905170 0x7e9051f4
  • 1
  • 2
  • 3
  • 4

實際應用例子

用一個結構體指針做一個最簡單的學生成績管理。

#include <stdio.h> #include <stdlib.h> #include <string.h>struct stud {char* name;int score; };int main() {int num;int i;printf("需要錄入幾個學生的成績?\n");scanf("%d",&num);//這里開辟了num個結構體所需要的空間,動態分配內存struct stud *pstu = (struct stud *)malloc(sizeof(struct stud)*num);for(i=0;i<num;i++){pstu->name = (char* )malloc(sizeof(char)*16);memset(pstu->name,0,sizeof(char)*16);printf("請輸入第%d個學生的名字\n",i+1);scanf("%s",pstu->name);printf("請輸入第%d個學生的成績\n",i+1);scanf("%d",&pstu->score);pstu++;}pstu -= num;//指針回頭for(i=0;i<num;i++){printf("%s:%d\n",pstu->name,pstu->score);pstu++;}return 0; }

結果:

美羊羊:45 廢羊羊:100 喜羊羊:60 灰太狼:76
  • 1
  • 2
  • 3
  • 4

野指針

是什么

野指針指向的地址是隨機(又稱為:"垃圾"內存)的,無法得知他的地址,操作系統自動對其進行初始化。

野指針是怎樣生成的?

(1)創建指針時沒有對指針進行初始化
(2)使用free釋放指針后沒有將其指向NULL

有什么危害

當一個指針成為野指針,指向是隨機的,當你使用它時,危害程度也是隨機而不可預測的。一般會造成內存泄漏也很容易遭到黑客攻擊,只要將病毒程序放入這塊內存中,當使用到這個指針時就開始執行。

如何避免

  • 定義指針時進行初始化

如果沒有確定指向,就讓它指向NULL

NULL在宏定義中是#define NULL (void **) 0,代表的是零地址,零地址不能進行任何讀寫操作

  • 要給指針指向的空間賦值時,先給指針分配空間,并且初始化空間

簡單示例:

//char型指針 char *p = (char *)malloc(sizeof(char)); memset(p,0,sizeof(char));//int型指針 //指針(指向地址)游標卡尺 開辟空間大小 int *p = (int *)malloc(sizeof(int)); memset(p,0,sizeof(int));//結構體指針 struct stu *p = (struct stu *)malloc(sizeof(struct stu)); memset(p,0,sizeof(struct stu));

malloc動態內存分配,用于申請一塊連續的指定大小的內存塊區域以void*類型返回分配的內存區域地址。void *malloc(unsigned int size),因為返回值時void*,所以要進行強制轉換。

memset將某一塊內存中的內容全部設置為指定的值, 這個函數通常為新申請的內存做初始化工作,是對較大的結構體或數組進行清零操作的一種最快方法。void *memset(void *s, int ch, size_t n);

  • 釋放指針同時記得指向NULL
free(p); p = NULL;
  • 1
  • 2

malloc與內存泄漏

情景:
程序剛跑起來的時候沒問題,時間久了程序崩潰,大多為內存泄漏。

最常見的情況是在無限的循環中一直申請空間。用malloc申請的空間,程序不會主動釋放,只有當程序結束后,系統才回收空間。

避免在循環中一直申請空間,即使合理釋放(free,指向NULL)

指針類型小測試

搞幾個容易混淆的

int *p[4]; int (*p)[4]; int *p(); int(*p)();
  • 1
  • 2
  • 3
  • 4
指針數組,數組中存放的是一系列的地址 數組指針,指向一個數組 只是一個普通的函數,其返回值是int* 的指針 函數指針,指向一個函數
  • 1
  • 2
  • 3
  • 4

四、指針也可以作為數組傳入的形式參數

數組作為子函數的形式參數小節中,子函數的形式參數我們用 int array[ ]來定義。

學了指針之后,我們也可以用 int *array來定義.這是因為前者的本質上傳入的是數組的首地址,而指針也一樣,需要傳入數組的首地址。

如下:

#include <stdio.h>int arraySum(int *array,int num)//數組形參,僅僅傳遞數組的首地址,代表不了個數。 {int sum;int i;for(i=0;i<num;i++){sum+=array[i];}return sum;}int main() {int sum;int array[5]={0,1,2,3,4};// sum=arraySum(array,sizeof(array)/sizeof(array[0])); //傳遞數組名sum=arraySum(&array[0],sizeof(array)/sizeof(array[0]));//或者傳遞首元素的地址(&)//sizeof里面只能傳入數組名 printf("sum=%d\n",sum);return 0; }

五、為什么要使用指針?

(1)節省內存

指針的使用使得不同區域的代碼可以輕易的共享內存數據,當然也可以通過數據的復制達到相同的效果,但是這樣往往效率不太好。

指針節省內存主要體現在參數傳遞上,比如傳遞一個結構體指針變量和傳遞一個結構體變量,結構體占用內存越大,傳遞指針變量越節省內存,也就是可以減少不必要的數據復制。

(2)在子函數中修改被傳遞過來的對象

C語言中的 一切函數調用中,值傳遞都是“按值傳遞” 的。如果要在函數中修改被傳遞過來的對象,就必須通過這個對象的指針來完成。

太抽象了不懂?舉個栗子:

#include <stdio.h>void add(int a) {a = a+1;printf("add:a的值為%d\n",a); }int main() {int a = 10;add(a);printf("main:a的值為%d\n",a);return 0; }

結果:可以發現main函數中a的值并沒有真正發生改變。

add:a的值為11 main:a的值為10
  • 1
  • 2

為什么沒有改變呢?
函數add調用時,才申請了一個內存空間,才有了這個變量a,同時把實際參數(main中的a)的值拷貝一份給形式參數(add中的a),函數執行結束后釋放空間,這個子函數中的變量自然也被釋放了。

其中這個a就是傳遞入子函數add的對象,如果想要在這個子函數中修改a的值,就要使用指針

#include <stdio.h>void add(int *a) {*a = *a+1;printf("add:a的值為%d\n",*a); }int main() {int a = 10;add(&a);printf("main:a的值為%d\n",a);return 0; }

結果:

add:a的值為11 main:a的值為11
  • 1
  • 2

傳入了main中a的地址,再在子函數中修改這個地址中的內容,當然能夠修改成功。

(3)動態分配內存

常??梢钥吹?#xff0c;程序使用的內存在一開始就進行分配(靜態內存分配)。這對于節省計算機內存是有幫助的,因為計算機可以提前為需要的變量分配內存。

但是大多應用場合中,可能一開始程序運行時不清楚到底需要多少內存,這時候可以使用指針,讓程序在運行時獲得新的內存空間(動態內存分配),并讓指針指向這一內存更為方便。

結構體指針實際應用舉例中有涉及:

int num;printf("需要錄入幾個學生的成績?\n"); scanf("%d",&num);//這里開辟了num個結構體所需要的空間,動態分配內存 struct stud *pstu = (struct stud *)malloc(sizeof(struct stud)*num);

(4)在指定地址寫入數據(目前少用)

了解就行 JAVA等其他編程語言實現不了這種操作

#include <stdio.h>int main() {volatile int *p = (volatile int *)0x7ead81eb;//強制轉化成整型地址*p = 10; //ARM架構 裸機編程 ARM驅動會使用printf("在地址%p中存放的數據是%d\n",p,*p);return 0; }

結果:

在地址0x7ead81eb中存放的數據是10
  • 1

注意哈,這個操作運行很有可能

Segmentation fault
  • 1

這也正常,畢竟可能這地址放有東西或者不允許這樣操作。

volatile和編譯器的優化有關:

編譯器的優化

在本次線程內,當讀取一個變量時,為了提高讀取速度,編譯器進行優化時有時會先把變量讀取到一個寄存器中(寄存器比內存要快!);以后,再讀取變量值時,就直接從寄存器中讀取;當變量值在本線程里改變時,會同時把變量的新值copy到該寄存器中,以保持一致。

當變量因別的線程值發生改變,上面寄存器的值不會相應改變,從而造成應用程序讀取的值和實際的變量值不一致。
當寄存器因別的線程改變了值,原變量的值也不會改變,也會造成應用程序讀取的值和實際的變量值不一致。

(5)函數多個返回值

有時候我們總是希望一個功能子函數的返回值可以有很多個,但奈何用return只能返回一個。

碰到實際用上的例子再補充,不硬找例子。

總結

以上是生活随笔為你收集整理的五大板块(2)—— 指针的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。