c++基礎

jiuwen567發表於2024-03-29

多檔案結構和編譯預處理命令

C++程式的一般組織結構

•一個源程式可以劃分為多個原始檔:

  • 類宣告檔案(.h檔案)

  • 類實現檔案(.cpp檔案)

  • 類的使用檔案(main()所在的.cpp檔案)

外部變數

•在所有類之外宣告的函式(也就是非成員函式),都是具有檔案作用域的。

•這樣的函式都可以在不同的編譯單元中被呼叫,只要在呼叫之前進行引用性宣告(即宣告函式原型)即可。也可以在宣告函式原型或定義函式時用extern修飾,其效果與不加修飾的預設狀態是一樣的。

namespace

•使用匿名的名稱空間:在匿名名稱空間中定義的變數和函式,都不會暴露給其它的編譯單元。

  **namespace** { //匿名的名稱空間

 int n;

 void f() {

 n++;

 }

  }

•這裡被“namespace { …… }”括起的區域都屬於匿名的名稱空間。

訪問許可權

  • public:所有

  • private:本類,友元

  • protected:本類,派生類,友元

過載

  1. 過載函式引數要不同(型別,個數或者順序不同)

行內函數

  1. 為了提高執行效率
  2. 不要有複雜的結構(迴圈,swich語句)
  3. 將函式體放在類的宣告中
  4. 使用inline關鍵字
  5. 類結構中所在的類說明內部定義的函式是行內函數。

帶預設引數的函式

  1. 類中宣告的時候帶預設引數,具體實現的時候不帶

建構函式

  1. 函式名和類名完全相同
  2. 不寫建構函式時,系統會預設給一個無參的預設建構函式
  3. 個數大於等於一個

解構函式

  1. 物件銷燬時被呼叫
  2. 不能過載,有且只有一個

委託建構函式

  1. C++11 引入

複製(複製)建構函式

#include <iostream>
using namespace std;

class Point {	//Point 類的定義
public:		//外部介面
	Point(int xx = 0, int yy = 0) {	//建構函式
		x = xx;
		y = yy;
	}
	Point(const Point &p);	//複製建構函式
	int getX() {
		return x;
	}
	int getY() {
		return y;
	}
private:	//私有資料
	int x, y;
};

//成員函式的實現
Point::Point(const Point &p) {
	x = p.x;
	y = p.y;
	cout << "Calling the copy constructor" << endl;
}

//形參為Point類物件的函式
void fun1(Point p) {
	cout << p.getX() << endl;
}

//返回值為Point類物件的函式
Point fun2() {
	Point a(1, 2);//臨時物件
	return a;
}

//主程式
int main() {
	Point a(4, 5);	//第一個物件A
	Point b = a;	//情況一,用A初始化B。第一次呼叫複製建構函式
	cout << b.getX() << endl;
	fun1(b);		//情況二,物件B作為fun1的實參。第二次呼叫複製建構函式
	b = fun2();		//情況三,函式的返回值是類物件,函式返回時,呼叫複製建構函式,賦值(此時b不構造)
	cout << b.getX() << endl;
	cout << b.getY() << endl; 
	return 0;
}
  1. 函式返回物件時會增加一個臨時物件,增加開銷(情況三)

移動建構函式

c++11標準

左值:賦值語句左側的物件變數。
右值:賦值語句右側的值,不依附於物件。
float n=6;
float &lr_n=n;        //對變數n的左值引用
float &&rr_n=n;     //錯誤,不能將右值引用繫結到左值n上
float &&rr_n=n*n;  //將乘法結果左值繫結到左值引用
float &lr_n=n*n;  //錯誤,不能將左值引用繫結到右值n*n上
標準庫utility中宣告提供了move函式,將左值物件移動成為右值。
float n=10;
float &&rr_n = std::move(n);

default、delete函式(C++11標準)

組合

•類中的成員資料是另一個類的物件。

•可以在已有抽象的基礎上實現更復雜的抽象。

原則:不僅要負責對本類中的基本型別成員資料賦初值,也要對物件成員初始化。
宣告形式:
類名::類名(物件成員所需的形參,本類成員形參)
       :物件1(引數),物件2(引數),......
{  
//函式體其他語句
}

構造組合類物件時的初始化次序

•首先對建構函式初始化列表中列出的成員(包括基本型別成員和物件成員)進行初始化,初始化次序是成員在類體中定義的次序。

