天天看點

C++數組、指針與字元串(三)C++數組、指針與字元串

C++數組、指針與字元串

1.指針通路控制數組元素

定義指向數組元素的指針

定義與指派

例:int a[10],*pa;
    pa=&a[0];或pa=a;
           

等效的形式

*pa就是a[0], (pa+1)就是a[1],…, *(pa+i)就是a[i]

a[i],*(pa+i), *(a+i) pa[i]都是等效的。

2.指針數組

指針數組:數組的元素是指針類型

例:Point *pa[2];
由pa[0]、pa[1]兩個指針組成
           
例:利用指針數組存放矩陣
#include"iostream"
using namespace std;
int main() {
	int line1[] = { 1,0,0 };//矩陣的第一行
	int line2[] = { 0,1,0 };//矩陣的第二行
	int line3[] = { 0,0,1 };//矩陣的第三行

	//定義整型指針數組并初始化
	int* pLine[3] = { line1,line2,line3 };
	cout << "Matrix test:" << endl;
	//輸出矩陣
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
			cout << pLine[i][j] << " ";
		cout << endl;
	}
	return 0;
}
運作結果:
Matrix test:
1 0 0
0 1 0
0 0 1
           

3.以指針作為函數參數

為什麼需要用指針做參數?

需要資料雙向傳遞時(引用也可以達到效果)

需要傳遞一組資料,隻傳首位址運作效率比較高

例:讀入三個浮點數,将整數部分和效數部分分别輸出。
#include"iostream"
using namespace std;

//将整數x分成整數部分和小數部分,形參intpart、fracpart是指針
void splitFloat(float x, int* intPart, float* fracPart) {
	*intPart = static_cast<int>(x);//取x的整數部分
	*fracPart = x - *intPart;//取x的小數部分
}

int main() {
	cout << "Enter 3 float point number:" << endl;
	for (int i = 0; i < 3; i++) {
		float  x, f;
		int n;
		cin >> x;
		splitFloat(x, &n, &f);//變量位址作為實參
		cout << "Integer Part =" << n << "Fraction Part=" << f << endl;
	}
	return 0;
}
運作結果:
Enter 3 float point number:
112.5 16.92 13.65
Integer Part =112Fraction Part=0.5
Integer Part =16Fraction Part=0.92
Integer Part =13Fraction Part=0.65

           
例:指向常量的指針做形參
#include"iostream"
using namespace std;
const int N = 6;
void print(const int* p, int n);
int main() {
	int array[N];
	for (int i = 0; i < N; i++)
		cin >> array[i];
	print(array, N);
	return 0;
}
void print(const int* p, int n) {
	cout << "{" << *p;
	for (int i = 1; i < n; i++)
		cout << "," << *(p + i);
	cout << "}" << endl;
}
           

4.指針類型的函數

指針函數的定義形式:

存儲類型 資料類型 *函數名(){

//函數體語句

}

注意:不要将非靜态局部位址用作函數的傳回值。

例:在子函數中定義局部變量後将其位址傳回給主函數,就是非法位址。

int main(){
  int* function();
  int* ptr=function();
  *prt=5;//危險的通路
  return 0;
}
int* function(){
  int local=0;//非靜态局部變量作用域和壽命都僅限于本函數體内
  return &local;
}//函數運作結束時,變量local被釋放
           

注意:傳回的指針要確定在主調函數中是有效、合法的位址。

例:主函數中定義的數組,在子函數中對照該數組元素進行某種操作,傳回其中一個元素的位址,着就是合法有效的位址。
#include"iostream"
using namespace std;
int main() {
	int array[10];//主函數中定義的數組
	int* search(int* a, int num);
	for (int i = 0; i < 10; i++)
		cin >> array[i];
	int* zeroptr = search(array, 10);//将主函數中數組的首位址傳給子函數
	return 0;
}
int* search(int* a, int num) {//指針a指向主函數中定義的數組
	for (int i = 0; i < num; i++)
		if (a[i] == 0)
			return &a[i];//傳回的位址指向的元素是主函數中定義的
}//函數運作結束時,a[i]的位址仍有效

           
正确例子:在子函數中通過動态記憶體配置設定new操作取得的記憶體位址傳回給主函數是合法有效的,但是記憶體配置設定和釋放不再同一級别,要注意不要忘記釋放,避免記憶體洩漏。
#include"iostream"
using namespace std;
int main() {
	int* newintvar();
	int* intptr = newintvar();
	*intptr = 5;//通路合法有效位址
	delete intptr;//如果忘記釋放,會造成記憶體洩漏
	return 0;
}
int* newintvar() {
	int* p = new int();
	return p;//傳回的位址指向的是動态配置設定空間
}//函數運作結束時,p中的位址仍然有效
           

