[C++基礎] 數組、指針、內存篇

1、數組

2.1 int a[2][2]= { {1}, {2,3} },則 a[0][1] 的值是多少?

二維數組的初始化通常有兩種方式:c++

  • 第一種方式是按行來執行,如int array\[2][3]= { {0,0,1}, {1,0,0} };
  • 而第二種方式是把數值寫在一塊,如int array\[2][3]= { 0,0,1,1,0,0 };

<span style="color:blue">若只對部分元素進行初始化,數組中未賦值的元素自動爲賦值爲 0,</span>因此 a[0][1] 的值是0。程序員

<br />編程

2.2 a是數組,(int*)(&a+1) 表示什麼意思?

表示 int 類型的數組指針,若 a 爲數組 a[5],則(int*)(&a+1)爲 a[5]。數組

示例程序以下:安全

#include <stdio.h> 

void main()
{
	int a[5]={1,2,3,4,5};
	int b[ 100];
	int *ptr=(int*)(&a+1); 
	printf("%d %d\n",*(a+1),*(ptr-1));  // 2 5
	printf("sizeof(b)=%d\n",sizeof(b)); // sizeof(b)=400
	printf("sizeof(&b)=%d\n",sizeof(&b)); // sizeof(&b)=4
}

<span style="color:blue">&a 是數組指針,是一個指向 int (*)[5] 的指針,因此 &a+1 的地址是 &a 地址再加 5*sizeof(int),它的運算單位是 int(*)[5]。通過類型轉換後,ptr 至關於 int *[5]。</span>數據結構

ptr-1 的單位是 ptr 的類型,所以 ptr-1 的位置恰好是 a[4]。由於 ptr 與 (&a+1) 類型是不同的,因此 ptr-1 只會減去 sizeof(int*)。函數

<span style="color:blue">值得注意的是,a 和 &a 的地址是同樣的,但意思不同,a 是數組首地址,也就是 a[0] 的地址;&a 是 對象(數組)首地址;a+1 是數組下一元素的地址,即 a[1];而 &a+1 是下一個對象的地址, 即 a[5]。</span>優化

<br />編碼

2.3 不使用流程控制語句,如何打印出1 ~ 1000的整數?

採用構造函數與靜態構造變量結合的方法實現。<span style="color:darkOrange">首先在類中定義一個靜態成員變量,而後在構造函數裏面打印該靜態變量的值,並對靜態變量進行自增操做,同時在主函數裏面定義一個類數組,</span>程序代碼示例以下:spa

class print
{
public:
	static int a;

	print()
	{
		printf("%d\n",print::a);
		a++;
	}
};
int print::a = 1;

int main()
{
	print tt[100]; 
	
	return 0;
}

<br />

2.4 int id[sizeof(unsigned long)]; 這個對嗎?爲何?

答案:正確。<span style="color:darkOrange">這個 sizeof 是編譯時運算符,編譯時就肯定了 ,能夠當作和機器有關的常量。</span>

<br />

擴展: 如下代碼可以編譯經過嗎,爲何?

const int size1 = 2;
char str1[size1];

int temp = 0;
const int size2 = temp;
char str2[size2];

<span style="color:blue">str1 能經過編譯,而 str2 定義出錯,size2 非編譯器期間常量,而數組定義要求長度必須爲編譯期常量。</span>

<br />

2、指針

1 使用指針有哪些好處?

通常而言,使用指針有如下幾個方面的好處:

(1)<span style="color:blue">能夠動態分配內存;</span>

(2)<span style="color:blue">進行多個類似變量的通常訪問;</span>

(3)<span style="color:blue">爲動態數據結構,尤爲是樹和鏈表,提供支持;</span>

(4)遍歷數組,如解析字符串;

(5)高效地按引用 「複製」 數組與結構,特別是做爲函數參數的時候,能夠按照引用傳遞函數參數,提升開發效率。

<br />

2 引用和指針的區別?

(1)<span style="color:blue">引用只能在定義時被初始化一次,以後不能被改變,即引用具備「從一而終」的特性。而指針倒是可變的;</span>

(2)<span style="color:blue">引用使用時不須要解引用(*),而指針須要解引用;</span>

(3)<span style="color:blue">引用不能夠爲空,而指針能夠爲空;</span>

(4)<span style="color:blue">對引用進行 sizeof 操做獲得的是所指向的變量(對象)的大小,而對指針進行 sizeof 操做獲得的是指針自己(所指向的變量或對象的地址)的大小;</span>

(5)做爲參數傳遞時,二者不一樣。引用傳遞參數是 「引用傳遞」,會經過一個間接尋址的方式操做到主調函數中的相關變量。指針傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。

<br />

3 指針和數組是否表示同一律念

從原理與定義上看,雖然指針與數組表示的是不一樣的概念,但指針卻能夠方便地訪問數組或者模擬數組,二者存在着一種貌似等價的關係,但也存在着諸多不一樣之處, 主要表如今如下兩個方面:

(1)<span style="color:blue">修改方式不一樣。</span>

例如,char a[] = 「hello」,能夠經過取下標的方式對其元素值進行修改。例如,a[0] = ‘X’是正確的,而對於char *p = 「world」,此時 p 指向常量字符串,因此p[0] = ‘X’是不容許的, 編譯會報錯。

(2)<span style="color:blue">所佔字節數不一樣。</span>

例如,char *p = 「world」,p 爲指針,則sizeof(p)獲得的是一個指針變量的字節數,而不是 p 所指的內存大小。而 sizeof 數組,獲得的是數組所佔的內存大小。

<br />

4 複雜聲明?

void * ( * (*fp1)(int))[10];

float (*(* fp2)(int,int,int))(int);

int (* (* fp3)())[10]();

分別表示什麼意思?

  • fp1 是一個指針,指向一個函數,這個函數的參數爲 int 型,函數的返回值是一個指針,這個指針指向一個數組,這個數組有 10 個元素,每一個元素是一個 void* 型指針;
  • fp2 是一個指針,指向一個函數,這個函數的參數爲 3 個 int 型,函數的返回值是一個指針,這個指針指向一個函數,這個函數的參數爲 int 型,函數的返回值是 float 型;
  • fp3 是一個指針,指向一個函數,這個函數的參數爲空,函數的返回值是一個指針,這個指針指向一個數組,這個數組有 10 個元素,每一個元素是一個指針,指向一個函數,這個函數的參數爲空,函數的返回值是 int 型。

<br />

5 const 指針?

const char *p1; // 常量指針 — 指向「常量」的指針
char * const p2; // 指針常量 — 指針類型的常量

說明上面兩種描述的區別;

(1)p1 本質上是一個指向 const char 的指針,能夠改變指向,可是指向的值是不能改變的;

(2)p2 本質上是一個 const char 指針類型的常量,不能夠改變指向,可是指向的值能夠改變。

<br />

3、內存

1 內存分配方式有幾種(並加以描述)?

內存分配方式有三種:

(1)<span style="color:blue">從靜態存儲區域分配。</span>內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。

(2)<span style="color:blue">在棧上分配。</span>在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。

(3)<span style="color:blue">從堆上分配,亦稱動態內存分配。</span>程序在運行的時候用malloc 或new 申請任意多少的內存,程序員本身負責在什麼時候用free 或delete 釋放內存。動態內存的生存期由程序員決定,使用很是靈活,但問題也最多。

<span style="color:orange">棧是向下增加的,堆是向上增加的。</span>程序示例以下:

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

int global=0; // 靜態存儲區
char *pl; // 靜態存儲區

int main()
{
	int a; // 棧
	char s[]="abcdefg"; // 棧
	char *p2; // 棧
	char *p3="123456789"; // p3在棧上,"123456789"在常量區
	static int c=0; // 靜態存儲區
	pl=(char *)malloc(100); // 分配而來的100B的區域就在堆中 
	strcpy(p1,"123456789") // "123456789"放在常量區,編譯器可能會將它與p3所指向的"123456789"優化成一個地方

	return 0;
}

<br />

2 棧空間和堆空間的最大值分別是多少?

在 Windows下,<span style="color:blue">棧是向低地址擴展的數據結構,是一塊連續的內存的區域。棧頂的地址和棧的最大容量是系統預先規定好的,在 Windows 下,棧的大小是 2MB;而Linux默認棧空間大小爲8MB,</span>能夠經過命令 ulimit -s 來設置。

<span style="color:blue">堆是向高地址擴展的數據結構,是不連續的內存區域。 這是因爲系統是用鏈表來存儲空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址的,</span>而堆的大小受限於計算機系統中有效的虛擬內存。

Linux 虛擬地址空間內核佔 1 GB,留給用戶進程 3GB,Windows 是各佔2GB,用戶空間也是用戶進程最大的堆申請數量了。但考慮到程序自己大小,動態庫等因素,<span style="color:blue">實際的堆申請數量是達不到最大值的,Linux小於3GB,Windows小於2GB。</span>

<br />

3 堆和棧的區別?

堆棧空間分配

棧由操做系統自動分配釋放 ,存放函數的參數值,局部變量的值等。<span style="color:blue">其分配方式相似於數據結構中的棧。</span>

堆通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由操做系統回收。<span style="color:blue">其分配方式相似於數據結構中的鏈表。</span>

<br />

4 什麼是內存泄漏?

<span style="color:orange">所謂內存泄露是指因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存的狀況。</span>內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存後,由於設計錯誤,失去了對該段內存的控制,於是形成了內存的浪費。

<br />

例如,對指針進行從新賦值,程序代碼以下:

char *memoryArea = malloc(lO); 
char *newArea = malloc(lO); 
memoryArea = newArea;

對 memoryArea 的賦值會致使 memoryArea 以前指向的內容丟失,最終形成內存泄露。

<br />

再看一個例子:

char *fun()
{
    return malloc(10);
}

void callfun()
{
    fun();
}

上面程序就由於未能對返回值進行處理,最終致使內存泄露。

<br />

怎樣解決內存泄露?

在編碼過程當中養成良好的編程習慣,<span style="color:orange">用 malloc 或 new 分配的內存都應當在適當的時機用 free 或 delete 釋放,在對指針賦值前,要確保沒有內存位置會變爲孤立的,另外要正確處理使用動態分配的函數返回值。</span>

<br />

5 什麼是緩衝區溢出?

緩衝區是程序運行的時候機器內存中的一個連續塊,它保存了給定類型的數據,隨着動態分配變量會出現問題。<span style="color:blue">緩衝區溢出是指當向緩衝區內填充數據位數超過了緩衝區自身的容量限制時,發生的溢出的數據覆蓋在合法數據(數據、下一條指令的指針、函數返回地址等)上的狀況。</span>

程序示例以下:

#include <unistd.h> 

void Test()
{
	char buff[4]; 
	printf("Some input:"); 
	gets(buff); 
	puts(buff);
}

該程序的 Test() 函數中使用了標準的 C 語言輸入函數 gets(),因爲它沒有執行邊界檢查,最終會致使 Test() 函數存在緩衝區溢出安全漏洞。<span style="color:blue">Test() 函數的緩衝區最多隻能容納 3 個字符和一個空字符,因此超過 4 個字符就會形成緩衝區溢出。</span>

<br />

6 有關內存的思考題

思考題(一)

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}

void Test(void)
{
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

程序會崩潰。<span style="color:blue">由於 GetMemory 並不能傳遞動態內存,Test 函數中的 str 一都是 NULL。 </span>

<br />

思考題(二)

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

多是亂碼。<span style="color:blue">由於 GetMemory 返回的是指 「棧內存" 的指針,該指針的不是 NULL, 其原現的內容被清除,新內容不可知。</span>

<br />

思考題(三)

void GetMemory2(char **p, int num)
{
	*p = (char *)malloc(num);
}

void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

<span style="color:blue">可以輸出 "hello",但會形成內存泄漏,由於沒有經過 free 釋放內存。</span>

<br />

相關文章
相關標籤/搜索