C++ 面向对象的笔记总结

关于 C++ 面向对象的笔记总结

//类的定义

class <类名>

{

private:

    ...;

protected:

    ...;

public:

    ...;

};

 

注意:成员变量不能在声明时初始化(但静态常成员函数可以);不要与JAVA的混淆

 

 

//成员函数的定义

//先搞清楚声明定义的区别,声明初始化的区别

<返回类型> <类名>::<成员函数名>([参数表])

{

    ...;

}

 

//带默认值的成员函数

只有在声明时才指定默认值

默认参数从右到左逐渐定义

void f(int a1, int a2 =0, int a3 = 9);

 

//类的内联成员函数:

隐式implicit: 在类中同时声明与定义

显式explicit: inline

    类中声明: inline <返回类型> <函数名>([参数表]);

   

    类外定义: inline <返回类型> <类名>::<函数名>([参数表])   //inline可省

              {

                  ...

              }

//普通的内联函数:

声明:inline <返回类型> <函数名>([参数表]);

   

定义:inline <返回类型> <类名>::<函数名>([参数表])   //inline可省

      {

          ...

      }

 

或者声明定义写一起

inline <返回类型> 函数名([参数表]) {...}

 

//成员函数重载

相同的函数名,不同的参数列表(考虑:参数类型及参数个数)

 

//指针引用访问成员函数(或变量)

<类名> obj;

<类名> *p = &obj;  //可以不在声明时初始化

<类名> &ref = obj;  //必须在声明时初始化(赋值)

p->xxx

ref.xxx

 

//常成员函数:

只“读”不可“写”不能在函数中改变参数的值

类中声明: <返回类型> <函数名>([参数表]) const;

类外定义: <返回类型> <类名>::<函数名>([参数表]) const  //const 不可省

          {

              ...

          }

//构造函数与析构函数

构造函数:对象生成时调用(如声明, new 等)

当没有自己定义构造函数时,编译器提供一个缺省的无参构造函数

这个函数相当于  <类名>(){}

若自己定义了一个构造函数(不管有参无参),则编译器不再提供缺省的无参构造函数

基本数据类型(整型等)的成员变量,不会自动初始化(一个随机的值) //不要与JAVA混淆

 

析构函数:对象生命周期结束时调用

 

显式调用构造函数可以创建一个无名对象,多用于传参

void fun(Point pt);

fun(Point(x,y));

 

可以重载多个不同参数的构造函数

    可以带默认参数

    用带默认参数的构造函数重载时,注意避免二义性错误 Point() Point(int x = 0, int y = 0),调用Point()时就不知道调用的是哪一个

 

注意:以下两个语句的区别

<类名> obj;   //对象声明

<类名> obj();  //函数声明

//不同作用域范围对象的构造与析构顺序

局部对象:    按声明的先后顺序(可能出现多次生灭)

局部静态对象:按声明的先后顺序(只析构一次)

全局对象:    单文件(按声明先后);多文件(未知)

全局静态对象:单文件(按声明先后);多文件(未知);只析构一次

对象成员:    按声明先后(与冒号语法中初始化表达式的顺序无关)

对象生命周期结束时调用析构函数,顺序与构造函数的顺序相反

//堆对象

可以在用 new 分配空间时调用带参数的构造函数进行初始化 Point* p = new Point(1,2);

delete 时析构

delete p;

delete[] psArray;

先析构对象,再释放空间

 

//拷贝构造函数

注意“生成临时对象”的情况,很多情况下生成临时对象时会调用拷贝构造函数

 

1.       在函数参数表中用对象时(非引用)会产生一个临时对象,并将调用拷贝构造函数

void f(OBJ obj); OBJ obj; f(obj);

2.       在函数返回对象时,也会产生一个临时对象,并将调用拷贝构造函数

OBJ f() {OBJ obj; return obj;}

3.       在函数返回对象引用时,不产生临时对象,不调用拷贝构造函数

OBJ& f(){OBJ obj; return obj;}

//注意一般不能返回局部变量的引用,这里只作演示,说明返回引用时不产生临时对象

4.       在函数返回临时无名对象时,不再产生临时对象,不调用拷贝构造函数

