【C語言】10.C語言指針(1)

文章目錄

  • 1.內存和地址
    • 1.1 內存
    • 1.2 究竟該如何理解編址
  • 2.指針變量和地址
    • 2.1 取地址操作符(&)
    • 2.2 指針變量和解引?操作符(*)
      • 2.2.1 指針變量
      • 2.2.2 如何拆解指針類型
      • 2.2.3 解引?操作符
    • 2.3 指針變量的??
  • 3.指針變量類型的意義
    • 3.1 指針的解引?
    • 3.2 指針+-整數
    • 3.3 void* 指針
  • 4.const修飾指針
    • 4.1 const修飾變量
    • 4.2 const修飾指針變量
      • 4.2.1.const在`*`左邊
      • 4.2.2.const在`*`右邊
  • 5.指針運算
    • 5.1 指針+- 整數
    • 5.2 指針-指針
    • 5.3 指針的關系運算
  • 6.野指針
    • 6.1 野指針成因
      • 6.1.1.指針未初始化
      • 6.1.2.指針越界訪問
      • 6.1.3.指針指向的空間釋放
    • 6.2 如何規避野指針
      • 6.2.1 指針初始化
      • 6.2.2 ??指針越界
      • 6.2.3 指針變量不再使?時,及時置NULL,指針使?之前檢查有效性
      • 6.2.4 避免返回局部變量的地址
  • 7.assert斷言
  • 8.指針的使用和傳址調用
    • 8.1 strlen的模擬實現
    • 8.2 傳值調?和傳址調?


1.內存和地址

1.1 內存

生活中,每個房間有了房間號,就能提高效率,能快速的找到房間。

CPU(中央處理器)在處理數據的時候,需要的數據是在內存中讀取的,處理后的數據也會放回內存中,那這些內存空間如何高效的管理呢?

其實也是把內存劃分為一個個的內存單元,每個內存單元的大小取1個字節

一個比特位可以存儲一個2進制的位1或者0。

  1. bit - 比特位

  2. Byte - 字節

  3. KB

  4. MB

  5. GB

  6. TB

  7. PB

  1. 1Byte = 8bit
  2. 1KB = 1024Byte
  3. 1MB = 1024KB
  4. 1GB = 1024MB
  5. 1TB = 1024GB
  6. 1PB = 1024TB

每個內存單元,相當于一個學生宿舍,一個字節空間里面能放8個比特位,就好比同學們住的八人間,每個人是一個比特位。

每個內存單元也都有一個編號(這個編號就相當于宿舍房間的門牌號),有了這個內存單元的編號,CPU就可以快速找到一個內存空間。

生活中我們把門牌號也叫地址,在計算機中我們把內存單元的編號也稱為地址。C語言中給地址起了新的名字叫:指針

所以我們可以理解為:

內存單元的編號 == 地址 == 指針

在這里插入圖片描述


1.2 究竟該如何理解編址

CPU訪問內存中的某個字節空間,必須知道這個字節空間在內存的什么位置,而因為內存中字節很多,所以需要給內存進行編址(就如同宿舍很多,需要給宿舍編號一樣)。計算機中的編址,并不是把每個字節的地址記錄下來,而是通過硬件設計完成的。

在這里插入圖片描述

首先,必須理解,計算機內是有很多的硬件單元,而硬件單元是要互相協同工作的。所謂的協
同,至少相互之間要能夠進行數據傳遞。

但是硬件與硬件之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來。

而CPU和內存之間也是有大量的數據交互的,所以,兩者必須也用線連起來。

不過,我們今天只關心地址總線。

我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么一根線,就能表示2種含義,2根線就能表示4種含義,依次類推。32根地址線,就能表示2^32種含義,每一種含義都代表一個地址。

地址信息被下達給內存,在內存上,就可以找到該地址對應的數據,將數據在通過數據總線傳入CPU內寄存器。


2.指針變量和地址

2.1 取地址操作符(&)

理解了內存和地址的關系,我們再回到C語言,在C語言中創建變量其實就是向內存申請空間。

#include <stdio.h>
int main()
{int a = 10;return 0;
}

為了方便查看內存空間,我們可以換到x86編譯環境。

在這里插入圖片描述

a = 10,這里為什么是0a呢?因為這里是十六進制數。

比如,上述的代碼就是創建了整型變量a,內存中申請4個字節,用于存放整數10,其中每個字節都有地址,上圖中4個字節的地址分別是:

0x003CF734

0x003CF735

0x003CF736

0x003CF737

這里變量的名字a,僅僅是給程序員看的,編譯器是不看名字的,編譯器是通過地址來找內存單元的。

我們如何能得到a的地址呢?

這里就得學習一個操作符(&)-取地址操作符。

#include <stdio.h>
int main()
{int a = 10;&a;//通過取地址操作符:& 來取出a的地址printf("%p\n", &a);//%p是專門用來打印地址的。return 0;
}

打印:

0x003CF734

&a取出的是a所占4個字節中地址較小的字節的地址。

在這里插入圖片描述

整型變量占用4個字節,我們只要知道了第一個字節地址,順藤摸瓜訪問到4個字節的數據也是可行的。


2.2 指針變量和解引?操作符(*)

2.2.1 指針變量

我們通過取地址操作符(&)拿到的地址是一個數值,比如:0x006FFD70,這個數值有時候也是需要存儲起來,方便后期再使用的,那我們把這樣的地址值存放在哪里呢?

答案是:指針變量中。

#include <stdio.h>
int main()
{int a = 10;int * pa = &a;//取出a的地址并存儲到指針變量pa中//pa是一個變量,這個變量pa是用來存放地址(指針)的,pa叫指針變量return 0;
}

pa是指針變量的名字

int*是指針變量的類型


2.2.2 如何拆解指針類型

a是一個int類型的變量,里面存了10,地址是0x003CF734

在這里插入圖片描述

我們存起來這個地址是不是又要創建一個變量?

在這里插入圖片描述

pa變量里面存的是0x003CF734,類型是int*

  1. *表示pa是指針變量。

  2. int表示pa指向的變量a的類型是int

然后我們依樣畫葫蘆:

char ch = 'w';

w的指針變量是是怎么寫的?

char* pc = &ch;

指針變量也是一種變量,這種變量就是用來存放地址的,存放在指針變量中的值都會理解為地址。


2.2.3 解引?操作符

我們將地址保存起來,未來是要使用的,那怎么使用呢?

我們只要拿到了地址(指針),就可以通過地址(指針)找到地址(指針)指向的對象,這里必須學習一個操作符叫解引用操作符(*)。

int main()
{int a = 100;int* pa = &a;*pa = 0;//* 解引用操作符(間接訪問操作符)//*pa 就是 areturn 0;
}

代碼中第5行就使用了解引用操作符, *pa 的意思就是通過pa中存放的地址,找到指向的空間,*pa其實就是a變量。

所以*pa = 0,這個操作符是把a改成了0。

這個操作和直接把a賦值為0作用是一樣的,但是多了一種的途徑,寫代碼就會更加靈活。


2.3 指針變量的??

32位機器假設有32根地址總線,每根地址線出來的電信號轉換成數字信號后是1或者0,那我們把32根地址線產生的2進制序列當做一個地址,那么一個地址就是32個bit位,需要4個字節才能存儲。

如果指針變量是用來存放地址的,那么指針變量的大小就得是4個字節的空間才可以。

同理64位機器,假設有64根地址線,一個地址就是64個二進制位組成的二進制序列,存儲起來就需要8個字節的空間,指針變量的大小就是8個字節。

#include <stdio.h>
//指針變量的大小取決于地址的大小
//32位平臺下地址是32個bit位(即4個字節)
//64位平臺下地址是64個bit位(即8個字節)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}
在這里插入圖片描述在這里插入圖片描述
X86環境X64環境
  • 32位平臺下地址是32個bit位,指針變量大小是4個字節

  • 64位平臺下地址是64個bit位,指針變量大小是8個字節

注意:指針變量的大小和類型是無關的,只要指針類型的變量,在相同的平臺下,大小都是相同的。


3.指針變量類型的意義

指針變量的大小和類型無關,只要是指針變量,在同一個平臺下,大小都是一樣的,為什么還要有各種各樣的指針類型呢?

其實指針類型是有特殊意義的。

3.1 指針的解引?

代碼1:

#include <stdio.h>
int main()
{int n = 0x11223344;int *pi = &n; *pi = 0; return 0;
}

代碼2:

#include <stdio.h>
int main()
{int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;
}

上面n的賦值之所以寫0x11223344這個東西,是因為寫小了是放不滿4個字節的,看變化就不明顯了。

通過調試我們可以看到,代碼1會將n的4個字節全部改為0,但是代碼2只是將n的第一個字節改為0。

我們可以得到一個結論:指針的類型決定了,對指針解引用的時候有多大的權限(一次能操作幾個字節)。

比如: char* 的指針解引用就只能訪問一個字節,而 int* 的指針的解引用就能訪問四個字節。


3.2 指針±整數

#include <stdio.h>
int main()
{int n = 20;int* pc = &n;char* pi = &n;printf("&n = %p\n", &n);printf("pc = %p\n", pc);printf("pi = %p\n", pi);printf("&n+1 = %p\n", &n+1);printf("pc+1 = %p\n", pc + 1);printf("pi+1 = %p\n", pi + 1);return 0;
}

打印:

在這里插入圖片描述

&n&n+1地址相差4。

pcpc+1地址相差4。

pipi+1地址相差1。

為了方便觀看,下面圖里面我們把a改一下。

在這里插入圖片描述

加一和減一是一樣的過程。

我們可以得到結論:指針的類型決定了指針向前或者向后走一步有多大(距離)。


3.3 void* 指針

在指針類型中有一種特殊的類型是 void * 類型的,可以理解為無具體類型的指針(或者叫泛型指針),這種類型的指針可以用來接受任意類型地址。但是也有局限性, void* 類型的指針不能直接進行指針的±整數和解引用的運算。

int main()
{int a = 10;int* pa = &a;//&a是int*char* pc = &a;//&a是int*return 0;
}

在上面的代碼中,將一個int類型的變量的地址賦值給一個char類型的指針變量。編譯器給出了一個警告,是因為類型不兼容。

而使用void類型就不會有這樣的問題。

#include <stdio.h>
int main()
{int a = 10;char ch = 'w';void* pv1 = &a;//&a是int*void* pv2 = &ch;//&ch是char*return 0;
}

解引用:

#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;//void* 是無具體類型指針,它解引用的時候不知道解析幾個字節*pc = 0;pv1 + 1;//這個也不行,因為它是無具體類型指針,你加一的時候打算跳過幾個字節?return 0;
}

編譯報錯。

因為void* 類型的指針不能直接進行指針的±整數和解引用的運算。

那么 void* 類型的指針到底有什么用呢?

一般 void* 類型的指針是使用在函數參數的部分,用來接收不同類型數據的地址,這樣的設計可以實現泛型編程的效果。使得一個函數來處理多種類型的數據。


4.const修飾指針

4.1 const修飾變量

變量是可以修改的,如果把變量的地址交給一個指針變量,通過指針變量的也可以修改這個變量。但是如果我們希望一個變量加上一些限制,不能被修改,怎么做呢?

我們可以通過const。

#include <stdio.h>
int main()
{int m = 0;m = 20;//m可以被修改const int n = 0;n = 20;//n不能被修改return 0;
}

const修飾的變量叫做常變量,這個被修飾的變量本質上還是變量,只是不能被修改。

怎么證明這個是常變量而不是常量呢?

