C/C++ 指針和數組

1. 數組 c++

聲明:

一維數組: type arrayName [ arraySize ];
多維數組: type name[size1][size2]...[sizeN];

比如結構體數組的初始化和訪問:

#include <iostream>
#include<stdlib.h>
using namespace std;


struct Share {
	long int share_price;
	char time_date[20];
};


int main()
{
	
	//一維數組
	Share one_D_share[] = { {100, "2017\\05\\31_09:00"}, {200,"2017\\05\\31_09:01"} };

	for (size_t index = 0; index < 2; ++index) {
		cout << one_D_share[index].share_price<<'\t' << one_D_share[index].time_date << endl;
	}

	//二維數組
	Share two_D_share[2][2] = { {{100, "2017\05\31_09:00"}, {200,"2017\\05\31_09:01"}},{{300, "2017\05\31_09:02"}, {400,"2017\05\31_09:04"}} };

	for (size_t i = 0; i < 2; ++i)
	for(size_t j = 0; j < 2; ++j){
		cout << two_D_share[i][j].share_price << two_D_share[i][j].time_date << endl;
	}
	
	//對部分元素賦值,爲魏闕賦值的部分整數位0,字符爲'\0'
	Share share_3[3] = { {100, "2017\\05\\31_09:00"}, {200,"2017\\05\\31_09:01"} };

	for (size_t index = 0; index < 3; ++index) {
		cout << share_3[index].share_price << '\t' << share_3[index].time_date << endl;
	}


	system("pause");
	return 0;
}

2. 指針

1. C中指針的概念

數據存放在內存中,而不同類型的數據佔用的字節數不同,比如int佔用4個字節。爲了正確的訪問到這些數據,必須爲每個字節編號,比如旅館中的房間號,根據編號可以準確的找到某個字節。

一下是4G內存每個字節的編號(十六進制表示:)
在這裏插入圖片描述

內存中字節的編號稱爲地址(Address)或指針(Pointer)。地址從 0 開始依次增加,對於 32 位環境,程序能夠使用的內存爲 4GB,最小的地址爲 0,最大的地址爲 0XFFFFFFFF。

#include <stdio.h>
#include <stdlib.h>

void Function() {

}

int main() {
	int a = 16;
	char str[20] = "C語言指針";
	int arr[] = { 10, 100, 1000 };
	printf("%#X, %#X, %#X, %#X\n", &a, &str, &arr, &Function);
	printf("%#x, %#X, %#X, %#X\n", a, str, arr, Function);
	system("pause");
	return 0;

	

}

out:

0X58FEAC, 0X58FE90, 0X58FE7C, 0XFA1348
0x10, 0X58FE90, 0X58FE7C, 0XFA1348

%#x是16進制輸出

一切都是地址
CPU訪問內存需要的是地址,而非變量名和函數名,在編譯和鏈接成可執行程序後,變量名和函數名都會被替換成地址。

比如:假設int類型 a, b, c的內存地址爲 0X1000, 0x1004, 0x1008,則減法運算c=a-b;
在執行過程中轉換爲:
(0x1008)=(0X1000)(0x1004)(0x1008)=(0X1000)-( 0x1004),其中()爲取值操作,表達式的意思是,取出地址0X1000,0X1004中的值,相減賦值到0X1008的內存地址中。

需要注意的是:比如在上的胡代碼示例中,變量名代表的是數據本身,而函數名,數組名,字符串名錶示的是首地址

2.C指針

如果想在程序中訪問數據的內存地址,只能需要指針變量了。

在C語言中們可以使用一個變量來存放指針(地址),該變量稱爲指針變量,值爲某一數據的地址,該數據可以使數組,字符串,函數,另一普通變量火指針變量。

定義指針變量

datatype *name或者datatype *name=value
*代表是一個指針變量,datatype爲該指針所指向數據的數據類型。比如:

int *ip1=NULL;
int a = 100;
ip1 = &a;

其中,&爲取地址運算符

內存指向模型:在這裏插入圖片描述

inp指向地址0x1000,然後根據類型爲int,則向後取4個字節的地址,作爲數據塊,最終會得到a的數據。

指針可以向普通變量一樣多次寫入和修改

//定義int和char普通變量
int a=0, b=1;
char c='a', d='b';
//定義指針變量
int *ip1 = &a;
char *cp2 = &c;

