(七)C語言之指針

c語言相比其他高級語言來說,更接近于對計算機硬件的操作,而指針的應用更是為我們對硬件的操作插上了翅膀,所以指針是嵌入式編程不可少的一部分,在一定意義上說,指針是c語言的精髓。

一、 什么是指針

在計算機中,數據時存放在內存中的,而內存其實就是一組有序字節組成的數組,一般以一個字節為一個內存單元,每個字節都有唯一的地址。cpu通過尋址的方式去查找內存中某個變量的位置,我們知道定義變量就是向CPU申請一個某一類型的空間,這個空間也有自己的地址,同樣地址也需要一種類型去存儲,C語言規定用指針類型的變量去存儲地址類型。記住一點:指針就是地址,指針變量時存放地址類型的變量。

二、指針變量的定義

2.1 聲明并初始化一個指針

可以保存地址值的變量稱為指針變量,指針變量定義如下:

數據類型  *  變量名

這里的數據類型為基本數據類型、構造類型,指針變量的聲明比普通變量的聲明多了一個’ * ‘,運算符’ * '就是間接引用或間接尋址。例如:

int *p;        // 聲明一個 int 類型的指針 p
char *p        // 聲明一個 char 類型的指針 p
int *arr[10]   // 聲明一個指針數組,該數組有10個元素,其中每個元素都是一個指向 int 類型對象的指針
int (*arr)[10] // 聲明一個數組指針,該指針指向一個 int 類型的一維數組
int **p;       // 聲明一個指針 p ,該指針指向一個 int 類型的指針

在上面的聲明中:p就是一個指針變量,里面存著一個地址。
這里要注意**指針在使用前一定要初始化,否則就會指針就會變成野指針。初始化有3種方式:

/* 方法1:使指針指向現有的內存 */
int x = 1;
int *p = &x;  // 指針 p 被初始化,指向變量 x ,其中取地址符 & 用于產生操作數內存地址/* 方法2:動態分配內存給指針 */
int *p;
p = (int *)malloc(sizeof(int) * 10);    // malloc 函數用于動態分配內存
free(p);    // free 函數用于釋放一塊已經分配的內存,常與 malloc 函數一起使用,要使用這兩個函數需要頭文件 stdlib.h/*方法3:定義為NULL */
int *p=NULL;

指針的初始化就是給指針一個合理的指向,讓程序知道往哪指,上述NULL是一個特殊的指針變量,相當于0。地址為0的內存一般都不允許訪問,但是內存地址為0有一個重要的意義,它表明指針指向不指向一個可訪問的內存地址。

2.2 指針的調用

訪問內存空間,一般分為直接訪問和間接訪問。

如果知道內存空間的名字,可通過名字訪問該空間,稱為直接訪問。由于變量即代表有名字的內存單元,故通。過變量名操作變量,也就是通過名字直接訪問該變量對應的內存單元。

如果知道內存空間的地址,也可以通過該地址間接訪問該空間。對內存空間的訪問操作一般指的是存、取操作,即向內存空間中存入數據和從內存空間中讀取數據。

在 C 語言中,可以使用間接訪問符(取內容訪問符)*來訪問指針所指向的空間,例如:

int *p,a=3;//p中保存變量a對應內存單元的地址
p=&a;

在該地址 p 前面加上間接訪問符 *,即代表該地址對應的內存單元。而變量 a 也對應該內存單元,故 *p 就相當于 a。

printf("a=%d\n",a); //通過名字,直接訪問變量a空間(讀取)
printf("a=%d\n",*p); //通過地址,間接訪問變量a空間(讀取)
*p=6;//等價于a=6;間接訪問a對應空間(存)

2.3 野指針

一般我們把沒有合法指向的指針稱為“野”指針。因為“野”指針隨機指向一塊空間,該空間中存儲的可能是其他程序的數據甚至是系統數據,故不能對“野”指針所指向的空間進行存取操作,否則輕者會引起程序崩潰,嚴重的可能導致整個系統崩潰。例如:

int *pi,a; //pi未初始化,無合法指向,為“野”指針
*pi=3; //運行時錯誤!不能對”野”指針指向的空間做存入操作。該語句試圖把 3 存入“野”指針pi所指的隨機空間中,會產生運行時錯誤。
a=*pi; //運行時錯誤!不能對”野”指針指向的空間取操作。該語句試圖從“野”指針pi所指的空間中取出數據,然后賦給變量a同樣會產生運行時錯誤。

正確的應該是:

pi=&a;//讓pi有合法的指向,pi指向a變量對應的空間
*pi=3;//把3間接存入pi所指向的變量a對應的空間

三、指針的運算

c指針的算術運算只限兩種形式:

(1)指針+/-整數

可以對指針變量加減整數,例如對指針變量p進行p++,p–,p+i等操作,所得結果任然是一個指針,只是指針p所指的內存地址前進或后退了i個操作數。
指針加減
在上圖中假設p指向內存10000008,p是一個int型指針可以看出對p進行加減所指向的內存。下面進行一個例子來說明指針變量自增自減運算:

#include<stdio.h>
#include<string.h>
void main(void)
{char str[]="hello";char *p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*p);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*p++);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",(*p)++);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*(p++));//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",++*p);//打印結果為'i'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*++p);//打印結果為'e'}

對于指針++*p和 *p++來說是依據就近原則運算的,而對y=*p++則相當于y=*p;p++;這里如果加上括號則為y=(*p)++則相當于y=*p;(*p)++;

(2)指針-指針

只有當兩個指針都指向同一個數組中的元素時,才允許從一個指針減去另一個指針。兩個指針相減的結果的類型是 ptrdiff_t,它是一種有符號整數類型。減法運算的值是兩個指針在內存中的距離(以數組元素的長度為單位,而不是以字節為單位),因為減法運算的結果將除以數組元素類型的長度。舉個例子:

#include "stdio.h"int main(){int a[10] = {1,2,3,4,5,6,7,8,9,0};int sub;int *p1 = &a[2];int *p2 = &a[8];sub = p2-p1;                                                                            printf("%d\n",sub);    // 輸出結果為 6return 0;
}

(3)指針的比較

指針在一定條件下可以比較,這里的一定條件指兩個指針指向同一個對象才有意義,例如兩個指針變量p,q指向同一數組,則<,>,>=,<=,== 等關系運算符都能正常運行。若q==p為真,則表示p和q為同一元素;若p<q為真,則表示p所指向的數組元素在q所指向的數組元素之前。

四、指針和數組

指針和數組的關系十分密切。在前面的文章(c語言二維指針數組詳解)中我們推算到a[i]=*(a+i),一般來說通過數組完成的工作都可以用指針來完成,但是使用數組更容易理解。

4.1 一維數組與指針

一維數組的數組名表示該數組的首地址,c語言中指針變量加1表示跳過該指針變量所指類型占用的空間大小。如果指針變量指向數組,那么指針加1表示指向數組的下一個元素。

int  *p;//聲明一個int類型的指針變量
int a[5];//聲明一個int型數組
p=a;//數組名表示數組首地址,把數組首地址賦給指針變量,p指向數組的第0個元素a[0]

在上面的程序中,數組名等價于數組的首地址,即&a[0]。
訪問數組的元素有三種方式:
(1)直接訪問: 數組名[下標]的形式

int a[5]={1,2,3,4,5};
int b=0;
b=a[3];//b=4,直接使用數組下標訪問數組元素

(2)間接訪問:*(數組名+i)的形式

int a[5]={1,2,3,4,5};
int b=0;
b=*(a+3);//b=4,直接使用*(數組名+i)訪問

(3)間接訪問:*(指針變量)的形式

int a[5]={1,2,3,4,5};
int b=0;
int *p=a;
b=*(p+3);//b=4,直接使用指針間接訪問數組元素

【例 1】通過指針變量實現對數組元素的輸入和輸出操作。
實例代碼為:

#include <stdio.h>
#define N 10
int main (void)
{int *p,a[N],i;p=a; //p初始指向a[0]printf("Input the array:\n");for(i=0;i<N;i++) //用整型變量i控制循環次數scanf ("%d",p++); //指針P表示地址,不能寫成&Pprintf ("the array is :\n");for(p=a;p<a+N;p++) //用p的移動范圍控制循環次數printf("%d\t", *p);return 0;
}

4.2 二維數組與指針

二維數組實際上還是一維數組,它的存儲結構仍是順序存儲,即二維數組中的元素在內存中的存儲地址是連續的,所以可以用指針變量訪問數組的各個元素。具體的解釋請看(c語言二維指針數組詳解)

*(a[i] + j) <--> *(*(a + i) + j) <-->*&a[i][j]<-->a[i][j]

4.3 指針數組