5.指向函數的指針

函數指針的定義

定義形式

存儲類型 資料類型 (*函數指針名)();

含義

函數指針指向的是程式代碼存儲區。

函數指針的典型用途——實作函數回調

通過函數指針調用的函數

例如将函數的指針作為參數傳遞給一個函數,使得在處理相似事件的時候可以靈活的使用不同的方法。

調用者不關心誰是被調用者

​ 需要知道一個具有特定原型和限制條件的被調用函數。

函數指針舉例:
編寫一個計算函數compute,對兩個整數進行各種計算。有一個形參為指向具體算法函數的指針,根據不同的實參函數,用不同的算法進行計算。
編寫三個函數:求兩個 整數的最大值、最小值、和。分别用這三個函數作為實參,測試compute函數
#include"iostream"
using namespace std;

int compute(int a, int b, int(*func)(int, int))
{
	return func(a, b);
}
int max(int a, int b)//求最大值
{
	return ((a > b) ? a : b);
}
int min(int a, int b)//求最小值
{
	return ((a < b) ? a : b);
}
int sum(int a, int b)//求和
{
	return a + b;
}
int main() {
	int a, b, res;
	cout << "請輸入整數a:"; cin >> a;
	cout << "請輸入整數b: "; cin >> b;

	res = compute(a, b, &max);
	cout << "Max of" << a << "and" << b << "is" << res << endl;
	res = compute(a, b, &min);
	cout << "Min of" << a << "and" << b << "is" << res << endl;
	res = compute(a, b, &sum);
	cout << "Sum of" << a << "and" << b << "is" << res << endl;
}
運作結果:
請輸入整數a:30
請輸入整數b: 25
Max of30and25is30
Min of30and25is25
Sum of30and25is55
           

6.對象指針

對象指針定義形式

類名 *對象指針名;

例:Point a(5,10);
       Point *ptr;
       ptr=&a;
           

通過指針通路對象成員

對象指針名->成員名

例子:

ptr->getx()相當于(*ptr).getx();

例:使用指針來通路Point類的成員
#include"iostream"
using namespace std;
class Point {
public:
	Point(int x = 0, int y = 0) :x(x), y(y) {}
	int getX() const { return x; }
	int getY() const { return y; }
private:
	int x, y;
};
int main() {
	Point a(4, 5);
	Point* p1 = &a;//定義對象指針,用a的位址初始化
	cout << p1->getX()<< endl;//用指針通路對象成員
	cout << a.getX()<< endl;//用對象名通路對象成員
	return 0;
}
           

this指針

隐含與類的每一個非靜态成員函數中。

指出成員函數所操作的對象。

當通過一個對象調用成員函數時,系統先将該對象的位址賦給this指針,然後調用成員函數,成員函數對對象的資料成員進行操作時,就隐含使用this指針。

例如:Point類的getX函數中的語句:
return x;
相當于:
return this->x;
           

7.動态配置設定與釋放記憶體

動态申請記憶體操作符new

new 類型名T (初始化參數清單)

功能:在程式執行期間,申請用于存放T類型對象的記憶體空間,并依初值清單賦以初值。

結果值:

成功:T類型指針,指向新配置設定的記憶體; 失敗:抛出異常。

釋放記憶體操作符delete

delete指針p

功能:釋放指針p所指向的記憶體。p必須是new操作的傳回值。

例:動态建立對象舉例
#include"iostream"
using namespace std;
class Point {
public:
	Point() :x(0), y(0) {
		cout << "Default Constructor called." << endl;
	}
	Point(int x, int y) :x(x), y(y) {
		cout << "construct ralled." << endl;
	}
	~Point() { cout << "Destructor called." << endl; }
	int getX() const { return x; }
	int getY() const { return y; }
	void move(int newX, int newY) {
		x = newX;
		y = newY;
	}
private:
	int x, y;
};
int main() {
	cout << "Step one:" << endl;
	Point* ptr1 = new Point;//調用構造函數
	delete ptr1;//s删除對象,自動調用析構函數

	cout << "Step two:" << endl;
	ptr1 = new Point(1, 2);
	delete ptr1;
	return 0;
}
運作結果:
Step one :
Default Constructor called.
Destructor called.
Step two :
construct ralled.
Destructor called.
           