//修該指針變量
ip1 = &b;
cp2=&c;
/*
與下列修改方法的不同:
*ip1 = b;
*cp2 = d;

指向的地址未變,更改了對應地址的內容,
而上一個是更改了指向的地址,也就是更改了指針變量本身的內容
*/

在聲明變量是必須帶*,而在給指針變量賦值是不需要,實際上*ip表示的是解引用,意思是取出ip所指向地址的數據

修改過程中,指針變化情況:
在這裏插入圖片描述

指針變量可以連續定義:注意每一變量前都要有*
int *a, *b, *c; //a、b、c 的類型都是 int*
而如果寫成下面的形式,只有a爲指針 變量,b,c爲int類型的變量。
int *a, b,c;

解引用獲取數據

格式: *pointer;

這裏的*指針運算符,來取出某個地址上的數據:

	int a = 20;
	int *ip = &a;
	printf("%d, %d\n", a, *ip);

out:

20, 20

在這裏插入圖片描述

處理流程爲,在程序被編譯和連接後,a,ip被替換爲相應的地址0x1001和0x2007,使用*ip時,先通過地址0x2007獲得ip的值,0x1001,也就是a的地址,然後再通過這個值取得變量a的數據,共進行兩次運算;而使用a的話,只需要通過0x1001直接取得它的數據,只要一步計算。

修改內存數據
	int a = 20, b = 200, c = 2000;
	int *ip = &a;
	*ip = b;
	c = *ip;

	printf("%d, %d,%d,%d\n", a, b,c,*ip);

out:

200, 200,200,200

*ip代表的是a中的數據,等價於a。可以將另外一份數據賦值給它,也可以將它賦值給別的數據。

注意:
*在不同的場景下有不同的作用:在變量定義中,表明是指針變量和普通變量。而在使用指針變量是,前面加*號表示獲取指針指向的數據

更復雜的指針變量賦值:

int x, y, *px = &x, *py = &y;
y = *px + 5;  //表示把x的內容加5並賦給y,*px+5相當於(*px)+5
y = ++*px;  //px的內容加上1之後賦給y,++*px相當於++(*px)
y = *px++;  //相當於y=(*px)++
py = px;  //把一個指針的值賦給另一個指針
關於*和&的謎題

假設int類型的變量a,pa是指向它的指針,則
*&a: 理解爲 *(&a),表示先取a的地址相當於pa,在對地址解引用,最表表示的是a
&*pa: 理解爲&(*pa),表示先解引用pa,相當於a,在取a的地址,最終表示的pa

*的總結
  1. 表示乘法,用於算數運算中
  2. 定義指針變量時使用,以和普通變量區分
  3. 使用指針變量時,表示解引用指針指向的數據

3.指針變量的運算

指針時一個用數值表示的地址,因此可以對指針執行算數: ++,–,+,-和比較運算==,<, >

值得注意的是,每次+或-,地址的增加或減少字節數與定義指針變量的類型有關:

int    a = 10,   *pa = &a, *paa = &a;
    double b = 99.9, *pb = &b;
    char   c = '@',  *pc = &c;
    //最初的值
    printf("&a=%#X, &b=%#X, &c=%#X\n", &a, &b, &c);
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //加法運算
    pa++; pb++; pc++;
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //減法運算
    pa -= 2; pb -= 2; pc -= 2;
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //比較運算
    if(pa == paa){
        printf("equally\n");
    }else{
        printf("not equallyd\n");
    }
    return 0;

out:

&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
not equally

從運算結果來看,pa、pb、pc 每次加 1,它們的地址分別增加 4、8、1個字節,這與他們數據類型所佔的字節相同。

如果兩個指針指向的是相關的變量,可以做比較運算,最常用的是利用算數和比較運算進行數組訪問。

	int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int *p;
	for (p = a; p < a + 10; ++p)
	{
		printf("%#x,%d\n", p, *p);
	}

out:

0x96f920,0
0x96f924,1
0x96f928,2
0x96f92c,3
0x96f930,4
0x96f934,5
0x96f938,6
0x96f93c,7
0x96f940,8
0x96f944,9

4. 指針和const關鍵字

以下兩種聲明等效:

const int *ip_a;
int const *ip_a;

代表的是指針指向的數據是常量。不能更改ip_a所指向數據的值,比如*ip_a=42出錯。

int *const ip_b

表示指針(地址)本身是常量,你可以改變ip_b指向的值,比如*ip_b=42;但是卻不可以改變ip_b本身指向的地址,也就是自己的值,比如ip+b++出錯。
注意: 數組的首地址就是常量指針,其指向的地址不可改變,比如int a[10];a++出錯

5.數組指針 array pointer

數組在內存區域中是一塊連續的內存塊
定義:

int arr[] = {1,2,3,4,5};
int *p=arr; // 第一種
int *p = &arr[0];//第二種

數組指針指向是數組中某一具體的元素,而非整個數組,所以數組指針的類型和數組元素的類型相同,在代碼中,p指向的數組元素是int類型,所以p的類型是int*

數組的遍歷方式:

int arr[] = { 0,1,2,3,4};
	int len = sizeof(arr) / sizeof(int);
	int *p=arr;
	//指針遍歷1
	printf("指針遍歷1\n");
	for (; p < arr + len; ++p)
	{
		
		printf("%#x,%d\n", p, *p);
	}
	//指針遍歷2
	p = arr;
	printf("指針遍歷2\n");
	for (int i=0; i < len; ++i)
	{
		
		printf("%#x,%d\n", p + i, *(p + i));
	}
	//下標遍歷
	p = arr;
	printf("下標遍歷\n");
	for (int i = 0; i < len; ++i)
	{
		
		printf("%#x,%d\n", p + i, p[i]); //等價於printf("%#x,%d\n", a + i, a[i])
	}
	//逆向遍歷,反向輸出
	

	printf("反向遍歷\n");
	for (p = arr + len - 1; p >= arr; --p)
	{

		printf("%#x,%d\n", p, *p);
	}

out:

指針遍歷1
0x6ffae8,0
0x6ffaec,1
0x6ffaf0,2
0x6ffaf4,3
0x6ffaf8,4
指針遍歷2
0x6ffae8,0
0x6ffaec,1
0x6ffaf0,2
0x6ffaf4,3
0x6ffaf8,4
下標遍歷
0x6ffae8,0
0x6ffaec,1
0x6ffaf0,2
0x6ffaf4,3
0x6ffaf8,4
反向遍歷
0x6ffaf8,4
0x6ffaf4,3
0x6ffaf0,2
0x6ffaec,1
0x6ffae8,0

注意:
數組在內存中只是數組元素的簡單排列,沒有開始和結束標誌,在求數組的長度時不能使用sizeof(p) / sizeof(int),因爲 p 只是一個指向 int 類型的指針,編譯器並不知道它指向的到底是一個整數還是一系列整數(數組),所以 sizeof§ 求得的是 p 這個指針變量本身所佔用的字節數,而不是整個數組佔用的字節數。

所以在訪問整個數組時,需要有數組長度參數,否則將無法訪問數組。這在數組作爲函數參數時尤其要注意,因爲只是傳入數組首地址。所以還要傳入長度參數

	void printf_arr(int arr[],int len) {
	printf("通過sizeof獲得的長度:%d\n", sizeof(arr) / sizeof(int));
	for (int i = 0; i < len; ++i) {
		printf("%d\t", arr[i]);
		}
	}
	int arr[] = { 0,1,2,3,4};
	int len = sizeof(arr) / sizeof(int);
	printf_arr(arr, len);

out:

通過sizeof獲得的長度:1
0 1 2 3 4

6. 指針數組

顧名思義,指針數組就是數組中的元素爲指針。
定義:

const int MAX=3;
int *p[MAX]

在這裏,把 ptr 聲明爲一個數組,由 MAX 個整數指針組成。因此,ptr 中的每個元素,都是一個指向 int 類型的指針

  1. 簡單的,指向三個int值
	int a = 1, b = 2, c = 3;

	int *ptr[3];
	//賦值爲整數的地址
	ptr[0] = &a;
	ptr[1] = &b;
	ptr[2] = &c;
	for (int i = 0; i < 3; i++)
	{
		printf("Value of var[%d] = %d\n", i, *ptr[i]);
	}

out:

Value of var[0] = 1
Value of var[1] = 2
Value of var[2] = 3

  1. 每個指針指向int類型的數組,最終ptr指向的是二維數組
	int arr1[3] = { 0,1,2 };
	int arr2[2] = { 9,99 };
	int arr3[5] = { 1,10,100,1000,10000 };
	int *ptr[3];
	ptr[0] = arr1;
	ptr[1] = arr2;
	ptr[2] = arr3;

	int length[] = { 3, 2, 5 };

	for (int i = 0; i < 3; ++i) {
		
		
		for (int j = 0; j < length[i]; ++j) {
			//printf("%d ", *(ptr[i] + j)); 與下列等價
			printf("%d ", ptr[i][j]);
		}
		printf("\n");
	}

out:

0 1 2
9 99
1 10 100 1000 10000

內存模型:
在這裏插入圖片描述

7 字符數組和字符串

用來存放字符的數組稱爲字符數組,例如

char a[10]; //一維數組
char b[5][10];  //二維字符數組
char c[20]={'c', '  ', 'p', 'r', 'o', 'g', 'r', 'a','m'};  // 給部分數組元素賦值
char d[]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm' };  //對全體元素賦值時可以省去長度

字符數組使勁上是一系列字符的集合,也就是字符串(String),但是在C語言中,沒有專門的String類型,使用字符數組來存放字符串

  1. 定義
  • 可以直接將字符串賦值給字符數組

    char str[30] = {"c.biancheng.net"};
    char str[30] = "c.biancheng.net";  //這種形式更加簡潔,實際開發中常用
    
  • 也可以不指定數組長度

    	char str[] = {"c.biancheng.net"};
    	char str[] = "c.biancheng.net";  //這種形式更加簡潔,實際開發中常用
    

注意: 字符數組只有在定義時才能將整個字符串一次性地賦值,一旦定義完成後,只能一個字符一個字符的賦值。

char str[4];
str = "ab1"; //錯誤

//一下賦值方式正確
str[0] = 'a'; str[1]='b';str[2]='1';
字符串結束標誌(重點)

在C語言中,字符串總是以\0作爲結尾,\0也被稱爲字符串結束標誌

\0是ASCII碼錶中的第0個字符,英文爲NUL,中文成文“空字符”,在C語言中唯一的效果就是作爲字符串結束標誌

C語言在處理字符串式,會從前往後逐個掃描字符,當遇到\0時認爲到達字符串的末尾,處理結束。如果沒有\0,意味着永遠到達不了字符串的結尾。

由""包裹的字符串會在末尾自動添加’\0’,所以會額外佔據一個字節位置。
下圖演示了“C program”在內存中的存儲情況:
在這裏插入圖片描述

注意:

  • 逐個字符的給數組賦值並不會自動添加\0,
  • 用字符數組存儲字符串式,要注意爲\0多留一個字節
    char str[7] = "abc123";
    "abc123"看起來只包含了 6 個字符,我們卻將 str 的長度定義爲 7,就是爲了能夠容納最後的’\0’。如果將 str 的長度定義爲 6,它就無法容納’\0’了。

當字符串長度大於數組長度時,有些較老或者不嚴格的編譯器並不會報錯,甚至連警告都沒有,這就爲以後的錯誤埋下了伏筆,讀者自己要多多注意。

最好的辦法是:在創建數組時將所有的元素初始化爲0

在數組中存儲A-Z:

#include <stdio.h>
int main(){
    char str[30] = {0};  //將所有元素都初始化爲 0,或者說 '\0'
    char c;
    int i;
    for(c=65,i=0; c<=90; c++,i++){
        str[i] = c;
    }
    printf("%s\n", str);
   
    return 0;
}

注意:在函數內部定義的變量,數組,結構體,共用體等都成爲局部數據,在很多編譯器下,局部數據的初始化通常是隨機初始化爲任意值,而非0值。

8.指針訪問字符數組

指針訪問字符數組和指針訪問普通數組完全一樣,不在贅述

處理上述7中講述的字符數組,還有一種字符串,直接使用指針指向字符串,

char *str="I want to learn c";

字符串中的所有字符在內存中爲連續排列,str指向字符串的第0個字符,通常將第0個字符的地址稱爲字符串的首地址

下面演示如何輸出字符串:

#include <stdio.h>
#include <string.h>
int main(){
    char *str="I want to learn c";
    int len = strlen(str);
   
    //直接輸出字符串
    printf("%s\n", str);
    //使用*(str+i)
    for(int i=0; i<len; i++){
        printf("%c", *(str+i));
    }
    printf("\n");
    //使用str[i]
    for(int i=0; i<len; i++){
        printf("%c", str[i]);
    }
    printf("\n");
    return 0;
}

一下是字符數組的輸出:

#include <stdio.h>
#include <string.h>
int main(){
    char str[] = "I want to learn c";
    int len = strlen(str);
    //直接輸出字符串
    printf("%s\n", str);
    //每次輸出一個字符
    for(int i=0; i<len; i++){
        printf("%c", str[i]);
    }
    printf("\n");
    return 0;
}