OBJ f(){return OBJ();}

 

区别:浅拷贝 (只拷贝栈内存,不分配堆内存) 深拷贝(一般由自己定义)

 

编译器提供一个缺省的拷贝构造函数,它以浅拷贝的方式实现,若有动态成员(指针),赋值后在析构时会出现问题(同一块内存被释放了两次)

class A

{

private:

    int a;

public:

    A(){a = 0; cout << "construct with no param\n";}

    A(int x){a = x; cout << "construct with " << x << "\n";}

    A(const A& obj)

    {

        this->a = obj.a;

        cout << "copy construct with " << a << endl;

}

}

A f() {return A();}

A a1;            // construct with no param

A a2 = a1;        // copy construct with 0

A a3;            // construct with no param

a3 = a1;          //没有输出

A a4;            // construct with no param

a4 = f();          // f 中输出construct with no param= 没有输出

A a5 = f();         // f 中输出construct with no param= 没有输出

 

声明时

 

默认的赋值操作为浅拷贝

自己定义一个拷贝构造函数

Point::Point(const Point& point);

//无名对象的使用

1.       作为实参传递给函数

void fun(OBJ obj);   fun(OBJ());

2.       用来构造新对象

Point p = Point(1,2); <=> Point p(1,2);只调用一次构造函数,不调用拷贝构造函数

3.       初始化一个引用的声明 Point &ref = Point(3,4);

无名对象将在生产的某个时刻调用构造函数

//构造函数用于类型转换

class LeiXingZhuanHuan

{

public:

    LeiXingZhuanHuan(char * s) {

        cout << "construct" << endl;

    }

    LeiXingZhuanHuan(const LeiXingZhuanHuan& x) {

        cout << "copy construct" << endl;

    }

};

void f3(const LeiXingZhuanHuan& x){}

void f4(LeiXingZhuanHuan x){}

 

LeiXingZhuanHuan lx1 = LeiXingZhuanHuan("001");  //先调用构造,再调用拷贝构造

LeiXingZhuanHuan lx2 = "001";//直接调用构造,这里隐式调用了构造函数,进行类型转换;隐式调用构造函数用于类于转换时1.只会尝试含有一个参数的构造函数;2.不能存在二义性

    LeiXingZhuanHuan lx3("001");      //调用构造

    LeiXingZhuanHuan& lxref1 = lx1;    //不调用构造函数

    f3(LeiXingZhuanHuan("001"));      //调用构造函数

f4("004");                       //隐式调用了构造函数,相当于实现了类型转换

 

//作用域

理解函数原型作用域,块作用域,类作用域,文件作用域

 

//静态成员

通过 <类名>::XXX <对象>.XXX访问   (当然得符合访问权限public)

静态数据成员的初始化:

   在类中声明:   static int n;

   在类外初始化: int A::n = 100;             可以为公有或私有,但必须在全局范围内初始化(即不能在某个函数中),可以在普通成员函数中访问静态成员(读或写,但一般不在构造函数中初始化,因为它是各个类共享的)

 

class CHasStaticMember

{

    static const int c0 = 0;  //静态常量成员可以在类中声明时初始化,其他都不行

    static const int c1

    static int x;

};

 

//在类外初始化时不加static

const int CHasStaticMember:: c1 = 1;

int CHasStaticMember::x = 0;

 

静态成员函数只能访问静态成员变量

 

//友元

1.友元函数

      友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,这样就可以访问该类的私有成员了

类中声明:friend ... ,不管声明在哪个访问权限下都可以(public,protected,private)

类外定义:同普通函数(其实就是一普通函数,只是多个一项权利)

class B

{

public:

         friend void f(const B& obj);

         B():x(0){}

         B(int val) : x(val){}

protected:

private:

         int x;

};

 

void f(const B& obj)

{

         cout << obj.x << endl;

}

2.友元成员函数(让类B的某个成员函数可以访问类A的私有成员)

class A;

 

//这个B必须定义在A的前面,因为A中有用的B::f()的原型,但前面要声明一下A

class B

{

public:

         void f(const A& obj);

};

 

class A

{

private:

         int a;

public:

         friend void B::f(const A& obj);

};

 

void B::f(const A& obj)

{

         cout << obj.a << endl;

}

 

3.友元类

让类B成为A的友元类

class B;

class A

{

private:

         int a;

         friend class B;

};

class B

{

public:

         void f(const A& obj);

};

void B::f(const A& obj)

{

         cout << obj.a << endl;

}

 

//操作符重载

1.重载为友元函数:

class A;

 

A operator + (const A& obj1, const A& obj2);

 

class A

{

public:

    A(){a = 0;}

    A(int x){a = x;}

    void Display()

    {

        cout << a << endl;

    }

    friend A operator + (const A& obj1, const A& obj2);

private:

    int a;

};

 

A operator + (const A& obj1, const A& obj2)

{

    return A(obj1.a + obj2.a);

}

 

2.重载为成员函数:

class A

{

public:

    A(){a = 0;}

    A(int x){a = x;}

    void Display()

    {

        cout << a << endl;

    }

    A operator + (const A& obj)

private:

    int a;

};

 

A A::operator + (const A& obj)

{

    return A(obj.a + a);

}

 

3.特殊运行符的重载

    //前置

    A& operator++ ()

    {

        ++a;

        return *this;

    }

 

    //后置也可以写成 A operator++(int) ,这样 obj1++ = obj2 不会报错,但已经引用不到这个临时变量了

    const A operator++(int)  // obj++ = obj2 会报错,因为常量不能做左值

    {

        A t(*this);

        a++;

        return t;

    }

 

写成友元时

 

类中:

friend A& operator++ (A& obj);

friend const A operator++ (A& obj, int);

 

类外:

    A& operator++ (A& obj)

    {

        obj.a++;

        return obj;

    }

 

    const A operator++(A& obj,int)

    {

        A t(obj);

        obj.a++;

        return t;

    }

输出输入

ostream& operator << (ostream& o, 类名对象名)

{

    o << 对象.成员;

    return o;

}

 

istream& operator >> (ostream& i, 类名& 对象名)

{

    i >> 对象.成员;

    return i;

}

 

其他操作符还有 +=,-=,*=,/=,[], , , = ->,()

= :联系一下“深拷贝”与“浅拷贝”

[]: 返回类型& operator [] (int i); 当左值时要返回引用

->,[],(),= 只能重载为成员运算符

 

类型转换运算符

class Length

{

    int meter;

public:

    Length(int m) {meter = m;}

    operator float()

    {

        return (1.0 * meter / 1000);

    }

};

 

//"组合",一个类中的某个成员变量为某个类的对象,把该对象叫做 "子对象"

子对象的有参构造要使用冒号语法  <对象名>(参数),默认调用无参构造

有多个子对象时,按声明的先后顺序构造,与冒号语句的顺序无关

 

 

//继承

派生类的声明 class <派生类名> : <继承方式> <基类1>[, <继承方式> <基类2>,...]

 

继承方式    public      protected     private

public         public      protected     隔离

protected      protected   protected     隔离

private        private      protected     隔离

 

隔离的对象,可以通过基类中继承下来的可见的成员函数访问

 

 

继承分为单继承多继承

在继承与派生过程中

1. 吸收基类成员(不包括成员函数和析构函数)

2. 改造

    a. 继承方式

    b. 同名覆盖(注意:函数参数不同时为重载)

3. 添加新成员

 

//派生类构造与析构

派生类构造函数的调用顺序

1.基类 2.子对象(对象成员) 3.派生类

析构函数的调用顺序相反

 

如果子类的构造函数不显式调用基类的构造函数的话,会自动调用基类的无参构造函数

如果子类的构造函数显式调用了基类的一个有参构造函数,则不再调用基类的无参构造函数

显式调用基类的一个有参构造函数只能使用冒号语法,用类名

成员对象的构造也要使用冒号语法,但两者有区别,用变量名

 

//冒号语法

组合与继承的冒号语法的区别:

给合:使用对象名(变量名)

继承:使用类名

 

另外,常量和引用的初始化要用冒号语法,且只能使用冒号语法

 

也就是说使用冒号语法的时机有:

调用子类的有参构造函数,子对象,常量,引用的初始化

 

//一个单继承的例子

class Point

{

public:

    Point(int x = 0, int y = 0)

    {

        this->x = x;

        this->y = y;

    }

    int GetX()

    {

        return x;

    }

 

    int GetY()

    {

        return y;

    }

 

    void SetX(int x = 0)

    {

        this->x = x;

    }

 

    void SetY(int y = 0)

    {

        this->y = y;

    }

 

    void Display()

    {

        cout << "(" << x << ", " << y << ")";

    }

protected:

private:

    int x, y;

};

 

class Circle : public Point

{

public:

    //派生类的构造函数:注意调用基类构造函数与构造子对象的不同之处

    Circle(int x = 0, int y = 0, int r = 0) : Point(x,y), r(r){}

    int GetR()

    {

        return r;

    }

    void SetR(int r)

    {

        this->r = r;

    }

    //覆盖基类的Display();

    void Display()

    {

        //调用基类的Display();

        Point::Display(); cout << " r = " << r;

    }

    void Test)

    {

        //通过类型的函数访问从基类中继承下来的但被隔离的对象

        cout << GetX() << ", " << GetY() << ", " << r << endl;

    }

private:

    int r;

};

 

//一个多继承的例子

class B1

{

public:

    int b, b1;

    B1(int b = 0, int b1 = 1) : b(b), b1(b1){}

    void f()

    {

        cout << "f() in B1" << endl;

    }

    void f1()

    {

        cout << "f1() in B1" << endl;

    }

};

 

class B2

{

public:

    int b, b2;

    B2(int b = 0, int b2 = 2) : b(b), b2(b2){}

    void f()

    {

        cout << "f() in B2" << endl;

    }

    void f2()

    {

        cout << "f2() in B2" << endl;

    }

};

 

class D : public B1, public B2

{

public:

    int d;

    Point p;

    //从子类中继承下来的成员不能直接使用冒号语法,而是使用基类类名

    D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d), p(1,2)

    {

       

    }

 

    /*也可以这样写,但一般用前者

    D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d)

    {

        p = Point(1,2);

    }

    */

    void f()

    {

        cout << "f() in D" << endl;

        //调用子类中的方法

        B1::f();

        B2::f();

    }

 

    void Test()

    {

        cout << B1::b << endl;

        cout << B2::b << endl;

        cout << B1::b1 << endl;

        cout << B2::b2 << endl;

        cout << d << endl;

    }

};

 

D d(999,1,2,3);

d.Test();

d.B1::f();  //访问基类的成员函数

d.B1::f1();

d.B2::f();

d.B2::f2();

 

//派生类成员的标识与访问

 

基类名::成员名

基类名::成员函数([参数列表])

 

同名覆盖时,将覆盖所有基类的同名成员

若没有覆盖,多基类的同名成员也要通过::访问才能区分(否则会有二义性)

 

 

//虚基类:

 

     B0

B1        B2

     D1

 

使得继承时只保存一份拷贝

"D1 通过 B1 继承自 B0" "D1 通过 B2 继承自 B0" 的东西只保存一份拷贝

如果派生类中重新义同名函数(非重载),同样可以同名覆盖

class B0{};

class B1 : virtual public B0 {};

class B1 : virtual public B0 {};

class D1 : public B1, public B2 {};

实例:

class B0

{

public:

    B0(int x0 = 0) : v0(x0) { cout << "construct B0 with " << x0 << endl; }

    ~B0(){cout << "~B0\n";}

    int v0;

};

 

class B1 : public B0

{

public:

    B1(int x0 = 0, int x1 = 1) : B0(x0),v1(x1) { cout << "construct B1 with " << x0 <<", " << x1 << endl; }

    ~B1(){cout << "~B1\n";}

    int v1;

};

 

class B2 : public B0

{

public:

    B2(int x0 = 0, int x2 = 2) : B0(x0),v2(x2) { cout << "construct B2 with " << x0 <<", " << x2 << endl; }

    ~B2(){cout << "~B2\n";}

    int v2;

};

 

class D : public B1, public B2

{

public:

    D(int x0 = 0, int x1 = 1, int x2 = 2) : B1(x0, x1), B2(x0, x2) { cout << "construct D with " << x0 <<", " << x1 <<", " << x2 << endl; }

    ~D(){cout << "~D\n";}

};

 

int main()

{

    D d;

}

 

输出结果为:

construct B0 with 0

construct B1 with 0, 1

construct B0 with 0

construct B2 with 0, 2

construct D with 0, 1, 2

~D

~B2

~B0

~B1

~B0

 

class B0{};

class B1 : virtual public B0 {};

class B1 : virtual public B0 {};

class D1 : public B1, public B2 {};

 

输出结果为:

construct B0 with 0

construct B1 with 0, 1

construct B2 with 0, 2

construct D with 0, 1, 2

~D

~B2

~B1

~B0

 

//虚函数: 用于多态

声明: 类中: virtual 返回类型函数名([参数表]);

定义: 类外: 返回类型类名::函数名([参数表]){...}

注意: 只有在声明时才加 virtual

当基类用virtual声明成员函数为虚函数时,

其派生类的同名函数(重载函数除外)自动成为虚函数(前面可加virtual也可不加)

若在派生类中不重新定义,则直接简单继承基类的虚函数

若在派生类中重新定义,  则通过基类的指针访问可以表现为多态

  基类指针只能访问基类的函数或虚函数(其实也是在基类中声明的函数,只是可以表现多态性)

 

基类的指针,引用常用作函数形参实现多态

Void fun(B0* p);

Void fun(B0& p);

 

不用virtual 时,只调用基类的函数

 

工作原理:每个对象多了一个指针空间,指向类中的虚函数表

 

//虚析构函数:

声明:virtual ~类名();

定义:...

其派生类的析构函数自动变为虚析构函数

同样用基类的指针处理时,可以从子类开始析构,而不是调用基类的析构

最好将基类的析构函数声明为虚析构函数

//几个不同的""

虚基类:使多个来自同一虚基类的相同的东西只拷贝一份,其他同一般的继承,可以进行同名覆盖(覆盖之后若要调用父类的函数父类::函数名)

虚函数:既不覆盖,也不是重载,而是一个函数在不同类中表现出多态;一般通过父类的指针来表现

纯虚函数:基类不需要此功能,只为派生类服务

    声明: virtual 返回类型函数名(参数表) = 0;   

  

抽象类:包含有纯虚函数的类显式的用abstract标记的类

class 类名

{

public:

    virtual 返回类型函数名(参数表) = 0;

};

class 类名 abstract { ... };

 

若抽象类的派生类没有给出所有纯虚函数的实现,则该派生类仍为抽象类

 

 

一个虚基类和虚函数同时使用的例子:

     B0

B1        B2

     B3

 

B0* p;

若把 p 指向四个不同类的对象时,对B0,B1,B2,B3都能表现出多态,

则必须同时使用虚基类和虚函数

否则无法通过 p 调用 B3 的相应函数(编译无法通过)

 

//模板

函数模板

template <class T>     //可以有多个

返回类型函数名(参数表)

{

    ...

}

 

类模板

template <class T>     //可以有多个

class <类名>

{

    ...

};

 

声明对象  类名<类型> 对象名(参数);

 

类模板的对象引用,可作函数参数

类模板可以作基类

 

 

//异常处理

try

{

  throw

}

catch(...)

{

}

 

//几个""

常引用 const int& x; //多用于函数形参

常量   const int PI = 3.14;  // <=>  int const PI = 3.14;

常函数 void print(int x) const; //""不可"" ,在函数中不能对x值进行改变

 

常数据成员

    静态: 

        在类外初始化

            class A{static const int x;};  const int A::x = 100;   //注意,这里不能加static ,非常量的静态成员初始化时也一样

        在类内声明时初始化 class A { static const int x = 100; } // 只有静态常量数据成员才可以在类中声明时初始化

    非静态: 在构造时用冒号语法,使用初始化列表初始化

常成员函数

    Print(x) const;                                        //只“读”不“写”

//几个不同指针的区别

指针

指向的位置

所指向的位置是否可变

通过该指针是否可以改变所指向位置的内容

int*

&int

int* const

&int

const int*

&(const int)

const int* const

&(const int)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值