int n = 10;
int arr[n];

這里會報錯,因為C99語法之前是不支持變長數組的,數組大小是需要常量,常量表達式來制定的,不能是變量。

const int n = 10;
int arr[n];

這里還是會報錯,說明const修飾的變量n不是常量,而是常變量。

但是如果我們繞過n,使用n的地址,去修改n就能做到了。

int main()
{const int n = 0;printf("n = %d\n", n);int*p = &n;*p = 20;printf("n = %d\n", n);return 0;
}

打印:

n = 0
n = 20

這里確實修改了const修飾的n,但是我們還是要思考一下,為什么n要被const修飾呢?

就是為了不能被修改。

如果p拿到n的地址就能修改n,這樣就打破了const的限制,這是不合理的,所以應該讓p拿到n的地址也不能修改n,那接下來怎么做呢?


4.2 const修飾指針變量

一般來講const修飾指針變量,放在*左邊和*右邊,意義是不一樣的。

先看個小例子:

int main() {int n = 10;int m = 100;int* p = &n;//p里面存放著n的地址,p本身也有個地址*p = 20;//通過p里面存放著n的地址來找到n,改變n的值,p本身地址不變//不會報錯p = &m;//把p里存放的n的地址改成m的地址,p本身地址不變//不會報錯return 0;
}

關于指針變量p有3個相關的值:

  1. p,p里面存放著一個地址

  2. *p,p指向的那個對象

  3. &p,表示的是p變量的地址

然后我們加入const:

4.2.1.const在*左邊

int main() {int n = 10;int m = 100;int const* p = &n;//const int* p = &n;這個和上面那行一樣*p = 20;//會報錯p = &m;//不會報錯return 0;
}

為什么會報錯呢?

因為const放在這里限制的是*p,也就是p指向的對象n。但是沒有限制p本身。也就是說可以修改p,但是不能通過*p修改n。

這里定義n的時候沒加const,所以可以直接更改n,但是不能通過*p修改n。

如果定義n的時候加了const,那么不可以直接更改n,也不能通過*p修改n。

所以,const修飾指針變量,限制的是指針指向的內容,就是不能通過指針變量p來修改p所指向的內容。但是指針變量p本身是可以改變的。

4.2.2.const在*右邊

int main() {int n = 10;int m = 100;int* const p = &n;*p = 20;//不會報錯p = &m;//會報錯return 0;
}

這里的const修飾的是p本身,沒有修飾*p

也就是我們不能改變p里面存放的值,但是我們可以通過*p來改變n的值。

const如果放在*的右邊,修飾的是指針變量本身,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變。


5.指針運算

5.1 指針± 整數

因為數組在內存中是連續存放的,只要知道第一個元素的地址,順藤摸瓜就能找到后面的所有元素。

int arr[10] = {1,2,3,4,5,6,7,8,9,10};

我們來看一下下面的代碼:

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("%d ", *(p+i));//p+i 這里就是指針+整數}return 0;
}

打印:

1 2 3 4 5 6 7 8 9 10

在這里插入圖片描述

int*p;

p+i是跳過i*sizeof(int)個字節

  1. 指針類型決定了指針+1的步長,決定了指針解引用的權限。

  2. 數組在內存中是連續存放的

然后我們看一下下面的例子:

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };char* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *p);//p+i 這里就是指針+整數p += 4;}return 0;
}

打印:

1 2 3 4 5 6 7 8 9 10

乍一看是不是感覺沒問題?其實這個代碼是有問題的。

在這里插入圖片描述

因為p是char*類型的,這里的p += 4;會只讀取每個int類型(int類型4字節,char類型1字節)的第一個字節,跳過每個int類型的后3個字節。

數字小的時候可能看不出什么,但是數字大了就可能會打印奇怪的東西。

int main()
{int arr[10] = { 100,200,3,4,5,6,7,8,9,10 };char* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *p);//p+i 這里就是指針+整數p += 4;}return 0;
}

