**在Python Web開發領域,Django框架的地位猶如璀璨的明星,其全面、高效和安全的特性使其在全球範圍內廣受歡迎。本文將全面解析Django框架的預設文件,並深入探討每個文件及其組成的意義和用途,透徹展示這個強大框架的文件結構和設計原理。** 首先,讓我們看一下創建一個新的Django項目 ...
記憶體模型
C++在執行程式的時候,將記憶體方向劃分為4個區域:
-
代碼區:存放二進位代碼,由操作系統進行管理
-
全局區:存放全局變數、靜態變數、常量,程式結束後由操作系統釋放
-
棧區:存放函數參數、局部變數,由編譯器自動分配和釋放
-
堆區:由開發者申請分配和釋放,若程式員不釋放,程式結束由操作系統自動回收
意義:對於不同區域存放的數據,賦予不同的生命周期,給編程更大的靈活性。
代碼區
存放CPU執行的二進位代碼(機器指令)
特點:
-
共用:對於頻繁被執行的程式,只需要在記憶體中有一份就夠了
-
只讀:防止被意外修改
全局區
-
存放全局變數和靜態變數,還存放常量,包括字元串常量和其他常量
-
數據在程式結束後由操作系統進行釋放
棧區
-
存放函數參數、局部變數
-
不要返回局部變數的地址,因為函數一執行完,棧區數據就被釋放了,雖然編譯器會做短暫的保留
堆區
-
這是由開發者分配和釋放的,如果程式結束開發者不釋放,也會操作系統回收
-
在C++中主要用new開闢堆區空間,用delete釋放
new 和 delete
new作用:用於讓開發者在堆區中開闢數據
delete作用:讓開發者手動釋放堆區數據
語法:
new 數據類型
delete 堆區地址
示例:
int *p = new int(8); //在堆區開闢一個int類型的記憶體,存放數據8
int *p = new int[10]; //在堆區開闢一個int類型的記憶體存放數組,數組中有10個元素
delete(p); //釋放地址a的數據
引用
作用:給變數起別名
語法:
數據類型 &別名 = 原名
註意事項:
-
引用必須初始化
-
初始化後,就不可以再發生改變了
-
引用必須引一塊合法的記憶體空間,可以是棧區,可以是堆區,但不可以是自變數(比如數字)
示例:
int a = 10;
int &b = a; //引用,且必須初始化
int &b = 10; //×,錯誤,引用必須引一塊合法的記憶體,10是自變數,既不是棧區也不是堆區
const int &b = 10; //√,正確,加上const後,編譯器會開闢出一塊臨時記憶體,int temp = 10,const int &b = temp;
引用做函數參數
作用:可以讓形參修飾實參,代替指針中形參修改實參的操作
示例:
void myswap(int &x, int &y) //引用就是取別名,所以參數就是實參,所以可以改變實參
{
int temp = x;
x = y;
y = temp;
}
int main
{
int a = 10;
int b = 20;
myswap(a , b); //引用傳遞,形參可以修改實參
system("pause");
}
引用做函數返回值
作用:可以作為函數返回值類型返回
註意事項:不要返回局部變數的引用,函數執行完局部變數記憶體就被釋放了,返回個錘子
示例:
int& test() //函數返回值類型就是引用類型
{
static int a = 10; //加個關鍵字static,這樣變數a就不是關鍵字了
return a;
}
int main()
{
int &ref = test();
}
引用的本質
本質:引用的本質在C++內部實現,它就是一個指針常量,由編譯器內部轉換
作用:也就說明為什麼引用初始化之後就不可更改,因為指針指向不可改
& <—等於—> int* const
示例:
int& ref = a; <==> int* const ref = &a
ref = 20; <==> *ref = 20
常量引用
作用:主要用來修飾形參,防止誤操作
用法:在函數形參列表中,加const修飾形參,防止形參改變實參
示例:
void temp(const int& val)
{
}
函數進階用法
函數的預設參數
在C++中,函數的形參列表中形參是可以用預設參數的
語法:
返回值類型 函數名 (參數 = 預設值)
{
}
註意事項:
-
如果函數某個參數有預設值,那麼從這個位置之後的參數必須有預設值
-
如果調用的時候有實參,那就用實參,沒有實參,就用預設值
-
如果函數聲明有預設值,那麼在函數定義的時候就不能有預設值
示例:
int func1(int a, int b=10, int c=20) //往後如果還有參數,必須要有預設值
int func2(int a=10, int b=20) //函數聲明有預設值了
int func2(int a, int b) //函數實現就不能有預設值了
{
}
函數的占位參數
作用:用來給函數的參數列表中做占位,調用函數的時候填補該位置就行了
語法:
返回值類型 函數名(數據類型)
{
}
缺點:現階段函數的占位函數存在意義不大。
示例:
void func(int a, int) //int 就是占位參數了,只需要寫一個數據類型即可
{
}
int main
{
func(10,20); //調用的時候占位函數要補上
}
函數重載
作用:函數名相同,其他的可以不同,可以提高函數的復用性
滿足條件:
-
同一個作用域下
-
函數名稱相同
-
函數參數類型不同,或者個數不同,或者順序不同
註意事項:
-
函數的返回值不能作為函數重載的滿足條件
-
具體調用的是哪一個函數,就看參數,看實參是否對應形參,比如類型、個數、順序
示例:
void func() //func是函數重載,這是在全局作用域下
{
}
void func(int a) //參數類型不同,這是在全局作用域下
{
}
void func(double a,double b) //參數個數不同,這是在全局作用域下
{
}
void func(double a, int b) //參數順序不同,這是在全局作用域下
{
}
引用作為函數重載
當引用作為函數參數時:
-
實參必須是一塊合法的記憶體
-
如果實參不是記憶體,只是一個自變數,那麼形參就必須加const來修飾
示例:
void func(int &a)
{
}
void func(const int &a) //這兩個func是函數重載
{
}
int main()
{
int a = 10;
func(a); //調用的是第一個func函數,因為a是變數,是一塊合法記憶體
func(10); //調用的是第二個func函數,因為10是自變數,加const修飾本質上是申請一塊臨時記憶體存放數據10
}
遇到預設參數
-
當函數重載遇到預設參數時,會出現二義性,也就是出錯
-
使用時儘量避免出現預設參數
示例:
void func(int a)
{
}
void func(int a, int b = 10)
{
}
int main()
{
func(10); //❌,編譯器懵了,不知道該調用哪一個func
}
類與對象
-
C++本身就是面向對象的編程語言
-
面向對象三大特性:封裝、繼承、多態
-
C++中萬物皆可為對象,對象上有屬性和行為
-
具有相同性質的對象,稱之為類
示例:
人可以作為對象,屬性有姓名、年齡、身高···,行為有唱,跳、rap···
你和你的死黨,是同一性質,屬於人類;
車可以作為對象,屬性有輪胎、車燈、方向盤···,行為有載人、音樂、顯擺···
五菱與奧迪,是同一性質,屬於車類;
封裝
封裝的意義
封裝是C++面向對象三大特性之一
封裝的意義:
-
將屬性和行為作為一個整體,來表現生活中的事物
-
將屬性和行為用許可權加以控制
封裝的術語:
-
類中的屬性和行為,統稱為成員
-
屬性也叫成員屬性或者成員變數
-
行為也叫成員函數或者成員方法
封裝意義一:將屬性和行為作為一個整體
語法:
class 類名{ 訪問許可權:屬性/行為 }
示例:創建一個類為圓,那半徑就是它的屬性了。
class Circle
{
public: //設置訪問許可權,公共許可權
double m_r; //屬性——半徑,
double calculate() //行為——計算周長
{
return 2 * PI * m_r;
}
}
int main()
{
Circle C1; //通過一個類創建一個對象,對象就是圓,也就是實例化
C1.m_r = 10;
cout << "圓的周長:" << C1.calculate() << endl;
}
封裝意義二:將屬性和行為用訪問許可權來加以管理
訪問許可權有三種:
-
public:公共許可權,成員類內和類外都可以訪問
-
protected:保護許可權,成員類內可以訪問,,類外不可以訪問
-
private:私有許可權,成員類內可以訪問,類外不可以訪問
protecred保護許可權和private私有許可權的區別在於後面要說到的繼承上,前者子類可以訪問父類,後者子類不可訪問父類,這是後面的內容了。
示例:
class person
{
public:
string m_name; //姓名,類內類外都可以訪問
protected:
string m_car; //汽車,類內可以訪問,類外不可以,家裡的汽車只有家裡人能用,外人不可以
private:
int m_password; //銀行卡密碼,類內極度私密,只有當事人能用,其他任何人甚至兒子也不能用
public:
void func()
{
m_name = "張三"; //類內可以訪問
m_car = "大眾"; //類內可以訪問
m_password = 123456; //類內可以訪問
}
}
int main()
{
person c1;
c1.m_name = "李四"; //√,類外可以訪問
c1.m_car = "吉利"; //×,類外不可以訪問
c1.m_password = 456789; //×,類外不可以訪問
c1.func(); //√,類外是可以訪問的
}
struct和class
struct和class都可以表示一個類,區別在於兩者預設的許可權不同:
-
struct:預設許可權為公共
-
class:預設許可權為私有
成員屬性設置為私有
優點:
-
所有成員設置成私有,自己可以控制讀寫許可權
-
對於寫許可權,可以檢查其數據的有效性
示例:所有成員設置成私有,自己可以控制讀寫許可權
class person
{
private:
string m_name; //成員設置成私有許可權,一般通過成員函數進行訪問
int m_age;
string m_lover;
public:
void SetName(string name) //這樣name就被設置成了可讀可寫的
{
m_name = name;
}
int getage() //age就被設置成了只讀
{
m_age = 18;
return m_age;
}
}
int main()
{
person c1;
c1.m_name = "張三"; //×,因為成員是私有許可權,沒法訪問
c1.SetName("張三"); //√,設置姓名
cout << c1.getage() << endl; //√,獲取年齡
}
示例:對於寫許可權,可以檢查其數據的有效性
class person
{
private:
string m_name;
int age;
public:
int GetAge(int age) //獲取年齡,設置成可讀可寫,如果想修改年齡,範圍必須是0—100
{
if( age < 0 || age >100 ) //加了判斷,可以判斷數據是否有效
{
cout << "年齡錯了"<< endl;
return ;
}
m_age = 0;
}
}
對象的初始化和清理
C++中,初始化和清理是非常重要的安全問題:
-
構造函數:在創建對象時為對象成員屬性初始化,該函數由編譯器自動調用
-
析構函數:在對象銷毀前系統自動調用,執行一些清理工作
完成對象的初始化和清理工作是編譯器必須要我們做的事情,這兩個函數由編譯器自動調用,但是如果我們不寫構造函數和析構函數,編譯器就會自己去實現,只不過函數裡面是空的,簡稱空實現
構造函數
語法:
類名(){}
特點:
-
不用寫void,沒有返回值
-
函數名與類名相同
-
可以有參數,因為可以發生函數重載
-
在調用對象時編譯器自動調用,而且只會調用一次
析構函數
語法:
~類名(){}
特點:
-
不用寫void,沒有返回值
-
函數名與類名相同,且在前面加~
-
不可以有參數,因此不可以發生函數重載
-
在對象銷毀前編譯器自動調用,而且只調用一次
示例:
class Person
{
public:
Person() //這就是構造函數,如果不寫,編譯器會自己寫一個
{
}
~Person() //這就是析構函數,如果不寫,編譯器會自己寫一個
{
}
}
構造函數的分類和調用
兩種分類方式:
-
按參數分:有參構造和無參構造(預設構造)
-
按類型分:普通構造和拷貝構造
三種調用方式:
-
括弧法
-
顯示法
-
隱式轉換法
示例:
class Peoson()
{
public:
Person() //無參構造,也就是普通構造
{
}
Person(int a) //有參構造
{
}
Person(const Person &p) //拷貝構造
{
}
}
int main()
{
//1、括弧法調用
Person P1; //普通調用,不用加(),不然編譯器會誤以為是函數聲明
Person P2(10); //調用的是對應的有參構造
Person P3(P1); //調用的是對用的拷貝構造
//2、顯示法調用
Person P1;
Person P2 = Person(10); //調用的是對應的有參構造
Person P3 = Person(P2); //調用的是對應的拷貝構造
//3、隱式轉換法
Person P1 = 10; //調用的是有參構造,相當於Person P1 = Person(10)
Person P2 = P1; //調用的是拷貝構造,相當於Person P2 = Person(P1)
}
說說拷貝構造函數
所謂拷貝構造函數就是將一個創建完畢對象的屬性預設賦值給新的對象,這個賦值過程由編譯器自動完成
通常以下三種情況會調用到拷貝構造:
-
使用一個已經創建完畢的對象來初始化一個新對象
-
值傳遞的方式給函數參數傳參
-
以值傳遞的方式返回局部對象
示例:
class Person()
{
Person()
{
}
Person(const Person &p)
{
}
}
void func1(Person p)
{
}
Person func2()
{
Person a;
return a;
}
int main()
{
Person P1;
Person P2(P1); //使用一個已經創建好的對象來初始化一個新對象
Person P3;
func1(P3); //以值傳遞的方式給函數傳參
Person P4 = func2(); //以值傳遞的方式返回對象,它會創建一個新的對象來接住返回對象
}
構造函數的調用規則
(1)預設情況下,創建一個類編譯器至少會添加3個函數
-
預設構造函數(無參,函數體為空)
-
預設析構函數(無參,函數體為空)
-
預設拷貝函數,對屬性進行預設拷貝
(2)如果開發者寫了有參構造函數,編譯器不再預設提供無參構造,但是會提供預設拷貝構造
(3)如果開發者寫了拷貝構造函數,編譯器不再提供其他構造函數,無參和有參都沒有
淺拷貝和深拷貝
這是經典面試題經常出現的案例,是一個常見的坑
淺拷貝:就是簡單的賦值拷貝
深拷貝:在堆區重新開闢一片記憶體,進行拷貝操作
註意事項:
-
如果涉及到空間開闢和釋放,淺拷貝容易發生記憶體重覆釋放的非法操作
-
需要自己寫一個拷貝構造函數,利用深拷貝去解決淺拷貝帶來的記憶體釋放問題
class person
{
public:
person(){}
person(int age,int height)
{
m_age = age; //這就是淺拷貝,就是簡單的賦值操作
m_height = new int(height); //這就是深拷貝,在堆區開闢一個新的記憶體
}
person(const person &p)
{
m_age = p.m_age;
//m_height = p.m_height //這是編譯器預設實現的,但是我們不要這樣的淺拷貝,會崩
m_height = new int(*p.m_height); //深拷貝,這樣不同的對象就有不同的堆區地址,釋放就不會發生重覆了
}
~person()
{
if(m_height != NULL)
{
delete m_height; //在構造函數手動開闢了堆區,就需要手動釋放
m_height NULL;
}
}
private:
int m_age;
int *height;
}
int main()
{
person P1(18,160);
person P2(P1); //將對象P1的屬性通過淺拷貝(拷貝構造函數)copy了一份賦值給對象P2了
//同時淺拷貝的還包括有參構造中開闢出來的堆區地址
//這時候如果沒有自己寫一個拷貝構造函數的話,P1和P2就擁有一樣的堆地址
//結束的時候P1和P2釋放就釋放了相同的堆地址,重覆釋放屬於非法操作,程式會崩
//所以需要在對象中自己寫一個拷貝函數,創建一個堆區,讓不同對象有不同的堆地址
//這就是利用深拷貝解決淺拷貝帶來的記憶體釋放問題
}
初始化列表
作用:用來初始化屬性,一般是在構造函數中初始化
語法:
構造函數():屬性(初值),屬性(初值),屬性(初值) { }
示例:
classs person
{
public:
/*
person(int a,int b,int c) //這是傳統賦值方式
{
m_A = a;
m_B = b;
m_C = c;
}
*/
/*
person():m_A(10),m_B(20),m_C(30) //初始化列表的方式賦初值
{ //不過這樣還是有點不夠靈活
}
*/
person(int a,int b,int c):m_A(a),m_B(b),m_C(c) //初始化列表的方式賦初值
{ //比上一個靈活一點
} //不過跟傳統方式相比,好處就是逼格高一點而已
private:
int m_A;
int m_B;
int m_C;
}
int main()
{
// person p1(10,20,30); //傳統賦初值的方式
// person P1; //利用初始化列表,創建的同時初始化完成
person P1(30,20,10); //初始化列表賦初值,比上一個靈活一點
//不過感覺跟傳統方式差不多吧,也就逼格高一點
}
類對象作為類成員
類中的成員可以是一個其他類的對象,該成員就是對象成員
註意事項:
-
創建此類對象的時候,先構造對象成員,再構造自身
-
先析構自身,再析構對象成員
示例:
class Phone //手機類
{
public:
Phone(string pName):m_phonename(pName)
{
}
private:
string m_phonename;
}
class Person //人類
{
public:
//第二個參數相當於:Phone m_phone = pName(隱式轉換法,編譯器隱藏轉換)
Person(string name,string pName):m_name(name),m_phone(pName)
{
}
private:
string m_name;
Phone m_phone; //先創建了Phone類,再有人類
}
int main()
{
Person p("張三","諾基亞"); //在賦值給了人類的同時,也賦值給了手機類
}
靜態成員
在成員變數和成員函數前面加一個關鍵字:static,就成了靜態成員
分類:
靜態成員變數:
-
所有對象共用一份同一份數據
-
在編譯階段分配記憶體
-
類內聲明,類外初始化
靜態成員函數:
-
所有對象共用同一個函數
-
靜態成員函數只能訪問靜態成員變數
根據上面三個特點,靜態成員(變數或者函數)不屬於某一個對象,所以:
-
public許可權的既可以通過對象進行訪問,也可以通過類名進行訪問
-
private無論如何類外訪問不了
示例: