1. 程式人生 > >C++學習筆記3_類.和相關函式

C++學習筆記3_類.和相關函式

1. 類
*在C++中,struct和class沒有明顯差別,不同C#,class一定要new(手動開闢記憶體)出來
struct Hero
{
char name[64];
int sex;
}
void print(struct Hero &h)
{
...
}
class AdvHero
{
public:
char name[64];
int sex;
void print()
{
cout<<name<<endl;
}
}
int main(void)
{
Hero h;
strcpy(h.name,"zhangsan");
//
AdvHero v;//不用new出來,c++中的new另有含義
strcpy(v.name,"lisi");
}
2. 建構函式:
class A
{
public:
int age;
char* Name;
A(int x)
{
age=x;
Name =(char*)malloc(100);
}
~A()
{
//解構函式沒有形參,當物件被銷燬前自動呼叫
if(name!=NULL)
{
free(name);
}
}
}
int mian()
{
A a(10);
}
//堆疊在a被宣告時,在棧中,開闢儲存a的記憶體,含有一個int大小的,和char*一個指標大小的空間,然後在堆中,開闢100個位元組的空間,用Name指向它。
//但是析構的時候,只會自動清除棧的東西,所以一定要記得釋放堆中的東西。
//棧是會被壓的!!

拷貝建構函式:
class Test
{
//顯式的拷貝建構函式,
Test(const Test &anthor)//const引用,就是不能改變anthor所代表的物件的任何東西。
{
//...
}
//=賦值操作符
void operator=(const Test &another)
{
//...
}
}
//和C#不同,C#是沒有拷貝建構函式的。
//
int main(void){
Test t1;
Test t2(t1);
//這句話,如果=號操作沒有過載,拷貝建構函式也沒有過載,那麼也相當於t2=t1;t1複製一份,給t2。
//就算Test沒有傳入引數為Test的建構函式,也能成功。能自動將成員值拷貝過去。
//因為有預設的拷貝建構函式
Test t3;
t3=t1;//這裡就不是呼叫構造函數了,而是呼叫=操作函式
//
Test t4=func();//**這裡也不會發生t4先構造,然後執行=操作,而是將func()返回的結果“轉正”
//
t3=func();//**這裡就會發生執行=操作了。而且func()返回的匿名物件會被析構掉。

}
******************************
//在C++中,struct和class沒有明顯差別。不同C#,class一定要new(手動開闢記憶體)出來。
//在classA a;這句中,已經呼叫了構造函數了。C#中,僅僅是宣告一個空的引用變數a。
//在a=b;呼叫了操作符號=函式。C#中,則是引用變數a來引用b引用的物件,a和b都是引用同一個物件。
//所以C++中,物件的傳參,引數返回(除非是返回堆中的物件引用),都是複製的操作,同時,複製的時候會呼叫預設的拷貝建構函式,出棧的時候,也會調解構函式。
//各種拷貝,析構,也是可以通過引用來優化的地方!
//這是C++和大多數語言的不同。
******************************
3. 深拷貝和淺拷貝
class Teacher
{
int m_id;
char*m_name;
public:
Teacher(int id,char *name)//這就算深拷貝了,淺拷貝的話,m_name的值和name是一樣,指向同一塊記憶體。
{
m_id=id;
int len = strlen(name);
m_name=(char*)malloc(len+1);//因為最後要以\0標誌字串結束,所以+1
strcpy(m_name,name);
}
//一般的淺拷貝建構函式
Teacher(const teacher&a)
{
mid=a.mid;
m_name=a.m_name;
}
//一般的淺拷貝建構函式
Teacher(const teacher&a)
{
//按Teacher(int id,char *name)來寫。
}
~Teacher()
{
if(m_name!=NULL)
{
free(m_name);//記得釋放堆上的記憶體
}
}
}
//*****free是不能重複對同一指標值(記憶體地址)執行兩次的。
//*****所以,見到類中,有指標成員,一定要特別留意,先看解構函式,會不會有可能free同一個記憶體地址;然後考慮要不要深拷貝。
//*****淺拷貝風險,就是有指標成員時,析構時釋放堆記憶體,可能會重複釋放記憶體。
4. 建構函式的初始化列表
class B
{
public:
B(A &a1,A&a2,int m):m_a1(a1),m_a2(a2),m_m(m)//常量成員的賦值,只能放在初始化列表中
{
//如果這使用m_a1=a1,顯然達不到呼叫A的建構函式的目的,只是調了A的=操作符函式
//如果這裡寫m_a1(a1),會語法錯誤,將m_a1當做了函式使用了
}
private:
A m_a1;
A m_a2;
int a=3;//很多編譯器不支援這種寫法,所以儘量要寫到建構函式中
const int m_m;//只能賦值一次,而且不能const int m_m=3;因為有些編譯器不支援
}
//在構造的時候,先執行A的構造兩次,然後執行B建構函式體;在析構的時候,先析構m_a1和m_a2,然後再執行B析構的函式體。

5. 靜態成員
void test()
{
int static a=10;//這句話只會在第一次執行的時候,給a賦值,因為a在執行初階段就放在靜態區了。
a++;
}


class A
{
public:
A()
{
//...
}
static int m_c;//也是在程式執行之初放在靜態區的
}
**************************************************************
int AA::m_c=100;//在宣告類的時候,就給靜態變數賦初值了。
*記得#ifndef #endif

void funtion()
{
AA:m_c=100;//不用寫成 int AA:m_c=100;這是在方法之外寫的。
}

6. this指標
//對於一個類,只有非靜態欄位才屬於這個物件(才在這個物件的記憶體中),static成員在靜態區,而且方法也不在這個物件的記憶體區域中。
//那為什麼a.getPrivateProperty()能獲得a的私有成員呢,但是getPrivateProperty()方法不在a的記憶體區域內???
class Test
{
private:
int n1;
public:
Test(int i)
{
n1=i;
}
int getn1()
{
return n1;
//等價於return this->n1;
}

int getn2() const //加個const,防止這個方法內改變this所指向的內容,相當於const Test * const this(指向常量的常指標,相當於常引用了)
{
}

static void print()
{//...}
}
Test a(10);
a.getn1();
Test::Print();
通過編譯後,變為:
struct Test
{
int n1;
};
void Test_initialize(struct Test *pThis,int i)
{
pThis->n1=i;
}
int Test_getn1(struct Test *pThis)
{
return pThis-<n1;
}
void Test_Print(){//.....}
Test a;
Test_initialize(&a,10);
Test_getn1(&a);
Test_Print();
//再次,在C++中,struct和class沒有明顯差別。

7. 返回物件本身,主要可以用於鏈式操作
class Test
{
private :
int a;
public:
&Text Add(Text &another)
{
this->a=this->a+another.a;
return *this;
}
}
//能達到每次連加都會改變自身,又不用複製。

8. 友元函式
class Point
{
private :
double x;
double y;
//
friend double PointDistance(const Point &p1,const Point &p2);
//也能friend double Calculater::PointDistance(const Point &p1,const Point &p2);
//Calculater類一定要在Point宣告之前宣告好
public:
Point(double x,double y)
{
this->x=x;
//...
}
double getX()
{
return this->x;
//以匿名的方式,返回x的複製值。
}
double getY(){ //...}
}
double PointDistance2(const Point &p1,const Point &p2)
{
//有getX(),getY()的操作,呼叫方法就有壓棧出棧的過程,效率比p1.x低多了。
}

如果在類中宣告有:
friend double PointDistance(const Point &p1,const Point &p2); 那麼就可以p1.x,使用私有成員了。就像PointDistance是類中的成員函式一樣。
**因為友元通常會破壞封裝性,所以不推薦使用。

9. 宣告。宣告的作用,就是預先告知有這個東西,其地方要使用它,必須要有預先告知。比如:
double PointDistance2(const Point &p1,const Point &p2);
如果這個函式宣告前,沒有通過標頭檔案,或者其他辦法,獲取到Point 的宣告,那麼編譯不過的。
正確的辦法:
class Point;//這個也是宣告
double PointDistance2(const Point &p1,const Point &p2);
****這也是C#和C++不同的地方,C#是宣告和實現放在一起,而且不分先後順序。

10. 友元類
class B;
class A
{
friend class B;
priviate : int a;
}
class B
{
private : int b;
public:
B& addA(const A &a)
{
this->b= this->b+a.a;
return *this;
}
}
**因為友元通常會破壞封裝性,所以不推薦使用。

11. 操作符過載
//
new和delete也是操作符,也能過載的
//
*通常會過載“=”號,用於深拷貝
*通常也會過載+-號,用於一些欄位的加減
class Complex
{
private:
int a;
int b;
public:
Complex operator +(const Complex &b)
{
Complex c;
c.a=this->a+b.a;
c.b=this->b+b.a;
return c;
}
//如果寫在外邊的話Complex operator +(const Complex &a, const Complex &b); 呼叫也可以Complex c3= operator+(c1,c2);

Complex& operator +=(const Complex &b)
{
c.a=this->a+b.a;
c.b=this->b+b.a;
return this;
}

//後++運算,加const,防止拼命++下去
const Complex operator ++(int)//要有個int填坑
{
Complex temp(*this);
this.a++;
this.b++;
return temp;
}
//為了能cout << complex,但這裡c1.operator<<(os);或者c1<<cout;太彆扭
//所以只能寫全域性了
ostream & operator <<(ostream &os)
{
os<<this->a<<this->b;
return os;
}
}
ostream & operator <<(ostream &os,Complex &c);//這樣就可以cout<<c了;


12. 任何傳入類的字串指標,都要考慮深拷貝和析構,否則如果外邊釋放了記憶體,那麼使用會出錯。

13. ***************************重寫無參構造,=操作符必要的注意事項***************************
等號操作符注意事項:
class A
{
public:
char*Name;
A(const A &a)
{
this->name=new char[strlen(a.Name)];
if(this->name!=NULL){
strcpy(this->Name,a.Name);
}
}
//遇到有成員指標是開闢堆記憶體的,一定要重寫=,重寫無參構造,重寫析構
A & operator=(const A &a)
{
if(this==*a) { return *a;}
//
if(this->name!=NULL)
{
delete[] this->name;
this->name = NULL;
}
//重新開闢
this->name=new char[strlen(a.Name)];
if(this->name!=NULL){
strcpy(this->Name,a.Name);
}
}
~A()
{
if(this->name==NULL)
{
delete[] this->name;
this->name = NULL;
}
}
}

14. []操作符過載,通常返回引用,因為a[i]應該既可以取,也可以修改才行。
char & operator[](const int &i)
{
return this->Name[i];
}

15. ()操作符過載
int main(void)
{
typeA a ;
a();//這裡是由於操作符過載,所以可以這樣
}

16. const引用和const函式
class A
{
public:
int getLenght1()//實際上會編譯為 int getLenght(int *const this),const指標,指向不可變
{

}
//
int getLenght2() const //實際上為const int *const this,相當於const引用,指向內容不可變,指向不可變 , 相當於const A &a
{

}
}

// 不能通過const引用修改變數的值
void function(const A &a) //為了保護a不在方法中被改變
{
a.getLenght1(); //這裡出錯,用高安全的引用作為實參,呼叫低安全形參的函式。
a.getLenght2(); //這裡才不會出錯
}