▫成員物件建構函式呼叫順序:按物件成員的宣告順序,先宣告者先構造。

▫初始化列表中未出現的成員物件,呼叫用預設建構函式(即無形參的)初始化

•處理完初始化列表之後,再執行建構函式的函式體。

eg類的組合,線段(Line)類

#include <iostream>
#include <cmath>
using namespace std;
//Point類省略
//類的組合
class Line {	//Line類的定義
public:	//外部介面
	Line(Point xp1, Point xp2);
	Line(Line &l);
	double getLen() { return len; }
private:	//私有資料成員
	Point p1, p2;	//Point類的物件p1,p2
	double len;
};
//組合類的建構函式
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
	cout << "Calling constructor of Line" << endl;
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
	len = sqrt(x * x + y * y);
}
Line::Line (Line &l): p1(l.p1), p2(l.p2) {//組合類的複製建構函式
	cout << "Calling the copy constructor of Line" << endl;
	len = l.len;
}
int main() {
	Point myp1(1, 1), myp2(4, 5);	//建立Point類的物件
	Line line(myp1, myp2); //建立Line類的物件
	Line line2(line);	//利用複製建構函式建立一個新物件
	cout << "The length of the line is: ";
	cout << line.getLen() << endl;
	cout << "The length of the line2 is: ";
	cout << line2.getLen() << endl;
	return 0;
}

前向引用宣告

class B;  //前向引用宣告
class A {
public:
  void f(B b);
};
class B {
public:
  void g(A a);
};

作用域

//5_1.cpp
#include <iostream>
using namespace std;
int i;				//全域性變數,檔案作用域
int main() {
    int i = 5;			//定義區域性變數i並賦值
    {				//子塊1
        int i;		//區域性變數,區域性作用域
        i = 7;
        cout << "i = " << i << endl;//輸出7
    }
    cout << "i = " << i << endl;//輸出5
    ::i = 10;
    cout << "i = " << i << endl;//輸出5
    cout <<"i = " << ::i << endl;//輸出10
    return 0;
}

靜態資料成員

  • 用關鍵字static宣告
  • 必須在類外定義和初始化,用*(::)來指明所屬的類

image-20240317203139663

eg :具有靜態資料成員的Point類

#include <iostream>
using namespace std;
 
class Point {	//Point類定義
public:	//外部介面
	Point(int x = 0, int y = 0) : x(x), y(y) { //建構函式
		//在建構函式中對count累加,所有物件共同維護同一個count
		count++;
	}
	Point(Point &p) {	//複製建構函式
		x = p.x;
		y = p.y;
		count++;
	}
	~Point() {  count--; }
	int getX() { return x; }
	int getY() { return y; }
        void showCount() {		//輸出靜態資料成員
		cout << "  Object count = " << count << endl;
	}
private:	//私有資料成員
	int x, y;
	static int count;	//靜態資料成員宣告,用於記錄點的個數
};
int Point::count = 0;//靜態資料成員定義和初始化,使用類名限定
int main() {	//主函式
	Point a(4, 5);	//定義物件a,其建構函式回使count增1
	cout << "Point A: " << a.getX() << ", " << a.getY();
	a.showCount();	//輸出物件個數
 
	Point b(a);	//定義物件b,其建構函式回使count增1
	cout << "Point B: " << b.getX() << ", " << b.getY();
	b.showCount();	//輸出物件個數
	return 0;
}

靜態成員函式

沒有this指標

靜態成員函式無法訪問成員變數

#include <iostream>
using namespace std;
class Point {	//Point類定義
public:	//外部介面
    Point(int x = 0, int y = 0) : x(x), y(y) { //建構函式
        //在建構函式中對count累加,所有物件共同維護同一個count
        count++;
    }
    Point(Point &p) {	//複製建構函式
        x = p.x;
        y = p.y;
        count++;
    }
    ~Point() {  count--; }
    int getX() { return x; }
    int getY() { return y; }
    static void showCount() {		//輸出靜態資料成員
        cout << "  Object count = " << count << endl;
    }
private:	//私有資料成員
    int x, y;
    static int count;	//靜態資料成員宣告,用於記錄點的個數
};
int Point::count = 0;//靜態資料成員定義和初始化,使用類名限定

int main() {	//主函式
    Point::showCount();
    Point a(4, 5);	//定義物件a,其建構函式回使count增1
    cout << "Point A: " << a.getX() << ", " << a.getY();
    a.showCount();	//輸出物件個數
    Point b(a);	//定義物件b,其建構函式回使count增1
    cout << "Point B: " << b.getX() << ", " << b.getY();
    b.showCount();	//輸出物件個數
    Point::showCount();
    return 0;
}