無論是指針數組還是數組指針都看后面兩個字區分,后兩個字為數組,那么它是一個存放指針元素的數組。指針數組定義:

數據類型 *數組名[數組大小]

在上面的聲明中,由于[]的優先級高于*,所以先形成數組。

4.4 數組指針

同樣看后面兩個字知道它是一個指針,指向數組。聲明一個數組指針方法如下:

//數據類型 (* 數組名)[元素個數]
int (*p)[5];//聲明一個數組指針p,它指向含有5個int類型元素的二維數組

上面p指向二維數組,它指向二維數組的每一行
二維數組 a[M][N] 分解為一維數組元素 a[0]、a[1]、…、a[M-1] 之后,其每一行 a[i] 均是一個含 N 個元素的一維數組。如果使用指向一維數組的指針來指向二維數組的每一行,通過該指針可以較方便地訪問二維數組中的元素。
使用數組指針訪問二維數組中的元素。

#define M 3
#define N 4
int a[M][N],i,j;
int (*p)[N]=a; // 等價于兩條語句 int (*p)[N] ; p=a;

以上語句定義了 M 行 N 列的二維整型數組 a 及指向一維數組(大小為 N)的指針變量 p,并初始化為二維數組名 a,即初始指向二維數組的 0 行。
i 行首地址與 i 行首元素地址的區別如下。

  • i 行首元素的地址,是相對于 i 行首元素 a[i][0] 來說的,把這種具體元素的地址,稱為一級地址或一級指針,其值加 1表 示跳過一個數組元素,即變為 a[i][1] 的地址。
  • i 行首地址是相對于 i 行這一整行來說的,不是具體某個元素的地址,是二級地址,其值加 1 表示跳過 1 行元素對應的空間。
  • 對二級指針(某行的地址)做取內容操作即變成一級指針(某行首元素的地址)。
    兩者的變換關系為:
*(i 行首地址)=i 行首元素地址0 行首地址:p + 0 <--> a + 0
1 行首地址:p + 1 <--> a + 1
...
i 行首地址:p + i <--> a + ii 行 0 列元素地址:*(p + i) +0 <—> *(a + i) +0 <—>&a[i][0]
i 行 1 列元素地址:* (p + i) +1 <--> *(a + i) +1 <—>&a[i][1]
...
i 行 j 列元素地址:* (p + i) + j <--> * (a + i) + j <--> &a[i][j]
i 行 j 列對應元素:* (* (p + i) + j) <--> * (* (a + i) + j) <--> a[i][j]

由此可見,當定義一個指向一維數組的指針 p,并初始化為二維數組名 a 時,即 p=a;, 用該指針訪問元素 a[i][j] 的兩種形式 ((p + i) + j) 與 ((a + i) + j) 非常相似,僅把 a 替換成了 p 而已。
由于數組指針指向的是一整行,故數組指針每加 1 表示跳過一行,而二維字符數組中每一行均代表一個串,因此在二維字符數組中運用數組指針能較便捷地對各個串進行操作。

五、指針和函數

c語言函數參數傳遞有兩種方式:值傳遞和地址傳遞。本節主要討論下地址傳遞,傳遞地址能夠改變主調函數對象中的值。

5.1指針函數

有時函數調用結束后,需要函數返回給調用者某個地址即指針類型,以便于后續操作,這種函數返回類型為指針類型的函數,通常稱為指針函數。
指針函數的定義格式為:

類型*函數名(形參列表)
{... /*函數體*/
}

5.2函數指針

C語言中,函數本身不是變量,但是可以定義指向函數的指針,也稱作函數指針,函數指針指向函數的入口地址。這種類型的指針可以被賦值、存放在數組中、傳遞給函數以及作為函數的返回值等等。 聲明一個函數指針的方法如下:

返回值類型 (* 指針變量名)([形參列表]);int (*pointer)(int *,int *);        // 聲明一個函數指針

上述代碼聲明了一個函數指針 pointer ,該指針指向一個函數,函數具有兩個 int * 類型的參數,且返回值類型為 int。

六、指針與字符串

6.1常量字符串與指針

常量字符串返回來的就是一個存放該字符串的首地址。**注意:**常量字符串不能改變,即使通過指針也不能改變,因為它存放在文字常量區。假設字符串常量 “abcd” 表示一個指針,那么該指針指向字符 ‘a’,表達式 “abcd”+1,是在指針 “abcd” 值的基礎上加 1,故也是一個指針,指向字符串中第二個字符的指針常量。同理,“abcd”+3 表示指向第 4 個字符 ‘d’ 的指針常量。
我們還可以這樣定義:

char *p="hello";

【例1·】如下代碼段通過指針變量依次遍歷輸出所指串中每個字符

#include<stdio.h>
int main (void)
{//初始指向首字符//間接訪問所指字符 //pc依次指向后面的字符char *pc="hello,world!";while (*pc! = '\0'){putchar(*pc);pc++;
}return 0;
}

6.2 變量字符串

我們想要存放可以改變的字符串,可以放在字符數組中。例如:

char str[]="i love china"

這里的str內容可以改變,通過指針或者下標都可以去操作其內容。

七、總結

到這里指針的相關內容已經講解完了,記住以下幾點:

  • 指針就是地址,指針變量存放的是地址類型的變量
  • 定義指針和調用指針的*作用不一樣
  • 在指針變量前每加一個* 表示取一次內容,類似[],在調用時每加一個*表示取一次內容
  • 指針變量一定要初始化,c語言不允許野指針出現
  • 指針指向字符串的首地址
  • 注意辨別指針的自增自減操作
  • 了解指針數組和數組指針、指針函數和函數指針的應用

本文章僅供學習交流用禁止用作商業用途,文中內容來水枂編輯,如需轉載請告知,謝謝合作

微信公眾號:zhjj0729

微博:文藝to青年

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/531450.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531450.shtml
英文地址,請注明出處:http://en.pswp.cn/news/531450.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

各種排序(數據結構復習之內部排序算法總結)

1.三種選擇排序&#xff08;簡單選擇排序&#xff0c;樹形選擇排序&#xff0c;堆排序&#xff09; #include<iostream> #include<cstring> #include<string> #include<queue> #include<map> #include<cstdlib> #include<cstdio> c…

(八)C語言之結構

今天來說一下C語言里的結構體(struct)、共用體(l聯合體)union、枚舉。 &#xff08;一&#xff09;結構體&#xff1a;struct 1.1 概念 是一種自定義的數據類型結構體是構造類型的一種不同數據類型的集合地址空間連續&#xff0c;每次分配最大數據類型的寬度占用內存為所有變…

插入排序之表插入排序

1.表插入排序只是求得一個有序的鏈表&#xff0c;它是修改指針的值來代替移動記錄&#xff0c;操作過程如下 2.但是這樣只能進行順序查找&#xff0c;不能進行隨機查找&#xff0c;為了能實現有序表的折半查找&#xff0c;需要對記錄進行重新排列。操作過程如下&#xff1a; 3.…

電容降壓LED驅動電路

電容降壓電路具有體積小、成本低、電流相對穩定等優點&#xff0c;可應用于小功率的LED驅動電路中&#xff0c;本文主要介紹了電容降壓電路的基本電路 圖一&#xff1a; 電容降壓式簡易電源的基本原理如圖一所示&#xff0c;C3為降壓電容器&#xff1b;D4為半波整流二極管&…

延時電路分析

延時電路經常會用到&#xff0c;RC電路是比較簡單的電路。在電路設計中經常會用到將電阻和電容正極連接&#xff0c;電阻另一端接上電源&#xff0c;電容負極接地。 簡單的延時電路 上面就是延時的電路圖了&#xff0c;延時的時間為T-ln((VCC-Vout)/VCC)RC&#xff0c;公式中的…

恒流電路的分析(一)

在這里分析一個簡單的LED恒流電路&#xff0c;軟件采用Multisim進行波形采集 一、元器件 R1為80KΩ左右的金屬膜電阻&#xff1b;Q選取耐壓值超過350V的VPN三極管&#xff1b;D選取2V左右的穩壓二極管(如1N4680)&#xff1b;C2選取10V、100UF以上的電解電容&#xff1b;R2選擇…

ST-LINK USB communication error解決方法

今天在用stlink-v2下載程序時出現ST-LINK USB communication error&#xff0c;突然就出現了這個問題&#xff0c;在網上找了好多解決辦法都不可以用&#xff0c;下面給出我的解決方案&#xff0c;文章末尾給出了網上的幾種解決辦法&#xff0c;僅供參考。 第一步&#xff1a;找…

ajax實現上傳文件

