css做購物網(wǎng)站的分類目錄搜索引擎優(yōu)化搜索優(yōu)化
一 拷貝構(gòu)造函數(shù)的概念:
拷貝構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),用于創(chuàng)建一個(gè)對(duì)象是另一個(gè)對(duì)象的副本。當(dāng)需要用一個(gè)已存在的對(duì)象來初始化一個(gè)新對(duì)象時(shí),或者將對(duì)象傳遞給函數(shù)或從函數(shù)返回對(duì)象時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù)。
二?拷貝構(gòu)造函數(shù)的特點(diǎn):
1:拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
2:拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類類型對(duì)象的引用,使用傳值方式編譯器直接報(bào)錯(cuò), 因?yàn)闀?huì)引發(fā)無窮遞歸調(diào)用。
3:若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按 字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
注意:在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類型是按照字節(jié)方式直接拷貝的,而自定 義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
4:編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己顯式實(shí)現(xiàn)嗎? 當(dāng)然像日期類這樣的類是沒必要的。
2.1 代碼示例:
class Time
{
public:// 普通構(gòu)造函數(shù)Time(int hour = 0, int minute = 0, int second = 0) {_hour = hour;_minute = minute;_second = second;}// 拷貝構(gòu)造函數(shù),使用引用傳遞Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;}void Print() const {std::cout << _hour << ":" << _minute << ":" << _second << std::endl;}private:int _hour;int _minute;int _second;
};int main()
{Time t1(10, 20, 30); // 使用普通構(gòu)造函數(shù)//構(gòu)造函數(shù)的重載Time t2 = t1; // 使用拷貝構(gòu)造函數(shù)//Time t2(t1); // 拷貝構(gòu)造的另一種寫法t1.Print();t2.Print();return 0;
}
輸出:
2.2 為什么要使用引用呢?
我們?cè)?increment 函數(shù)中改變x的值并沒有間接性改變a,這是因?yàn)閭鬟^去的只是編譯器創(chuàng)建實(shí)參的一個(gè)副本,而修改副本怎么可能可以改變a呢?
#include <iostream>void increment(int x)
{x = x + 1; // 修改的是副本,不影響實(shí)參
}int main()
{int a = 5;increment(a); // 傳遞a的副本std::cout << a << std::endl; // 輸出5,原始值a未被修改return 0;
}
知道傳值傳參的本質(zhì)之后,再來想一想為什么要用引用?咱們先來說說如果沒用用引用的后果會(huì)是怎么樣,當(dāng)把自定義類型傳出去后且不用引用或者指針來接收,它會(huì)
調(diào)用 Time(const Time other)
,其中 other
是 t1
的按值傳遞副本。
為了按值傳遞,編譯器需要?jiǎng)?chuàng)建 other
的副本。
創(chuàng)建 other
的副本時(shí),再次調(diào)用 Time(const Time other)
。
這個(gè)新調(diào)用的 Time(const Time other)
又需要?jiǎng)?chuàng)建自己的 other
副本,再次調(diào)用 Time(const Time other)
。
如此反復(fù),導(dǎo)致無限遞歸調(diào)用,最終導(dǎo)致棧溢出。
圖:
C++規(guī)定,自定義類型的拷貝,都會(huì)調(diào)用拷貝構(gòu)造
那為什么要引用呢?
首先我們來回顧一下引用 :
1:引用是現(xiàn)有變量的另一個(gè)名字。
2:它們不創(chuàng)建新對(duì)象,只是指向已有對(duì)象。
3:引用只是指向現(xiàn)有對(duì)象,不創(chuàng)建新副本
因?yàn)橐镁褪撬旧?#xff0c;所以何來創(chuàng)建新副本這一說法,創(chuàng)建新副本是怕改變副本從而導(dǎo)致改變實(shí)參值
2.3 總結(jié):
1:按值傳遞會(huì)遞歸:每次傳遞對(duì)象會(huì)復(fù)制對(duì)象,導(dǎo)致無限遞歸。
2:引用傳遞避免遞歸:引用只是指向?qū)ο蟊旧?#xff0c;不會(huì)復(fù)制對(duì)象
三 默認(rèn)拷貝構(gòu)造:
當(dāng)你沒有顯式定義拷貝構(gòu)造函數(shù)時(shí),編譯器會(huì)為你自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)。這個(gè)默認(rèn)拷貝構(gòu)造函數(shù)會(huì)逐個(gè)拷貝對(duì)象的所有成員變量。
3.1 內(nèi)置類型與自定義類型的拷貝:
內(nèi)置類型:如 int
, char
, float
等,拷貝時(shí)直接按照字節(jié)方式進(jìn)行復(fù)制,也就是直接復(fù)制其值。
自定義類型:如類和結(jié)構(gòu)體,拷貝時(shí)會(huì)調(diào)用該類型的拷貝構(gòu)造函數(shù)。
3.2 代碼示例:
內(nèi)置類型:
#include <iostream>class MyClass
{
public:int x; // 內(nèi)置類型成員
};int main()
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1; // 使用編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)std::cout << "obj1.x: " << obj1.x << std::endl; std::cout << "obj2.x: " << obj2.x << std::endl;return 0;
}
輸出:
對(duì)于一個(gè)類里面只有內(nèi)置類型成員那編譯器生成的默認(rèn)拷貝構(gòu)造會(huì)自動(dòng)復(fù)制其值。
自定義類型:
#include <iostream>class Time
{
public:// 默認(rèn)構(gòu)造函數(shù)Time() { _hour = 0;_minute = 0;_second = 0;}// 拷貝構(gòu)造函數(shù)Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;std::cout << "Time::Time(const Time& other)" << std::endl;}private:int _hour;int _minute;int _second;
};class MyClass
{
public:int x; // 內(nèi)置類型成員Time t; // 自定義類型成員
};int main()
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1; // 使用編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)std::cout << "obj1.x: " << obj1.x << std::endl;std::cout << "obj2.x: " << obj2.x << std::endl; return 0;
}
當(dāng)執(zhí)行MyClass obj2 = obj1; 因obj1類里面有自定義類型 t 所以編譯器生成的默認(rèn)拷貝構(gòu)造會(huì)自動(dòng)調(diào)用Time(const Time& other) 來完成
3.3 總結(jié):
內(nèi)置類型:編譯器默認(rèn)拷貝構(gòu)造函數(shù)會(huì)直接復(fù)制其值。
自定義類型:編譯器默認(rèn)拷貝構(gòu)造函數(shù)會(huì)調(diào)用該類型的拷貝構(gòu)造函數(shù)來復(fù)制其內(nèi)容。
四 內(nèi)存分區(qū):
要理解好深拷貝與淺拷貝那就得先了解內(nèi)存是怎么樣分區(qū)的。
計(jì)算機(jī)程序運(yùn)行時(shí),內(nèi)存通常被分為四個(gè)主要區(qū)域:棧區(qū)、堆區(qū)、全局靜態(tài)區(qū)和只讀區(qū)(常量區(qū)和代碼區(qū))。
4.1 棧區(qū):
局部變量:函數(shù)內(nèi)部定義的變量。
形參(函數(shù)參數(shù)):函數(shù)定義時(shí)的參數(shù)。
返回地址:函數(shù)調(diào)用后的返回地址。
特點(diǎn):
棧區(qū)中訪問速度快且棧的內(nèi)存連續(xù)分配。
因存儲(chǔ)的都是 局部/形參/返回地址 所以棧區(qū)空間小,存儲(chǔ)的生命周期短。
在我們局部變量所在的函數(shù)執(zhí)行完成時(shí),它會(huì)自動(dòng)釋放內(nèi)存。
4.2 堆區(qū):
動(dòng)態(tài)分配的數(shù)據(jù):通過 new
或 malloc
等動(dòng)態(tài)分配函數(shù)分配的內(nèi)存。
特點(diǎn):
因存儲(chǔ)的都是new 或者malloc開辟的空間所以堆區(qū)空間大,所以訪問速度慢。
堆中的內(nèi)存分配和釋放是通過指針進(jìn)行的,可能不是連續(xù)的。
堆區(qū)的內(nèi)存需要程序員手動(dòng)管理,必須手動(dòng)釋放動(dòng)態(tài)分配的內(nèi)存,否則會(huì)導(dǎo)致內(nèi)存泄漏。
4.3 全區(qū)/靜態(tài)區(qū):
全局變量:在所有函數(shù)外部定義的變量。
靜態(tài)變量:使用 static
關(guān)鍵字定義的變量。
特點(diǎn):
全局變量和靜態(tài)變量在程序的整個(gè)運(yùn)行期間一直存在,直到程序結(jié)束。
全局變量可以在程序的所有函數(shù)中訪問,靜態(tài)變量在聲明的作用域內(nèi)共享。
4.4 只讀常量區(qū):
常量:程序中定義的常量。
代碼:程序的指令代碼。
特點(diǎn):
常量區(qū)的數(shù)據(jù)在程序運(yùn)行期間不能被修改,保證了數(shù)據(jù)的安全性和穩(wěn)定性。
代碼區(qū)存儲(chǔ)程序的指令代碼,在程序運(yùn)行時(shí)被載入內(nèi)存以執(zhí)行。
五 淺拷貝:
首先我們來回顧C(jī)語言里面的基本類型和指針類型。
5.1 基本類型:
基本類型是C語言內(nèi)置的數(shù)據(jù)類型,它們用于存儲(chǔ)最基本的數(shù)值數(shù)據(jù)。常見的基本類型包括:int float char……
5.2 指針類型:
指針類型是存儲(chǔ)內(nèi)存地址的數(shù)據(jù)類型。指針用于指向其他變量或?qū)ο笤趦?nèi)存中的位置。
5.3 基本類型代碼示例:
#include <iostream>class BasicType
{
public:int value;// 構(gòu)造函數(shù)BasicType(int v) {value = v;}// 拷貝構(gòu)造函數(shù)BasicType(const BasicType& other) {value = other.value;}
};int main()
{BasicType obj1(10);BasicType obj2 = obj1; // 淺拷貝,復(fù)制基本類型的值std::cout << "改變前: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;obj2.value = 20; // 修改obj2的值std::cout << "改變后: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;return 0;
}
輸出:
值會(huì)被復(fù)制但修改新對(duì)象的值不會(huì)影響原對(duì)象。
5.3 指針類型代碼示例:
#include <iostream>class SimplePointer
{
public:int* ptr; // 成員變量 ptr// 構(gòu)造函數(shù)SimplePointer(int value)
{ptr = (int*)malloc(sizeof(int)); // 動(dòng)態(tài)分配內(nèi)存并初始化if (ptr != nullptr) {*ptr = value;}
}SimplePointer(const SimplePointer& other) {this->ptr = other.ptr; // 淺拷貝,復(fù)制內(nèi)存地址}void print() const {std::cout << "Value: " << *ptr << std::endl;}
};int main()
{SimplePointer obj1(10); // 創(chuàng)建第一個(gè)對(duì)象,并將值初始化為10SimplePointer obj2(obj1); // 使用拷貝構(gòu)造函數(shù)(淺拷貝)// 打印初始值std::cout << "Initial values:" << std::endl;obj1.print();obj2.print();// 修改obj2的值*obj2.ptr = 20;// 打印修改后的值std::cout << "After change:" << std::endl;obj1.print();obj2.print(); return 0;
}
輸出:
復(fù)制內(nèi)存地址,共享同一塊內(nèi)存,修改會(huì)互相影響
六 深拷貝:
#include <iostream>
#include <cstdlib>
#include <cstring>class SimpleClass
{
public:int* ptr;// 默認(rèn)構(gòu)造函數(shù)SimpleClass(int value) {ptr = (int*)malloc(sizeof(int)); // 動(dòng)態(tài)分配內(nèi)存并初始化if (ptr != nullptr) {*ptr = value;}}// 深拷貝構(gòu)造函數(shù)SimpleClass(const SimpleClass& other) {ptr = (int*)malloc(sizeof(int)); // 分配新內(nèi)存if (ptr != nullptr) {*ptr = *(other.ptr); // 復(fù)制內(nèi)容}}// 析構(gòu)函數(shù)~SimpleClass() {if (ptr != nullptr) {free(ptr); // 釋放內(nèi)存}}void Print() const {if (ptr != nullptr) {std::cout << "Value: " << *ptr << std::endl;}}
};int main()
{SimpleClass obj1(10); // 創(chuàng)建對(duì)象,ptr 指向的值為 10SimpleClass obj2 = obj1; // 使用深拷貝構(gòu)造函數(shù)obj1.Print();obj2.Print();// 修改 obj2 的值if (obj2.ptr != nullptr) {*(obj2.ptr) = 20;}obj1.Print();obj2.Print();return 0;
}
輸出:
深拷貝不僅復(fù)制對(duì)象的指針成員,還為指針指向的內(nèi)容分配新的內(nèi)存,并復(fù)制原對(duì)象的數(shù)據(jù)。這樣,兩個(gè)對(duì)象擁有獨(dú)立的內(nèi)存,修改一個(gè)不會(huì)影響另一個(gè)。