8.申請和釋放動态數組(一)

配置設定和釋放動态數組

配置設定:new 類型名T[數組長度]

數組長度可以是任何整形類型表達式,在運作時計算

釋放:delete[] 數組名p

釋放指針p所指向的數組

p必須是用new配置設定得到的數組首位址。

例:動态建立對象數組
#include "iostream"
using namespace std;
class Point {//類的聲明
public:
	Point() : x(0), y(0) {
		cout << "Default Constructor called." << endl;
	}
	Point(int x, int y) :x(x), y(y) {
		cout << "construct ralled." << endl;
	}
	~Point() { cout << "Destructor called." << endl; }
	int getX() const { return x; }
	int getY() const { return y; }
	void move(int newX, int newY) {
		x = newX;
		y = newY;
	}
private:
	int x, y;
};
int main() {
	Point* ptr = new Point[2];//建立對象數組
	ptr[0].move(5, 10);//通過指針通路數組元素的成員
	ptr[1].move(15, 20);//通過指針通路數組元素成員
	cout << "Deleting..." << endl;
	delete[] ptr;//删除整個對象數組
	return 0;
}
運作結果:
Default Constructor called.
Default Constructor called.
Deleting...
Destructor called.
Destructor called.
           

動态建立多元數組

new  類型名T[第一維長度][第二維長度]....;
           

如果記憶體申請成功,new運算傳回一個指向新配置設定記憶體首位址的指針。

例如:
char (*fp)[3];
fp = new char[2][3];
           
例:動态建立多元數組
#include"iostream"
using namespace std;
int main() {
	int(*cp)[9][8] = new int[7][9][8];
	for (int i = 0; i < 7; i++) 
		for (int j = 0; j < 9; j++) 
			for (int k = 0; k < 8; k++)
				*(*(*(cp + i) + j) + k) = (i * 100 + j * 10 + k);
			for (int i = 0; i < 7; i++) {
				for(int j = 0;j < 9; j++) {
				    for (int k = 0; k < 8; k++)
					    cout << cp[i][j][k] << " ";
				     cout << endl;
			}
			cout << endl;
		}
		delete[] cp;
		return 0;
	}
           

9.智能指針

c++11的智能指針

unique_ptr:

不允許多個指針共享資源,可以用标準庫中的move函數轉移指針

share_ptr:

多個指針共享資源

weak_ptr:

可複制shared_ptr,但其構造或者釋放對資源不産生影響

10.VECTOR對象

vector标準庫的類模版

VECTOR對象的定義

vector<元素類型> 數組對象名(數組長度);

例:
vector<int> arr(5)
建立大小為5的int數組
           

vector對象的使用

對數組元素的引用

與普通數組具有相同形式:

vector對象名 [下标表達式]

vector數組對象名不表示數組首位址

獲得數組長度

用size函數

vector對象名.size()

例:vector舉例
#include"iostream"
#include"vector"
using namespace std;
//計算數組arr中元素的平均值
double average(const vector<double>& arr) {
	double sum = 0;
	for (unsigned i = 0; i < arr.size(); i++)
		sum += arr[i];
	return sum / arr.size();
}
int main() {
	unsigned n;
	cout << "n=";
	cin >> n;

	vector<double>arr(n);//建立數組對象
	cout << "Please input" << n << "real numbers:" << endl;
	for (unsigned i = 0; i < n; i++)
		cin >> arr[i];

	cout << "Average=" << average(arr) << endl;
	return 0;
}
           
例:基于範圍for循環配合auto舉例
#include<vector>
#include<iostream>
int main()
{
	std::vector<int>v = { 1,2,3 };
	for (auto i = v.begin(); i != v.end(); ++i)
		std::cout << *i << std::endl;
	for (auto e : v)
		std::cout << e << std::endl;
}
           

11.深層複制與淺層複制

淺層複制

實作對象間資料元素的一一對應複制

深層複制

當被複制的對象資料成員是指針類型時,不是複制該指針成員本身,而是将指針所指對象進行複制

12.移動構造

移動構造

C++11标準中提供了一種新的構造方法——移動構造

C++11之前,如果要将源對象的狀态轉移到目标對象隻能通過複制。在某些情況下,沒有必要複制對象——隻需要移動他們

C++11引入移動語義:

源對象資源的控制權全部交給目标對象

移動構造函數

