更多参考其他文档菜鸟文档、W3C、微软C++文档
类和对象基础
类和对象基本概念
#include <iostream>
using namespace std;
class CRectangle //定义一个类 { public: int w,h; int Area(); int Perimeter(); void Init(int w_,int h_); };
int CRectangle::Area(){ return w * h; } int CRectangle::Perimeter(){ return 2 * (w + h); } void CRectangle::Init(int w_,int h_){ w = w_; h = h_; } int main() { return 0; }
|
- private : 私有成员,只能在函数内访问 - public :公有成员,可以在任何地方访问 - protected:保护成员,以后再说 class className { private: 私有属性和函数 public : 公有属性和函数 protected: 保护属性和函数 };
class Man{ int nAge; char szName[20]; public: void SetName(char * szName) { strcpy(Man::szName,szName); } };
|
- 当前对象的全部函数、属性; - 同类其它对象的全部属性、函数;
class CEemployee { private: char szName[30]; public: int salary; void setName(char * name); void getName(char * name); void averageSalary(CEmployee e1,CEmployee e2); };
void CEmplotyee::setName(char * name){ strcpy(szName,name); } void CEmployee::getName(char * name){ strcpy(name,szName); } void CEmployee::averageSalary(CEmployee e1,CEmployee e2){ cout<<e1.szName; salary = (e1.salary + e2.salary)/2; } int main() { CEmployee e; strcpy(e.szName,"Tom1234567889"); e.setName("Tom"); e.salary = 5000; return 0; }
int main() { CEmployee e; strcpy(e.szName,"Tom1234567889"); e.setNName("Tom"); e.salary = 5000; return 0; }
“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
当一个程序移植到小内存的设备上时,会有例如将 szName[30] 改为 szName[5] 的需求,如果不在程序中设置为私有成员,那么程序中会有很多下面这样的语句: strcpy(e.szName,"Tom1234567889"); 此时,相关的语句也会报错,要一个一个修改,设置为私有后,我们就不会有上面的语句出现,可以通过如setName函数 来实现: e.setName("Tom12345678909887"); 这样,即便有这样的需求我们只需要将set函数做修改就行
|
成员函数的重载及参数缺省
#include<iostream> using namespace std;
class Location { private : int x,y; public: void init(int x=0,int y=0); void valueX(int val){x = val;} int valueX(){return x;} } int main(){ Location A,B; A.init(5); A.valueX(5); cout<<A.valueX(); return 0; }
class Location { private : int x,y; public: void init(int x=0,int y=0); void valueX(int val = 0){x = val;} int valueX(){return x;} } int main(){ Location A,B; A.init(5); A.valueX(); cout<<A.valueX(); return 0; }
|
构造函数(constructor)
构造函数是成员函数的一种,名字 与 类名 相同,可以有参数,不能有返回值(void 也不行)
作用:对对象进行初始化,如给成员变量赋初值
如果定义类时没有写构造参数,则编译器生成一个默认的无参数的构造函数
默认构造函数无参数,不做任何操作
类如果定义了构造函数,则编译器不生成默认的无参数的构造函数 对象生成时,构造函数自动被调用。对象一旦生成,就再也不能在其执行构造函数 一个类可以有多个构造函数
构造函数执行必要的初始化工作,有了构造函数,就不必专门写初始化函数,也不用担心忘记调用初始化函数。 对象没有被初始化,容易出错
class Complex{ private: double real,imag; public: void Set(double r,double i); };
Complex cl; Complexa * pc = newComeplex;
class Complex{ private: double real,imag; public: Complex(double r,double i = 0); }; Complex::Complex(double r,double i){ real = r;imag = i; } Complex c1; Complex * pc = new Complex; Complex C1(2); Complex c1(2,4),c2(3,5); Complex * pc = new Complex(3,4);
|
一个类可以有多个构造函数,只要这些类的参数个数或类型不同就可以,类似函数重载,也用于初始化
class Complex{ private: double real,imag; public: void Set(double r,double i); Complex(double r,double i); Complex(double r); Complex(Complex c1,Complex c2); }; Complex::Complex(double r,double i) { real = r; imag = i; } Complex::Complex(double r) { real = r; imag = 0; } Complex::Complex(Complex c1,Complex c2); { real = cl.real + c2.real; imag = cl.imag + c2.imag; } Complex c1(3); Complex c2(1,0); Complex c3(c1,c2);
|
构造函数在数组中的使用
#include <iostream>
using namespace std;
class CSample { int x; public: CSample(){ cout<<"Constructor 1 Called"<<endl; } CSample(int n){ x = n; cout<<"Constructor 2 Called"<<endl; } };
int main() { CSample array1[2]; cout<<"step1"<<endl; CSample array2[2] = {4,5}; cout<<"step2"<<endl; CSample array3[2] = {3}; cout<<"step3"<<endl; CSample *array4 = new CSample[2]; delete []array4; return 0; }
class Test{ pulic: Test(int n){} Test(int n,int m){} Test(){} } Test array1[3] = {1,Test(1,2)};
Test array2[3] = {Test(2,3),Test(1,2),1};
Test * Parray[3] = {new Test(4),new Test(1,2)};
|
复制构造函数
只有一个参数,即对同类对象的引用
形式: X::X(X &) X::X(const X &) 如果没有定义复制构造函函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
|
class Complex{ private: double real,imag; }; Complex c1; Complex c2(c1);
|
复制构造函数起作用的三种情况
总结三种情况:用一个对象初始化另一个对象、函数的形参是个对象、函数的返回值是对象
注意:对象间的赋值并不导致复制构造函数被调用
class CMyclass { public: int n; CMyclass(){}; CMyclass(CMyclass & c){n = 2 * c.n;} }; int main(){ CMyclass c1,c2; c1.n = 5; c2 = c1; CMyclass c3(c1); cout <<"c2.n="<< c2.n <<","; cout <<"c3.n="<< c3.n <<endl; return 0; }
|
常量引用参数
void fun(CMyclass obj_){ cout <<"fun"<<endl; }
void fun(const CMyclass & obj){ }
|
为什么要自己写构造函数????
类型转换构造函数
定义转换构造函数的目的是实现类型的自动转换
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
当需要的时候,编译系统自动调用转换构造函数 ,建立一个无名的临时对象(或临时变量)。
class Complex{ public : double real,imag; Complex(int i){ cout << "IntConstructor called"<< endl; real = i ; imag =0; } Complex(double r,double i){real = r; imag = i;} }; int main(){ Complex c1(7,8); Complex c2 = 12; c1 = 9; cout << c1.real<<","<<c1.imag <<endl; }
|
析构函数
名字与类名相同 ,在前面加‘ ~’ ,没有参数和返回值 ,一个类 最多只能有一个析构函数 。
析构函数对象消亡时象消亡时即自动被调用。用来在对象消亡前做善后工作,比如释放分配的空间 等。
如果定义类时没写析构函数,则编译器生成缺省析构函数 ,缺省析构函数什么也不做 。
如果定义了析构函数,则编译器就不在定义缺省的析构函数了
class String{ private : char * p; public: String(){ p = new char[10]; } ~ String(); } String ::~String(){ delete [] p; }
|
析构函数和数组
class Ctest{ public: ~Ctest(){ cout<<"destryctor called"<< endl;} }; int main() { Ctest array[2]; cout <<"End Main"<<endl; return 0; }
|
析构函数和运算符delete
Ctest * pTest; pTest = new Ctest; delete pTest;
pTest = new Ctest[3]; delete [] pTest;
|
析构函数在对象作为函数返回值返回后被调用
class CMyclass{ public : ~CMyclass(){cout <<"destructor"<<endl;} }; CMyclass obj; CMyclass fun(CMyclass sobj){ return sobj; } int main(){ obj = fun(obj); return 0; }
|
构造函数和析构函数什么时候被调用
实例:
class Demo{ int id; public: Demo(int i){ id = i; cout <<"id="<<id<<"constructed"<<endl; } ~Demo(){ cout << "id="<<id<<"destructed"<<endl; } };
Demo dl(1); void Fun(); void Func() { static Demo d2(2); Demo d3(3); cout<<"func"<<endl; } int main(){ Demo d4(4); d4 = 6; cout<<"main"<<endl; { Demo d5(5); } Func(); cout<<"main ends"<<endl; return 0; }
|
复制构造函数在不同编译器中的表现
class A{ public: int x; A(int x_):x(x_){ cout<<x<<"constructor called"<<endl; } A(const A&a){ x = 2 +a.x; cout<<"copy called"<<endl; } ~A(){ cout <<x<<"destructor called"<<endl; } };
Af(){ Ab(10); return b; }
int main(){ A a(1); a = f(); return 0; }
|
类类型作函数参数的三种方式
c++依然采用传值的方式传递参数
对象本身作为参数
- 使用对象本身作为参数时,形参时实参的一个拷贝,即调用了拷贝构造函数。最好自己定义一个明确的构造函数,以免发生不容易发现的错误。
- 多次调用拷贝构造函数,效率低
对象引用作为参数
- 推荐用这一种,容易理解使用,没有副作用,参考 引用 那里
对象指针作为参数
- 使指针指向实参对象,还是有指针空间的使用和释放,空间相对较小
this指针
c++翻译到c
起初是没有c编译器的,是将c翻译到c
class CCar{ public: int price; void SetPrice(int p); }; void CCar::SetPrice(int p) { price = p; } int main() { CCar car; car.SetPrice(20000); return 0; }
|
翻译为C语言,如下:
sturct CCar { int price; };
void SetPricr(struct CCar * this,int p) { this->pricr = p; } int main() { struct CCar car; SetPrice(&car,20000); return 0; }
|
C++中的成员函数到了C中就会成为全局函数,并且在参数中多出一项this指针,这个this指针含义是指向被作用的对象
this指针的作用: 指向成员函数所作用的对象
非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针
class Complex{ public: double real imag; void Print(){ cout<< real<<","<<imag; } Complex(double r,double i):real(r),imag(i) { } Complex AddOne(){ this->real ++; this->Print(); return * this; } }
int main() { Complex c1(1,1),c2(0,0); c2 =c1.AddOne(); return 0; }
|
class A { int i; public: void Hello(){ cout << "hello"<<endl; } } int main() { A*p = NULL; p->Hello(); }
class A { int i; public: void Hello(){ cout << i << "hello"<<endl; } int main() { A*p = NULL; p->Hello(); }
因为静态成员函数并不具体作用于某个对象 因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数
|
“ : ”在c++中的作用
对目标x 初始化
#include<iostream>
using namespace std;
class A{ public: const int x = 100; A(int x_):x(x_){ cout<<x<<"constructor called"<<endl; } }; const int abc = 0; A f(){ A b(10); return b; }
int main(){ A a(10); cout << a.x << endl; return 0; }
|
在初始化列表中是对变量进行初始化,而在构造函数内是进行赋值操作。
两都的差别在对于像const类型数据的操作上表现得尤为明显。我们知道,const类型的变量必须在定义时进行初始化,而不能对const型的变量进行赋值,因此const类型的成员变量只能(而且必须)在初始化列表中进行初始化
其他参考文章:https://blog.csdn.net/chw1989/article/details/7480375
静态成员变量
静态成员
在定义前面加了 static 关键字的成员,本质是全局变量
class CRectengle { private: int w ,h; static int nTotalArea; static int nTotalNumber; public: CRectangle(int w_,int h_); ~CRectangle(); static void PrintTotal(); }
|
静态的成员变量,是所有该类的对象共享的,而普通变量是只有对象自己能用。
如: class CMclass{ int n; static int s; }; sizeof(CMyclass);
|
CRectangle::PrintTotal();
CRrctangle r; r.PrintTotal();
CRectangle*p = &r; p->Printotal();
CRectangle &ref = r; int n = ref.nTotalNumber;
|
- 引入的意义,把和类紧密相关的内容能写到类中去,使得成为一个整体,便于理解维护。
比如:记录与类相关的数据,就可以用静态变量,放到类里
class CRectangle { private: int w,h; static int nTotalArea; static int nTotalNumber; public : CRectangle(int w_,int h_); ~CRectangle(); static void PrintTotal(); }
Crectangle::CRectangle(int w_,int h_) { w = w_; h = h_; nTotalNumber ++; nTotalArea += w*h; }
CRectangle::~CRectangle() { nTotalNumber --; nTotalArea -= w*h; }
void CRectangle::PrintTotal() { cout<<nTotalNumber<<","<<nTotalArea<<endl; }
int CRectangle::nTotalNumber=0;
void CRectabgle::PrintTotal() { cout <<w<<","<<.... }
|
-
缺陷
复制构造函数生成对象初始化,就导致会存在漏洞
-
临时对象消亡时调用析构函数,影响数值
-
要自己写一个复制构造函数,包含对静态变量的操作
静态变量和常量
共同点
区别
- (本质)static是类型引用,const是实例引用。
- (赋值)静态变量的值在运行时可以更改赋值,而常量的值是不可改变的,运行一开始已经固定,之后修改会报错。
- (内存)静态变量存放在全局数据区中,伴随着这个程序流程,能将此变量的值保留到下次调用,不过数据过大的静态变量有可能造成内存泄露。 而const常量算是一个普通的只读变量,随函数结束而结束。在C里,const常量总是会分配内存,位于只读数据段。在C++,如果const常量在没有声明为extern,那么就是一个编译时的符号,不占用内存。
成员对象和封闭类(enclosing)
class CTyre //l轮胎类 { private: int radius; int width; public: CTyre(int r,int w):radius(r),width(w){} };
class CEngine //引擎类 { };
class CCar //这个类就是封闭类 { private: int price; CTyre tyre; CEngine engine; public: CCar(int p,int tr,int tw){}; }; CCar::CCar(int p,int tr,int w):price(p),tyre(tr,w) { };
int main() { CCar car(20000.17,225); return 0; }
|
- 封闭类构造函数和析构函数的执行顺序
- 封闭类对象生成时,先执行所有对象成员的构造函数,然后执行封闭类的构造函数。
- 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
- 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。
先构造的后析构,先初始化后消亡
class CTyre { public: CTyre(){cout << "CTyre contructor" <<endl;} ~CTyre(){cout << "CTyre destructor" <<endl;} }; class CEngine { public: CEngine(){cout << "CEngine contructor" <<endl;} ~CEngine(){cout << "CEngine destructor" <<endl;} }; class CCar { private: CEngine engine; CTyre tyre; public: CCar(){cout<<"CCar contructor"<<endl;} ~CCar(){cout << "CCar destructor" <<endl;} }
int main() { CCar car; return 0; }
|
-
封闭类的复制构造函数
class A { public: A(){cout <<"default"<<endl;} A(A & a){cout <<"copy"<<enld;} }; class B(A a;);
int main() { B b1,b2(b1); return 0; }
|
常量对象、常量成员、常引用
-
如果不希望改变某个对象的值,即可以再定义对象前加const关键字
class Demo { private: int value; public: void SetValue(){} }; const Demo Obj;
|
-
在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数
-
常量成员函数执行期间不应修改其所作用的对象 。因此,在常量成员函数中不能修改成员变量的值( 静态成员变量除外),也不能调用同类的非常量成员函数 (静态成员函数除外)
class Sample { public: int value; void GetValue() const; void func(){}; Sample(){} }; void Sample::GetValue() const { value = 0; func(); }
int main() { const Sample o; o.value = 100; o.func(); o.getvalue(); return 0; }
|
常量成员函数的重载
class CTest { private: int n; public: CTest(){n=1;} int GetValue() const {return n;} int GetValue() {return 2*n;} };
int main() { const CTest objTest1; CTest objTest2; cout << objTest1.GetValue()<<","<<objTest2.GetValue(); return 0; }
|
常引用
class Sample { ... };
void PrintfObj(const Sample & o) { ..... }
|
友元
友元函数
一个类的友元函数可以访问该类的私有成员
友元函数并不是成员函数,友元函数可以是一个全局函数,当把这个全局函数声明为友元的时候,就可以访问该类的私有成员了
class CCar; class CDriver { public: void ModifyCar(CCar *pCar); }; class CCar { private: int price; friend int MostExpensiveCar(CCar cars[],int total); friend void CDriver::ModifyCay(CCAr * pCar); } void CDriver::ModifyCar(CCar * pCar) { pCar->price += 1000; }
int MostExpensiveCar(CCar car[],int total) { int tmpMax = -1; for(int i = 0;i<total; ++i) { if(cars[i].pricr > tmpMax) { tmpMax = cars[i].price; } } return tempMax; }
int main() { return 0; }
|
class B { public: void function(); };
class A { private: int num; friend void B::function(); };
|
友元类
如果A是B的友元,那么A的成员函数可以访问B的私有成员
class CCar { private: int price; friend class CDriver; };
class CDriver { public: CCar myCar; void ModifyCar(){ myCar.price += 1000; } }; int main(){return 0;}
|