友元

友元不是成員函式

友元使用的原因

結合著類的特性,可知:類具有封裝和資訊隱藏的特性。只有類的成員函式才能訪問類的私有成員,程式中的其他函式是無法訪問私有成員的。非成員函式可以訪問類中的公有成員,但是如果將資料成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函式多次呼叫時,由於引數傳遞,型別檢查和安全性檢查等都需要時間開銷,而影響程式的執行效率。

為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函式,但它需要在類體內進行宣告,為了與該類的成員函式加以區別,在宣告時前面加以關鍵字friend。友元不是成員函式,但是它可以訪問類中的私有成員。友元的作用在於提高程式的執行效率,但是,它破壞了類的封裝性和隱藏性,使得非成員函式可以訪問類的私有成員。

優缺點

利用 friend 修飾符,可以讓一些普通函式 或 另一個類的成員函式 直接對某個類的保護成員和私有成員進行操作,提高了程式的執行效率;同時避免把類的成員都宣告為public,最大限度地保護資料成員的安全。

但是,即使是最大限度地保護資料成員,友元也破壞了類的封裝性

如果將類的封裝比喻成一堵牆的話,那麼友元機制就像牆上開了一個門。所以使用友元時一定要慎重。

eg友元函式 (計算兩點間的距離)

#include <iostream>
#include <cmath>
using namespace std;
class Point {	//Point類宣告
public:	//外部介面
	Point(int x=0, int y=0) : x(x), y(y) { }
	int getX() { return x; }
	int getY() { return y; }
	friend float dist(Point &a, Point &b); 
private:	//私有資料成員
	int x, y;
};
float dist( Point& a, Point& b) {
  double x = a.x - b.x;
  double y = a.y - b.y;
  return static_cast<float>(sqrt(x * x + y * y));
}
int main() {
  Point p1(1, 1), p2(4, 5);
  cout <<"The distance is: ";
  cout << dist(p1, p2) << endl;
  return 0;
}

友元類

宣告

friend class A;
friend void A::print();

eg

class A {
  friend class B;
public:
  void display() {
    cout << x << endl;
  }
private:
  int x;
}

class B {
public:
  void set(int i);
  void display();
private:
  A a;
};
void B::set(int i) {
   a.x=i;//由於B是友元類,故B中函式可訪問A中的x
}
void B::display() {
   a.display();
}

友元關係是單向的

上面案例如果宣告B類是A類的友元,B類的成員函式就可以訪問A類的私有和保護資料,但A類的成員函式卻不能訪問B類的私有、保護資料。

共享資料的保護

•對於既需要共享、又需要防止改變的資料應該宣告為常型別(用const進行修飾)。對於不改變物件狀態的成員函式應該宣告為常函式。

  • 常物件:必須進行初始化,不能被更新。

const 類名 物件名

class A
{
  public:
    A(int i,int j) {x=i; y=j;}
                     ...
  private:
    int x,y;
};
A const a(3,4); //a是常物件,不能被更新

透過常物件只能呼叫它的常成員函式。

  • 常成員

用const進行修飾的類成員:常資料成員和常函式成員

  • 常函式成員
  1. 使用const關鍵字說明的函式。

  2. 常成員函式不更新物件的資料成員

  3. 常成員函式說明格式:
    型別說明符 函式名(參數列)const;
    這裡,const是函式型別的一個組成部分,因此在實現部分也要帶const關鍵字。

  4. const關鍵字可以被用於參與對過載函式的區分

  5. 常物件只能呼叫常成員函式,並且優先呼叫普通成員函式

  1. eg const關鍵字可以被用於參與對過載函式的區分
