文章目錄
- 1. const對象
- 2. 指向對象的指針
- 3. 對象數組
- 4. c++中const常見用法總結
- 4.1 修飾常量
- 4.2 修飾指針
- 4.3 修飾函數參數
- 4.4 修飾函數返回值
- 4.5 修飾成員函數
- 4.6 const對象
- 5. 賦值運算符函數(補充)
- 5.1 概念
- 5.2 默認賦值運算符函數局限
- 5.3 解決辦法
1. const對象
1.概念:在 C++ 中,const 用于定義常量對象,一旦對象被初始化,其值就不能再改變。因為 const 對象只能被創建、撤銷和只讀訪問,寫操作是不允許的。
2.基本使用
#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public:Person() :_age(0), _name(new char[strlen("張三") + 1]){cout << "無參構造" << endl;strcpy(_name, "張三");}Person(int age, const char* name) :_age(age), _name(new char[strlen(name) + 1]){cout << "有參構造" << endl;strcpy(_name, name);}void setAge(int age){_age = age;}void print(){cout << "age=" << _age << endl;cout << "name=" << _name << endl;}//const成員函數,不能修改對象的數據成員void print() const{cout << "age=" << _age << endl;cout << "name=" << _name << endl;}~Person(){delete[] _name;cout << "析構函數" << endl;}
private:int _age;char* _name;
};int main(void)
{//普通對象Person p1(1,"張大");p1.print();p1.setAge(10);p1.print();//const 對象只能被創建、撤銷和只讀訪問,寫操作是不允許的const Person p2(2, "張二");//p2.setAge(20) //不允許//p2.print(); //不允許p2.print(); //調用的是void print() constreturn 0;
}
3.總結
const對象與const成員函數的規則:
(1)當類中有const成員函數和非const成員函數重載時,const對象會調用const成員函數,非const對象會調用非const成員函數;
(2)當類中只有一個const成員函數時,無論const對象還是非const對象都可以調用這個版本;
(3)當類中只有一個非const成員函數時,const對象就不能調用非const版本。
建議:如果一個成員函數中確定不會修改數據成員,就把它定義為const成員函數。
2. 指向對象的指針
int main(void)
{//在棧上開辟空間Person p1(1, "一號");Person* p2 = &p1; //指向p1的指針;Person* p3 = nullptr; //空指針;//在堆上開辟空間Person* p4 = new Person(2, "二號");delete p4;p4 = nullptr;Person* p5 = new Person();p5->setAge(20); p5->print();(*p5).setAge(30);(*p5).print();delete p5;p5 = nullptr;return 0;
}
3. 對象數組
#include <iostream>
#include <cstring>namespace myspace1
{using std::endl;using std::cout;using std::cin;using std::string;
}using namespace myspace1;class Person
{
public:Person() :_age(0), _name(new char[strlen("張三") + 1]){cout << "無參構造" << endl;strcpy(_name, "張三");}Person(int age, const char* name) :_age(age), _name(new char[strlen(name) + 1]){cout << "有參構造" << endl;strcpy(_name, name);}void setAge(int age){_age = age;}void print(){cout << "age=" << _age << endl;cout << "name=" << _name << endl;}//const成員函數,不能修改對象的數據成員void print() const{cout << "age=" << _age << endl;cout << "name=" << _name << endl;}~Person(){delete[] _name;cout << "析構函數" << endl;}
public:int _age;char* _name;
};int main(void)
{// 方式一:使用無參/默認構造函數創建對象數組cout << "使用默認構造函數創建對象數組:" << endl;Person p1[3];for (int i = 0; i < 3; i++) {p1[i].print();}// 方式二:使用有參構造函數初始化對象數組cout << "使用有參構造函數初始化對象數組:" << endl;Person p2[3] = {Person(20, "李四"),Person(21, "王五"),Person(22, "趙六")};for (int i = 0; i < 3; ++i) {p2[i].print();}// 方式三:動態分配對象數組cout << "動態分配對象數組:" << endl;Person* p3 = new Person[2];p3[0].setAge(25);strcpy(p3[0]._name, "孫七");p3[1].setAge(26);strcpy(p3[1]._name, "周八");for (int i = 0; i < 2; ++i) {p3[i].print();}// 釋放動態分配的內存delete[] p3;return 0;
}
4. c++中const常見用法總結
4.1 修飾常量
//只有讀權限,沒有寫權限
const int a = 10; //==int const a=10;
//a = 20; 不可以修改
4.2 修飾指針
int main(void)
{int a = 10;int b = 20;//1.指向常量的指針const int* p1 = &a; //==int const* p1 = &a;//*p1 = 100; 不可以為通過指針修改a的值;p1 = &b; //可以修改指針p1的指向//2.常量指針;可以通過指針修改變量的值,但是指針的指向不可以變;int* const p2 = &a;*p2 = 20;//p2 = &b; 錯誤//3.指向常量的常量指針:指針的指向和所指向的值都不能改變const int* const p3 = &a;// *p3 = 20; // 錯誤,不能通過指針修改所指向的值// p3 = &b; // 錯誤,不能改變指針的指向return 0;
}
4.3 修飾函數參數
const 用于修飾函數參數時,能夠保證在函數內部不會修改該參數的值。
void fun(const int a)
{//a = 100; 錯誤cout << "a=" <<a<< endl;
}int main(void)
{int a = 10;fun(a);return 0;
}
4.4 修飾函數返回值
const 修飾函數返回值時,表明返回值是一個常量,不能被修改。
//const 修飾函數返回值時,表明返回值是一個常量,不能被修改。
int const fun()
{int a = 10;return a;
}int main(void)
{//fun() = 100; 錯誤,不可以修改return 0;
}
4.5 修飾成員函數
const 修飾類的成員函數時,表明該成員函數不會修改對象的狀態
class MyClass {
private:int _value;
public:MyClass(int val) : _value(val) {}// 常量成員函數int getValue() const {// value = 20; // 錯誤,不能在常量成員函數中修改成員變量return _value;}
};
4.6 const對象
const 對象是指那些一經創建,其狀態(即成員變量的值)就不能被修改的對象
class MyClass {
public:int _value;MyClass(int v) : _value(v) {}
};int main() {const MyClass obj(10);// obj.value = 20; // 錯誤,不能修改 const 對象的成員變量cout << "Value: " << obj._value << endl;return 0;
}
5. 賦值運算符函數(補充)
5.1 概念
Point pt1(1, 2), pt2(3, 4);
pt1 = pt2;//賦值操作
在執行 pt1 = pt2; 該語句時, pt1 與 pt2 都存在,所以不存在對象的構造,這要與 Point pt2 =pt1; 語句區分開,這是不同的。所以當 = 作用于對象時,需要調用的是賦值運算符函數。
格式:
類名& operator=(const 類名 &)
class Point {
private:int _ix;int _iy;
public:Point(int x = 0, int y = 0) : _ix(x), _iy(y) {}//默認賦值運算符重載函數Point& operator=(const Point& rhs){_ix = rhs._ix;_iy = rhs._iy;return *this;}void print() const {cout << "(" << _ix << ", " << _iy << ")" <<endl;}
};int main() {Point p1(1, 2);Point p2(3, 4);p2 = p1;p2.print();return 0;
}
5.2 默認賦值運算符函數局限
(1)當類中包含指針數據成員且該指針指向堆上分配的內存時,默認的賦值運算符函數(編譯器自動生成的)就無法滿足需求,這可能會導致淺拷貝問題,進而引發內存泄漏或懸空指針等錯誤。
(2)默認的賦值運算符執行的是淺拷貝,也就是只復制指針的值,而不復制指針所指向的內存。這會造成兩個對象的指針成員指向同一塊內存,當其中一個對象被銷毀時,它會釋放這塊內存,而另一個對象的指針就會變成懸空指針。另外,如果兩個對象都嘗試釋放同一塊內存,會導致重復釋放,引發未定義行為。
class Person
{
public://構造函數Person(const int* age) :_age(new int(*age)){cout << "構造函數" << endl;}//打印函數void print()const{cout << "age=" << *_age << endl;}//析構函數~Person(){delete _age;cout << "析構函數" << endl;}
private:int* _age;
};int main(void)
{int age1 = 20;int age2 = 30;Person p1(&age1);p1.print();Person p2(&age2);p2.print();p2 = p1;return 0;
}
問題分析:
當執行 p2 = p1; 時,會調用編譯器自動生成的默認賦值運算符。默認賦值運算符執行的是淺拷貝,即只復制指針的值,而不復制指針所指向的內存。這會導致 p1 和 p2 的 _age 指針指向同一塊內存。由于淺拷貝,當 p2 對象被銷毀時,會釋放 _age 所指向的內存。此時,p1 的 _age 指針就會變成懸空指針。當 p1 對象被銷毀時,再次釋放同一塊內存,會導致重復釋放,引發未定義行為。
5.3 解決辦法
為了解決淺拷貝的問題,需要自定義賦值運算符函數,實現深拷貝。深拷貝會為新對象分配新的內存,并將原對象的數據復制到新的內存中。
class Person
{
public://構造函數Person(const int* age) :_age(new int(*age)){cout << "構造函數" << endl;}//打印函數void print()const{cout << "age=" << *_age << endl;}//自定義賦值運算符函數Person& operator=(const Person& rhs){//自我賦值檢查if (this != &rhs) {//釋放當前_age空間;delete _age;_age = nullptr;_age = new int(*rhs._age);}return *this;}//析構函數~Person(){delete _age;cout << "析構函數" << endl;}
private:int* _age;
};int main(void)
{int age1 = 20;int age2 = 30;Person p1(&age1);p1.print();Person p2(&age2);p2.print();p2 = p1;return 0;
}
若你有自行定義某些特殊成員函數,編譯器會為類自動生成以下 6 個默認函數:
- 默認構造函數
- 析構函數
- 拷貝構造函數
- 拷貝賦值運算符
- 移動構造函數(C++11 及以后)
- 移動賦值運算符(C++11 及以后)
注意:拷貝構造函數、賦值運算符函數、析構函數,如果需要手動定義其中的一個,那么另外兩個也需要手動定義。 三合成原則