打印:

100 -56 3 4 5 6 7 8 9 10

這里的-56是怎么來的呢?我也很好奇,就算了一下。

-56原碼:1011 1000
-56反碼:1100 0111
-56補碼:1100 1000

-56的補碼,如果看成無符號二進制數的話,剛好是200。

200存到int類型里面是這樣的:因為int類型4字節

200原碼:00000000 00000000 00000000 11001000
200補碼:00000000 00000000 00000000 11001000

但是char類型就1個字節,計算機里面又是看補碼的。

補碼:1100 1000
反碼:1100 0111
原碼:1011 1000 ---> -56

于是就得到-56這個東西。


5.2 指針-指針

指針-指針的絕對值是指針和指針之間元素的個數。

指針-指針計算的前提條件是兩個指針指向的是同一個空間

int main()
{int arr[10] = { 1,200,3,4,5,6,7,8,9,10 };printf("%d\n", &arr[9] - &arr[0]);return 0;
}

打印:

9

用一個函數求字符串長度:

int main()
{char arr[] = "abcdef";size_t len = strlen(arr);//strlen統計\0之前的字符個數//arr是arr[]數組首元素地址,相當于&arr[0]printf("%zd\n", len);return 0;
}

打印:

6

寫一個函數求字符串長度:(指針++)

//size_t是一種無符號整型
size_t my_strlen(char* p) {size_t count = 0;//計數器while (*p != '\0') {count++;p++;}return count;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}

打印:

6

或者我們也可以用指針-指針

size_t my_strlen(char* p) {char* start = p;char* end = p;while (*end != '\0') {end++;}return end-start;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}

打印:

6

注意:

指針+指針沒啥意義。

就像日期-日期可以得到間隔天數,日期+日期得到的東西沒啥用。


5.3 指針的關系運算

實際上就是比較指針的大小 。

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int sz = sizeof(arr)/sizeof(arr[0]);while(p < &arr[sz]) //指針的大小比較{printf("%d ", *p);p++;}return 0;
}

打印:

1 2 3 4 5 6 7 8 9 10

在這里插入圖片描述


6.野指針

野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)

6.1 野指針成因

6.1.1.指針未初始化

#include <stdio.h>
int main()
{ int *p;//局部變量指針未初始化,默認為隨機值*p = 20;return 0;
}

6.1.2.指針越界訪問

#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//當指針指向的范圍超出數組arr的范圍時,p就是野指針*(p++) = i;}return 0;
}

6.1.3.指針指向的空間釋放

#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();printf("%d\n", *p);return 0;
}

test函數調用完就銷毀了,p得到的地址就不屬于當前程序了。


6.2 如何規避野指針

6.2.1 指針初始化

如果明確知道指針指向哪里就直接賦值地址,如果不知道指針應該指向哪里,可以給指針賦值NULL。

NULL 是C語言中定義的一個標識符常量,值是0,0也是地址,這個地址是無法使用的,讀寫該地址會報錯。

int main()
{int num = 10;int*p1 = &num;int*p2 = NULL;*p2 = 200;//寫這行的話程序會崩掉,因為空指針是不能訪問的。return 0;
}

我們也可以這樣:

int main()
{int* p = NULL;if(p!=NULL){*p = 200;}return 0;
}

這樣就沒問題啦~


6.2.2 ??指針越界

一個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間,不能超出范圍訪問,超出了就是越界訪問。


6.2.3 指針變量不再使?時,及時置NULL,指針使?之前檢查有效性

當指針變量指向一塊區域的時候,我們可以通過指針訪問該區域,后期不再使用這個指針訪問空間的時候,我們可以把該指針置為NULL。因為約定俗成的一個規則就是:只要是NULL指針就不去訪問,同時使用指針之前可以判斷指針是否為NULL

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;for(i=0; i<10; i++){*(p++) = i;}//p++是先用后加的,所以用完之后會越界//此時p已經越界了,可以把p置為NULLp = NULL;//下次使用的時候,判斷p不為NULL的時候再使用p = &arr[0];//重新讓p獲得地址if(p != NULL) //判斷{//...}return 0;
}