1.html部分 <input style"width: 280px" type"file" name"upLoadProjectPlan" id"upLoadProjectPlan" value"<%taskAppend.getTaskAllocationDoc()%>"/><a style"float: right; margin-right: 40px&qu…

利用STM32制作紅外測溫儀之硬件設計

最近受疫情的影響詳細大家都在家里沒事干&#xff0c;這里利用stm32最小系統做一個紅外測溫儀 這篇教程里我們來制作紅外測溫儀需要用到的硬件&#xff0c;關于PCB的工程文件&#xff0c;后文會給出。 &#xff08;一&#xff09;系統分析 由于我們的功能比較單一&#xff0c;…

如何在博客中插入背景音樂

1.首先進入網音樂官方網站&#xff1b; 2.查找自己喜歡的歌&#xff0c;看到如下界面 3.點擊"生成外鏈播放器" 4.看到下面的html代碼了嗎&#xff1f;將代碼進行復制。 5.進入博客園&#xff0c;點擊 "管理" ->"設置"&#xff0c; 將代碼復制…

常用存儲器介紹

注意&#xff1a;"易失/非易失"是指存儲器斷電后&#xff0c;它存儲的數據內容是否會丟失的特性。 &#xff08;一&#xff09;RAM和ROM 1.1 RAM RAM即隨機存儲器&#xff0c;它是指存儲器中的數據被讀入或者寫入與信息所在位置無關&#xff0c;時間都是相同的 1…

TortoiseGit與github實現項目的上傳

1. 下載并安裝相關軟件 這里主要涉及的軟件包括msysgit和TortoiseGit。 msysgit的下載地址&#xff1a;http://msysgit.googlecode.com/files/Git-1.7.4-preview20110204.exe TortoiseGit的下載地址&#xff1a;http://code.google.com/p/tortoisegit/downloads/list&#xff0…

Uboot啟動

&#xff08;一&#xff09;uboot 配置編譯分析 u-boot源碼是通過gcc和Makefile組織編譯的&#xff0c;頂層目錄下的Makefile可通過boards.cfg來設置開發板的定義 然后遞歸調用各級子目錄下的Makefile&#xff0c;把編譯過的程序連接成u-boot boards.cfg文件&#xff1a; 開發…

行列式計算的兩種方法

#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 100 using namespace std; int a[N][N]; double aa[N][N]; int n;/**********************************************************/ //求行列式的值&#xff1…

uboot啟動流程分析

Uboot的啟動流程分為兩個階段&#xff0c;第一階段主要是匯編語言編寫&#xff0c;第二階段是C語言編寫&#xff0c;每個階段所做的工作不同&#xff0c;這篇文章分析的是uboot 2010版&#xff0c;以tiny4412的uboot為例。 啟動過程涉及的主要文件&#xff1a; arch/arm/cpu/a…

(一)uboot的移植與制作

目錄&#xff08;一&#xff09;環境&#xff08;二&#xff09;流程分析&#xff08;三&#xff09;具體步驟在裸機啟動流程里涉及到BL1&#xff0c;BL2為系統的加載啟動項&#xff0c;全稱為BootLoader。 Boot Loader 是在操作系統內核運行之前運行的一段小程序。通過這段小程…

jquery ajax(實現單獨提交某個form)

function submitTaskScore(formid) {//formid表示的是表單的id$.ajax({type:"post",url:"companyAndDistributeAction!scoreTask",//后臺處理程序data:$(formid).serialize(),success:function(){document.getElementById("hjzggContent").inner…

(二)linux內核鏡像制作

&#xff08;一&#xff09;目的 在進行嵌入式開發的時候&#xff0c;我們往往會先在電腦上安裝交叉編譯器&#xff0c;然后編譯目標板上的代碼&#xff0c;最后把代碼下載到電路板中&#xff0c;嵌入式系統組成包括&#xff1a;BootLoaderkernelfilesystemapplication&#x…

js+css實現骰子的隨機轉動

網上找的例子&#xff0c;然后增添了新的東西&#xff0c;在這里展示一下...... 效果圖預覽&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html x…

linux安裝交叉編譯環境

&#xff08;一&#xff09;交叉編譯器的簡介 &#xff08;1&#xff09;本地編譯 在了解交叉編譯之前我們首先介紹一下另一個概念&#xff1a;本地編譯 之前所做的C開發屬于本地編譯&#xff0c;即在當前PC下&#xff08;x86的CPU下&#xff09;&#xff0c;直接編譯出可以運…