class_name(class_name &&)

例:函數傳回含有指針成員的對象(使用複制構造)
#include"iostream"
using namespace std;
class IntNum {
public:
	IntNum(int x = 0) : xptr(new int(x)) {//構造函數
		cout << "Calling constructor..." << endl;
	}
	IntNum(const IntNum& n) :xptr(new int(*n.xptr)) {//複制構造函數
		cout << "Calling copy constructor..." << endl;
	};
	~IntNum() {//析構函數
		delete xptr;
		cout << "Destructing..." << endl;
	}
	int getInt() { return *xptr; }
private:
	int* xptr;
};
//傳回值為IntNum類對象
IntNum getNum() {
	IntNum a;
	return a;
}
int main() {
	cout << getNum().getInt() << endl;
	return 0;
}
運作結果:
Calling constructor...
Calling copy constructor...
Destructing...
0
Destructing...

//使用移動構造
#include "iostream"
using namespace std;
class IntNum {
public:
	IntNum(int x = 0) : xptr(new int(x)) {//構造函數
		cout << "Calling constructor..." << endl;
	}
	IntNum(const IntNum& n):xptr(new int(*n.xptr)) {//複制構造函數
		cout << "Calling copy constructor..." << endl;
	}
	IntNum(IntNum&& n) :xptr(n.xptr) {//移動構造函數
		n.xptr = nullptr;
		cout << "Calling move constructor..." << endl;
	}
	~IntNum() {//析構函數
		delete xptr;
		cout << "Destructing..." << endl;
	}
	int getInt() { return *xptr; }
private:
	int* xptr;
};
//傳回值為IntNum類對象
IntNum getNum() {
	IntNum a;
	return a;
}
int main() {
	cout << getNum().getInt() << endl;
	return 0;
}
運作結果:
Calling constructor...
Calling move constructor...
Destructing...
0
Destructing...

           

13.C風格字元串

字元串常量

例:“program”

各字元連續、順序存放,每個字元占一個位元組,以‘\0’結尾,相當于一個隐含建立的字元常量數組

“program”出現在表達式中,表示這一char數組的手位址

首位址可以賦給char常量指針:

const char *STRING1=“program”

用字元數組存儲字元串(C風格字元串)

例如:
char str[8]={'p','r','o','g','r','a','m','\0'};
char str[8]="program";
char str[]="program";
           

用字元數組表示字元串的缺點

執行連接配接、拷貝、比較等操作,都需要顯式調用庫函數,當字元串長度不确定時,需要用new動态建立字元數組,最後要用delete釋放,字元串事迹長度大于它配置設定的空間時,會産生數組下标越界的錯誤。

14.string類

string類常用的構造函數

string();//預設構造函數,建立一個長度為0的串

string(const char *s);//用指針s所指向的字元串常量初始化string對象

string(const string& rhs);//複制構造函數

例: string類應用舉例
#include"string"
#include"iostream"
using namespace std;

//根據value的值輸出true或false
//title為提示文字
inline void test(const char* title, bool value)
{
	cout << title << "returns" << (value ? "ture" : "false") << endl;
}
int main() {
	string s1 = "DEF";
	cout << "s1 is" << s1 << endl;
	string s2;
	cout << "Please enter s2:";
	cin >> s2;
	cout << "length of s2:" << s2.length() << endl;

	//比較運算符的測試
	test("s1<=\"ABC\"", s1 <= "ABC");
	test("\"DEF\"<=s1", "DEF" <= s1);

	//連接配接運算符的測試
	s2 += s1;
	cout << "s2=s2+s1:" << s2 << endl;
	cout << "length of s2:" << s2.length() << endl;
	return 0;
}
           

輸入整行字元串

getline可以輸入整行字元串(要包含string頭檔案)。

例如:
getline(cin,s2);
           

輸入字元串時,可以使用其它分隔符作為字元串結束的标志(例如逗号、分号),将分隔符作為getline的第3個參數即可。

例如:
getline(cin,s2,',');
           
例:用getline輸入字元串
#include"iostream"
#include"string"
using namespace std;
int main() {
	for (int i = 0; i < 2; i++){
		string city, state;
	    getline(cin, city, ',');
		getline(cin, state);
		cout << "City:" << city << "State:" << state << endl;
	}
	return 0;
}
運作結果:
Beijing, China
City : BeijingState:China

San Francisco, the Ynited States
City :
San FranciscoState : the Ynited States
           

繼續閱讀