6.2.4 避免返回局部變量的地址

不要返回局部變量的地址。


7.assert斷言

assert.h 頭文件定義了宏 assert() ,用于在運行時確保程序符合指定條件,如果不符合,就報錯終止運行。這個宏常常被稱為“斷言”。

assert(p != NULL);

上面代碼在程序運行到這一行語句時,驗證變量 p 是否等于 NULL 。如果確實不等于 NULL ,程序繼續運行,否則就會終止運行,并且給出報錯信息提示。

assert() 宏接受一個表達式作為參數。

如果該表達式為真(返回值非零), assert() 不會產生任何作用,程序繼續運行。

如果該表達式為假(返回值為零), assert() 就會報錯,在標準錯誤流 stderr 中寫入一條錯誤信息,顯示沒有通過的表達式,以及包含這個表達式的文件名和行號。

使用 assert() 有幾個好處:

它不僅能自動標識文件和出問題的行號,還有一種無需更改代碼就能開啟或關閉 assert() 的機制。如果已經確認程序沒有問題,不需要再做斷言,就在 #include <assert.h> 語句的前面,定義一個宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

然后,重新編譯程序,編譯器就會禁用文件中所有的 assert() 語句。如果程序又出現問題,可以移除這條 #define NDEBUG 指令(或者把它注釋掉),再次編譯,這樣就重新啟用了 assert() 語句。

缺點:因為引入了額外的檢查,增加了程序的運行時間。

一般我們可以在 Debug 中使用,在 Release 版本中選擇禁用 assert 就行,在 VS 這樣的集成開發環境中,在 Release 版本中,直接就是優化掉了。


8.指針的使用和傳址調用

8.1 strlen的模擬實現

#include <assert.h>
size_t my_strlen(const char * str)//這里加個const是為了防止arr通過str被修改
{//這里加了const會增強代碼的健壯性(魯棒性)size_t count = 0;assert(str != NULL);//斷言while(*str){count++;str++;}return count;
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%d\n", len);return 0;
}

打印:

6

8.2 傳值調?和傳址調?

學習指針的目的是使用指針解決問題,那什么問題,非指針不可呢?

例如:寫一個函數,交換兩個整型變量的值

#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}

打印:

10 20
交換前:a=10 b=20
交換后:a=10 b=20

咦?為什么沒產生交換效果呢?

因為a和ba和b在main函數內部創建,形參x和y在Swap1函數內部創建并接收a和b的值。

x和y是獨立的空間,在Swap1函數內部交換x和y的值,自然不會影響a和b。

Swap1函數在使用的時候,是把變量本身直接傳遞給了函數,這種調用函數的方式我們之前在函數的時候就知道了,這種叫傳值調用

說明:實參傳遞給形參的時候,形參會單獨創建一份臨時空間來接收實參,對形參的修改不影響實參。

所以Swap1失敗。

怎樣解決?

我們現在要解決的就是當調用Swap函數的時候,Swap函數內部操作的就是main函數中的a和b,直接將a和b的值交換了。

那么就可以使用指針了,在main函數中將a和b的地址傳遞給Swap函數,Swap函數里邊通過地址間接的操作main函數中的a和b,并達到交換的效果

void Swap2(int*px, int*py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}

打印:

10 20
交換前:a=10 b=20
交換后:a=20 b=10

成功交換了。

這里調用Swap2函數的時候是將變量的地址傳遞給了函數,這種函數調用方式叫:傳址調用

傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量。

所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采用**傳值調用**。

如果函數內部要修改主調函數中的變量的值,就需要**傳址調用**。

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

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

相關文章

匯編:字符串的輸出