#include<iostream>
using namespace std;
class R {
public:
  R(int r1, int r2) : r1(r1), r2(r2) { }
  void print();
  void print() const;
private:
  int r1, r2;
};
void R::print() {
  cout << r1 << ":" << r2 << endl;
}
void R::print() const {
  cout << r1 << ";" << r2 << endl;
}
int main() {
  R a(5,4);
  a.print(); //呼叫void print()
  const R b(20,52);  
  b.print(); //呼叫void print() const
	return 0;
}
  1. eg 常物件只能呼叫常成員函式

    #include<iostream>
    using namespace std;
    class R {
    public:
      R(int r1, int r2) : r1(r1), r2(r2) { }
      void print();
    private:
      int r1, r2;
    };
    void R::print() {
      cout << r1 << ":" << r2 << endl;
    }
    int main() {
      R a(5,4);
      a.print(); //呼叫void print()
      const R b(20,52);  
      b.print();//出錯
    	return 0;
    }
    

    image-20240322201850452

  • 常資料成員

    #include <iostream>
    using namespace std;
    class A {
    public:
    	A(int i);
    	void print();
    private:
    	const int a;
    	static const int b;  //靜態常資料成員
    };
    const int A::b=10; 
    A::A(int i) : a(i) { }//只能這樣初始化常資料成員,不能出現為a賦值的操作
    void A::print() {
      cout << a << ":" << b <<endl;
    }
    int main() {
    /*建立物件a和b,並以100和0作為初值,分別呼叫建構函式,透過建構函式的初始化列表給物件的常資料成員賦初值*/
      A a1(100), a2(0);
      a1.print();
      a2.print();
      return 0;
    }
    
  • 常引用:被引用的物件不能被更新。

const 型別說明符 &引用名

#include <iostream>
#include <cmath>
using namespace std;
class Point {	//Point類定義
public:	//外部介面
	Point(int x = 0, int y = 0)
    : x(x), y(y) { }
	int getX() { return x; }
	int getY() { return y; }
	friend float dist(const Point &p1, const Point &p2);
private:	//私有資料成員
	int x, y;
};
float dist(const Point &p1, const Point &p2) {
	double x = p1.x - p2.x;	
	double y = p1.y - p2.y;
	return static_cast<float>(sqrt(x * x + y * y));
}

int main() {	//主函式
	const Point myp1(1, 1), myp2(4, 5);	
	cout << "The distance is: ";
	cout << dist(myp1, myp2) << endl;
	return 0;
} 
  • 常陣列:陣列元素不能被更新

型別說明符 const 陣列名[大小]

  • 常指標:指向常量的指標

物件指標

•宣告形式

類名 *物件指標名;

例: Point a(5,10);

Piont *ptr;

ptr=&a;

•透過指標訪問物件成員

物件指標名->成員名

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

動態建立物件(new delete)

#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<< "Constructor called."<<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;		//刪除物件,自動呼叫解構函式
	
	cout << "Step two: " << endl;
	ptr1 = new Point(1,2);	
	delete ptr1;
    
    
    
    
	Point *ptr = new Point[2];	//建立物件陣列
	ptr[0].move(5, 10);  //透過指標訪問陣列元素的成員
	ptr[1].move(15, 20); //透過指標訪問陣列元素的成員
	cout << "Deleting..." << endl;
	delete[] ptr;	    //刪除整個物件陣列
	return 0;
}

string類

•常用建構函式

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

▫string(const char *s); //用指標s所指向的字串常量初始化string類的物件

▫string(const string& rhs); //複製建構函式

•例:

▫string s1; //建立一個空字串

▫string s2 = “abc”; //用常量建立一個初值為”abc”的字串

▫string s3 = s2;//執行複製建構函式,用s2的值作為s3的初值

•常用運算子

▫s + t 將串s和t連線成一個新串

▫s = t 用t更新s

▫s == t 判斷s與t是否相等

▫s != t 判斷s與t是否不等

▫s < t 判斷s是否小於t(按字典順序比較)

▫s <= t 判斷s是否小於或等於t (按字典順序比較)

▫s > t 判斷s是否大於t (按字典順序比較)

▫s >= t 判斷s是否大於或等於t (按字典順序比較)

▫s[i] 訪問串中下標為i的字元

•例:

▫string s1 = "abc", s2 = "def";

▫string s3 = s1 + s2; //結果是"abcdef"

▫bool s4 = (s1 < s2); //結果是true

▫char s5 = s2[1]; //結果是'e'

用getline輸入整行字串

•輸入整行字串

▫用cin的>>運算子輸入字串,會以空格作為分隔符,空格後的內容會在下一回輸入時被讀取

▫用string標頭檔案中的getline可以輸入整行字串,例如:

getline(cin, s2);

•以其它字元作為分隔符輸入字串

▫輸入字串時,可以使用其它分隔符作為字串結束的標誌(例如逗號、分號)

▫把分隔符作為getline的第3個引數即可,例如:

getline(cin, s2, ',');

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;
}

相關文章