上述兩者在輸出方式是很相似,但是兩者最本質的區別是在內存中的存儲區域不同

*字符數組存儲在全局數據區或棧區,而第二種形式的字符串存儲在常量區,*而全局數據區和棧區的字符串(也包括其他數據)有讀取和寫入的權限,而常量區的字符串(也包括其他數據)只有讀取權限,沒有寫入權限

這就意味着:

  • char *str="dsdsd";使用指針定義的字符串爲常量字符串,只可讀取。不可更改字符串內容
  • char srt[]="dsdsd";這種字符數組可以讀取和修改

9. 指針作爲函數參數

用指針變量作爲函數參數可以將函數外部的地址傳遞帶函數內部,使得在函數內部可以操作函數外部的數據,並且這些數據不會隨着函數的結束而被銷燬。

像數組、字符串、動態分配的內存等都是一系列數據的集合,沒有辦法通過一個參數全部傳入函數內部,只能傳遞它們的指針,在函數內部通過指針來影響這些數據集合。
而對於整數、小數、字符等基本類型數據的操作有時也必須要藉助指針,比如交換兩個變量的值


#include <stdio.h>
void swap(int *p1, int *p2){
    int temp;  //臨時變量
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
int main(){
    int a = 66, b = 99;
    swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

實際上將a和b的地址重新拷貝然後分別給p1,p2.這樣*p1,*p2就是提取地址上的數據,進行數據交換。函數運行結束後p1,p2銷燬,但是外部地址上的數據已經交換了。

數組作爲函數參數

數組是一系列數據的集合,無法通過參數將它們一次性傳遞到函數內部,如果希望在函數內部操作數組,必須傳遞數組指針。

定義max_and_min函數找到數組中的最大最小值

#include <stdio.h>
#include <stdlib.h>
void max_and_min(int * arr, int *max, int *min, int len) {
	*max = *min = arr[0];
	for (int i = 1; i < len; ++i) {
		if (*max < arr[i]) *max = arr[i];
		else if (*min > arr[i]) *min = arr[i];
	}
}
int main(){
	int max = 0, min = 0;
	int num[6] = { 1, 2, 3,0, -1, 2 };
	max_and_min(num, &max, &min, 6);
	printf("Max value is %d!\n", max);
	printf("Min value is %d!\n", min);


	system("pause");
	return 0;

	

}

out:

Max value is 3!
Min value is -1!

也可以採用:
void max_and_min(int arr[6], int *max, int *min, int len)
void max_and_min(int arr[], int *max, int *min, int len)

兩者都不會創建一個數組出來,編譯器也不會爲它們分配內存,實際的數組是不存在的,它們最終還是會轉換爲int *arr這樣的指針。這就意味着,兩種形式都不能將數組的所有元素都傳遞進來,大家還得使用數組指針

不管使用哪種方式傳遞數組,都不能在函數內部求得數組長度,因爲 intArr 僅僅是一個指針,而不是真正的數組,所以必須要額外增加一個參數來傳遞數組長度。

C語言爲什麼不允許直接傳遞數組的所有元素,而必須傳遞數組指針呢?

參數的傳遞本質上是一次賦值的過程,賦值就是對內存進行拷貝。所謂內存拷貝,是指將一塊內存上的數據複製到另一塊內存上。
對於像 int、float、char 等基本類型的數據,它們佔用的內存往往只有幾個字節,對它們進行內存拷貝非常快速。而數組是一系列數據的集合,數據的數量沒有限制,可能很少,也可能成千上萬,對它們進行內存拷貝有可能是一個漫長的過程,會嚴重拖慢程序的效率,所以,C語言沒有從語法上支持數據集合的直接賦值。

除了C語言,C++、Java、Python 等其它語言也禁止對大塊內存進行拷貝,在底層都使用類似指針的方式來實現。

10 函數返回指針

C語言支持函數返回指針
定義:
typename * functionName()

但是使用指針作爲函數返回值需要注意一點,函數運行結束後會銷燬內部定義的所有局部數據,包括局部變量,局部數組和形式參數,所以函數返回的指針儘量不要指向這些數據。否則可能會在後續使用過程中可能會引發意向不到的錯誤。

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

當指向這段代碼時,n將不是100,在函數結束後,n失去對該塊內存的使用權限,程序的其他代碼可以任意使用該塊內存,因而導致代碼出錯。

而如果實在函數內定義static靜態變量,則可以,因爲其存儲在靜態數據區,並不想局部變量一樣存儲在棧區,不會因爲函數結束就銷燬其內存區域。因而可以正常返回其地址。
將代碼中的int n = 100;改爲static int n = 100;即可。

11.二級指針

定義:
typename **p1;

在這裏插入圖片描述
代碼:

int a =100;
int *ip1 = &a;
int **ip2 = &ip1;

12. 二維數組

二維數組在概念上是二維的,有行和列,但在內存中所有的數組元素都是連續排列的
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

在內存中,a 的分佈是一維線性的,整個數組佔用一塊連續的內存:
在這裏插入圖片描述

C語言中的二維數組是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最後存放 a[2] 行;每行中的 4 個元素也是依次存放。數組 a 爲 int 類型,每個元素佔用 4 個字節,整個數組共佔用 4×(3×4) = 48 個字節。

C語言允許把一個二維數組分解成多個一維數組來處理。對於數組 a,它可以分解成三個一維數組,即 a[0]、a[1]、a[2]。每一個一維數組又包含了 4 個元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。

假設數組 a 中第 0 個元素的地址爲 1000,那麼每個一維數組的首地址如下圖所示
在這裏插入圖片描述

先看一下以下定義:
int (*p)[4] = a;

括號中的*表明 p是一個指針,它指向一個數組,數組的類型爲int [4],這正是a所包含的每個一維數組的類型

注意:
[ ]的優先級高於*( )是必須要加的,如果赤裸裸地寫作int *p[4],那麼應該理解爲int *(p[4]),p 就成了一個指針數組,而不是二維數組指針。

對指針進行加法(減法)運算時,它前進(後退)的步長與它指向的數據類型有關,p 指向的數據類型是int [4],那麼p+1就前進 4×4 = 16 個字節,p-1就後退 16 個字節,這正好是數組 a 所包含的每個一維數組的長度。也就是說,p+1會使得指針指向二維數組的下一行,p-1會使得指針指向數組的上一行。

數組名a在表達式中也會內轉換爲和p等價的指針
訪問二維數組:

  1. p指向數組 a 的開頭,也即第 0 行;p+1前進一行,指向第 1 行。

  2. *(p+1)表示取地址上的數據,也就是整個第 1 行數據。注意是一行數據,是多個數據,不是第 1 行中的第 0 個元素

	int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
	int(*p)[4] = a;
	printf("%d\n", sizeof(*(p + 1)));
	printf("%d\n", sizeof(*(a + 1)));
	

out:

16
16

3)*(p+1)+1表示第 1 行第 1 個元素的地址

*(p+1)單獨使用時表示的是第 1 行數據,放在表達式中會被轉換爲第 1 行數據的首地址,也就是第 1 行第 0 個元素的地址;就像一維數組的名字,在定義時或者和 sizeof、& 一起使用時才表示整個數組,出現在表達式中就會被轉換爲指向數組第 0 個元素的指針

	int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
	int(*p)[4] = a;
	//printf("%d\n", sizeof(*(p + 1)));
	//printf("%d\n", sizeof(*(a + 1)));
	printf("%#x\n", *(p + 1));
	printf("%#x\n", *(p + 1) + 1);
	
	printf("%#x\n", *(p + 2));

out:

0xddfb44
0xddfb48
0xddfb54

4)((p+1)+1)表示第 1 行第 1 個元素的值

根據以上結論,可以退出一下等價關係:

a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)

二維數組遍歷方式:

	int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
	int(*p)[4] = a;
	int i, j;
	//下標訪問
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 4; j++) printf("%2d  ", a[i][j]);
		printf("\n");
	}
	//指針訪問1
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 4; j++) printf("%2d  ", a[i][j]);
		printf("\n");
	}
	//指針訪問2

	for (i = 0; i < 3; i++) {
		for (j = 0; j < 4; j++) printf("%2d  ", *(*(p+i)+j));
		printf("\n");
	}
	//指針訪問3
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 4; j++) printf("%2d  ", *(p[i] + j));
		printf("\n");
	}

13.函數指針

函數在內存中佔據一段連續的內存區域,函數名在表達式中也被轉換爲函數所在內存區域的首地址,和數組名類似。可以將函數的首地址賦值給指針變量,然後可以通過指針變量就可以調用該函數,這種指針稱爲函數指針。

定義:

returnType (*pointerName)(param list);
returnType 爲函數返回值類型,pointerNmae 爲指針名稱,param list 爲函數參數列表。參數列表中可以同時給出參數的類型和名稱,也可以只給出參數的類型,省略參數的名稱,這一點和函數聲明類似,但是注意指針參數列表和要指向的函數參數列表要完全一樣