在16位匯編程序中&#xff0c;可以使用DOS中斷21h的功能號09h來打印字符串&#xff1b;下面是一個簡單的示例程序&#xff0c;演示了如何在16位匯編程序中打印字符串&#xff1a; assume cs:code,ds:data ? data segmentszBuffer db 0dh,0ah,HelloWorld$ //定義字符串 data …

【C++】哈夫曼編碼:高效的壓縮算法

哈夫曼編碼&#xff1a;高效的壓縮算法 什么是哈夫曼編碼&#xff1f; 哈夫曼編碼是一種用于數據壓縮的無損編碼方法&#xff0c;由David A. Huffman于1952年提出。它利用了字符出現頻率的不均勻性&#xff0c;通過構建最優前綴碼&#xff0c;能夠有效減少數據的冗余&#xf…

Flutter仿照微信實現九宮格頭像

一、效果圖 2、主要代碼 import dart:io; import dart:math;import package:cached_network_image/cached_network_image.dart; import package:flutter/material.dart;class ImageGrid extends StatelessWidget {final List<String> imageUrls; // 假設這是你的圖片URL…

關于Iterator 和ListIterator的詳解

1.Iterator Iterator的定義如下&#xff1a; public interface Iterator<E> {} Iterator是一個接口&#xff0c;它是集合的迭代器。集合可以通過Iterator去遍歷集合中的元素。Iterator提供的API接口如下&#xff1a; forEachRemaining(Consumer<? super E> act…

VS2022通過C++網絡庫Boost.Asio創建一個簡單的同步TCP服務器和客戶端

Boost.Asio是一個用于網絡和異步編程的C庫。它提供了一種跨平臺的方式來處理網絡編程和異步操作&#xff0c;使開發人員能夠創建高性能的網絡應用程序&#xff0c;asio幾乎支持所有你能夠想到的網絡協議&#xff0c;比如tcp、udp、ip、http、icmp等&#xff0c;C通過asio庫可以…

找出第 K 大的異或坐標值

問題 給你一個二維矩陣 matrix 和一個整數 k &#xff0c;矩陣大小為 m x n 由非負整數組成。 矩陣中坐標 (a, b) 的 值 可由對所有滿足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下標從 0 開始計數&#xff09;執行異或運算得到。…

淺談網絡通信(1)

文章目錄 一、認識一些網絡基礎概念1.1、ip地址1.2、端口號1.3、協議1.4、協議分層1.5、協議分層的2種方式1.5.1、OSI七層模型1.5.2、TCP/IP五層模型[!]1.5.2.1、TCP/IP五層協議各層的含義及功能 二、網絡中數據傳輸的基本流程——封裝、分用2.1、封裝2.2、分用2.2.1、5元組 三…

基于大模型和RAG技術實現的開源項目

基于大模型和RAG技術實現的開源項目 為解決大模型的不足&#xff0c;使用RAG技術增強大模型生成內容的針對性和可讀性能力&#xff0c;有很多不錯的開源項目。例如下面的項目。 1 ragflow 優點&#xff1a;可以對文檔和知識庫進行管理&#xff0c;構建不同的知識庫&#xff…

python冰雹序列的探索與編程實現

新書上架~&#x1f447;全國包郵奧~ python實用小工具開發教程http://pythontoolsteach.com/3 歡迎關注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目錄 一、冰雹序列的奧秘 二、編程實現冰雹序列 三、測試與驗證 四、總結與展望 一、冰雹序列的…

整理好了!2024年最常見 20 道 Redis面試題(八)

上一篇地址&#xff1a;整理好了&#xff01;2024年最常見 20 道 Redis面試題&#xff08;七&#xff09;-CSDN博客 十五、Redis 的性能調優有哪些方法&#xff1f; Redis的性能調優是一個多方面的工作&#xff0c;涉及到硬件、配置、代碼層面的優化等多個方面。以下是一些常…

openEuler 22.03 LTS SP3源碼編譯部署OpenStack-Caracal

openEuler 22.03 LTS SP3源碼編譯部署OpenStack-Caracal 說明機器詳情安裝操作系統注意事項基礎準備Controller節點 && Compute節點 && Block節點關閉防火墻關閉selinux設置靜態IP更新安裝前準備Controller節點 && Compute節點 && Block節點設…

第十課,while循環

一&#xff0c;認識循環是什么 循環普遍存在于日常生活中&#xff0c;同樣&#xff0c;在程序中&#xff0c;循環功能也是至關重要的基礎功能。 當程序需要重復執行某一段代碼&#xff0c;利用循環可以輕松完成工作 例如我要你打印100次上課&#xff0c;直接寫100次print&…

python調用阿里云通義千問(q-wen-max)API-只能總結pdf文檔內容

文章目錄 通義千問插件PDF解析插件調用案例通義千問插件 Dashscope插件功能能夠使得大模型的生成內容與外部三方應用結合,使得模型生成的內容更加準確和豐富,模型將擁有更好的生成能力。您也可以通過開發自定義插件,來使得模型生成更符合您預期的結果。 使用插件功能,大模…

電子閱覽室在管理時需注意什么

關于如今的絕大多數人來說&#xff0c;想必都聽說過“電子閱覽室”這一概念。它首要運用在校園中&#xff0c;給學生們供給愈加豐厚的常識儲藏。它也是一個獨立的局域網&#xff0c;在校園網絡中作為重要的一個組成部分而存在。但是&#xff0c;一個好的電子閱覽室是需求滿意運…

LORA學習筆記3——訓練參數

訓練步長 Step&#xff08;步&#xff09;:模型訓練時ai模型會根據標注生成一個圖片&#xff0c;并與學習圖片進行對比&#xff0c;通過對比的結果調整嵌入向量。這樣的一個流程就被稱為“一步”。 如果一個訓練集中有50張圖片&#xff0c;每張圖片設定為要訓練10次&#xff…

CCF20231201——倉庫規劃

CCF20231201——倉庫規劃 代碼如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,m,a[1001][11],b[1001]{0};cin>>n>>m;for(int i1;i<n;i){for(int j1;j<m;j)cin>>a[i][j];}for(int i1;i<n;i){bool foundfals…

設計模式在芯片驗證中的應用——模板方法

一、模板方法 模板方法(Template Method)設計模式是一種行為設計模式&#xff0c; 它在父類中定義了一個功能的框架&#xff0c; 允許子類在不修改結構的情況下重寫功能的特定步驟。也就是模板方法定義了一組有序執行的操作&#xff0c;將一些步驟的實現留給子類&#xff0c;同…

把自己的垃圾代碼發布到官方中央倉庫

參考博客&#xff1a;將組件發布到maven中央倉庫-CSDN博客 感謝這位博主。但是他的步驟有漏缺&#xff0c;相對進行補充 訪問管理頁面 網址&#xff1a;Maven Central 新注冊賬號&#xff0c;或者使用github快捷登錄&#xff0c;建議使用github快捷登錄 添加命名空間 注意&…

連接mysql的java代碼

要在Java中連接MySQL數據庫,你需要以下幾個步驟: 導入MySQL JDBC驅動:在項目中添加MySQL JDBC驅動的依賴。如果你使用的是Maven,可以在pom.xml中添加依賴;如果使用的是Gradle,可以在build.gradle中添加依賴;如果不使用構建工具,需要手動下載驅動并添加到項目中。 編寫J…

【Linux】進程通信實戰 —— 進程池項目

送給大家一句話: 沒有一顆星&#xff0c;會因為追求夢想而受傷&#xff0c;當你真心渴望某樣東西時&#xff0c;整個宇宙都會來幫忙。 – 保羅?戈埃羅 《牧羊少年奇幻之旅》 &#x1f3d5;?&#x1f3d5;?&#x1f3d5;?&#x1f3d5;?&#x1f3d5;?&#x1f3d5;? &a…