int add(int, int);
int main() {
	int a = 10, b = 5;
	int(*padd)(int, int) = add;
	int addition = (*padd)(a, b);
	printf("addition of a and b:%d", addition);
	system("pause");
	return 0;

	

}

int add(int a , int b) {
	return a + b;
}

out:

addition of a and b:15

14 總結:

常見指針變量的定義

15 動態分配內存

c
  • malloc(size) 分配內存
  • free() 釋放指針

size=nsizeof(typename)size = n * sizeof(typename)
比如要分配長度16的double數組:

int n = 16;
	double *arr = NULL;//聲明,並指向NULL,避免野指針
	arr = (double *)malloc(n * sizeof(double));
	if (!arr) {
		printf("分配失敗!!!");
	}
	//賦值
	for (int i = 0; i < 16; i++) {
		arr[i] = i;
	}
	for (int i = 0; i < 16; i++) {
		printf("%f\n", arr[i]);
	}
	free(arr);//釋放指針

c++
  • new 動態申請
  • delete 釋放內存

在c++中,malloc函數依然存在,但是推薦使用new關鍵字,因爲與malloc相比,new不僅分配了內存,還創建了對象。

  • 指針變量名=new 類型(初始化)

  • delete 指針名
    new表達式的操作序列爲:在自有存儲區(棧區)爲對象或變量分貝內存,然後使用括號中的值初始化該對象或變量。 new表達式是調用庫中操作符new()完成的
    比如int *pi=new int(0);
    而當pi指向的對象或變量的聲明週期結束時,需要釋放pi所指向的目標所佔用的內存空間:
    delete pi;
    注意的是,釋放了pi所指向的內存空間,但是並未釋放指針pi本身,也就是說pi本省所佔用的內存空間並未釋放。
    c++ vs有保護機制

幾種初始化方式:

//1
int *p=NULL;
p=new int(100);
//2
int *p=new int;
*p=100;
//3
int *p=new int[100];
關於數組delete釋放需要注意:

一次只能釋放申請的一維的數組地址,如果是二維數組,要先釋放列在釋放行

  1. 一維數組的的申請釋放
    指針變量名=new 類型名[長度];
    delete []指針變量名
     int n;
	char *pc;
	cout << "請輸入動態數組的元素個數" << endl;
	cin >> n;
	pc = new char[n];//申請25個字符,可以裝下12個漢字和1個結束符
	strcpy(pc, "自由存儲區內存的動態分配");
	cout << pc << endl;
	delete[]pc;
  1. 二維動態數組的創建和釋放
    加上變量名理解!!!
int **array
// 假定數組第一維長度爲 m, 第二維長度爲 n

// 動態分配空間
array = new int *[m];//分配m行的內存空間,指向各行的指針數組,每一個元素指向一行
for( int i=0; i<m; i++ )
{
    array[i] = new int [n] ;//爲每一行分配內存空間
}
//賦值
for(int i=0;i<m;++i)
	for(int j=0;j<n;++j){
	array[i][j] = i * n + j;
	} 

//顯示
for(int i=0;i<m;++i){
	for(int j=0;j<n;++j){
		cout<<array[i][j] <<' ';
	}
	cout<<endl;
 }
	

//釋放
for( int i=0; i<m; i++ )
{
    delete [] arrar[i];
}
delete [] array;

指向結構體/類:

struct Share {
	long int share_price;
	string time_date;
};


int main()
{
    //動態創建結構體指針
	Share *pS = new Share;
	pS->share_price = 100;
	pS->time_date = "2017_09_10 09:10";

	//定義結構體數組
	Share *pSa = new Share[2];
	//賦值
	for (int i = 0; i < 2; ++i) {
		pSa->share_price = (i + 1) * 100;
		pSa->time_date = "2017_09_10";
	}
	}

參考:

  1. 指針 http://c.biancheng.net/cpp/html/87.html
  2. 動態分配內存https://blog.csdn.net/qq_29924041/article/details/54897204
  3. http://www.runoob.com/cplusplus/cpp-dynamic-memory.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章