C++ 面向对象 从入门到入门

Author Avatar
Aryb1n 6月 04, 2017

突然发现自己不会使用C++,所以要入门一下
因为是入门,所以可能有很多错误
先写概念,然后补上例子

static

静态的成员变量属于类,也可以通过实例来调用,但静态可以直接使用ClassName::method()的方法就可以使用,就像是工具类,而静态的成员变量可以记录公共的信息,比如实例的个数
hack点: 静态成员变量使用前必须先初始化,后面我们会说怎么初始化,有两种方法,一种是c++ 11的新方法, 还有赋值与初始化的区别

  • 静态成员函数中不能调用非静态成员,因为非静态的属于实例
  • 非静态成员函数中可以调用静态成员,因为静态成员属于类

const (以后填坑)

在c++中const定义的变量一般是不分配内存的,和C语言中的#define。但是在c语言中const是默认分配的

const 有顶层const,底层const
修饰指针变量的时候有变量本身不可变变量指向的内容不可变

函数参数列表后 函数体前 这个位置加 const作用 (然后填)

指针

引用

构造函数 + hack点

这两种方式初始化有什么区别咩

#include<cstdio>
#include<iostream>
using namespace std;
class A{
    public:
        A():a(3){printf("a is %d\n", a);};
    private:
        int a;
};

class B{
    public:
        B(){printf("b is %d\n", b);};
    private:
        int b = 3;
};
int main() {
    A a1;
    B b1;
    return 0;
}

这个A和B中成员变量的初始化是不是没有区别啊,只是发现这个B中类内成员的这种初始化方法好像是C++ 11开始支持的
所以会报这个warning,然后我没加std=c++11,难道是编译器自己帮我加了?

15 col 17| warning: non-static data member initializers only available with -std=c++11

不过咩,注意以上两种都是初始化如果写成

class C{
    public:
        C(){c = 3;printf("c is %d\n", c);};
    private:
        int c;
};

这个就不是初始化了,这个c算是赋值,这里看起来没问题对吧,但在某些情况下有些成员变量就不能赋值,只能初始化(就是传说中的那种不能变的对象)比如

class C{
    public:
        C(){c = 3;printf("c is %d\n", c);};
    private:
         const int c;
};

这样做的话,就会报错

error: assignment of read-only member ‘C::c’

而必须采用上面A或者B的方法,看起来是A的方法兼容好一些,我以后就A这样子写吧
除了const外,reference也只能初始化,不能赋值.这也是指针和引用的有些不同

  • 引用必须被初始化,指针可以不
  • 引用初始化后不能改变其值
  • 不存在指向空值的引用

刚查资料,看到人家满满的证书,同样是大三了,我好菜啊,C++都不会,突然又想学算法了
https://www.liuchuo.net/about

继续
hack点: 静态成员变量不能类内初始化,因为静态的其实是属于类的,我们可以叫他类变量(可以这样子叫吗,反正我就这样子叫了),需要在类外初始化

#include<cstdio>
class A{
    public:
        A();
    private:
        static int a = 3;
};
int main() {
    A a;
    return 0;
}

这样子是会报error的

error: ISO C++ forbids in-class initialization of non-const static member ‘A::a’

看起来是只有conststatic才能类内初始化了

#include<cstdio>
class A{
    public:
        A(){puts("Hello A");};
    private:
        const static int a = 3;
};
int main() {
    A a;
    return 0;
}

我们加了个const果然通过了
所以static的成员变量要类外初始化

int A::a = 1;

友元

申明

  • 构造函数申明不能包含初始化参数列表
  • 派生类申明不能包含派生列表

final

final 可以防止继承发生(Java里面好像也是这样子吧)

静态类型 & 动态类型

  • 如果表达式不是指针或者引用的话,那他的动态和静态类型就是一样的
  • 指针或者引用的静态类型和动态类型不同这一事实正是C++支持多态的根本所在

虚函数

基类虚函数设计出来大部分是为了让子类来override实现多态的

hack点: 我们必须为每一个虚函数都提供定义
当某个虚函数通过指针或者引用调用的时候,编译器产生的代码直到运行时才能知道该调用哪一个(动态绑定),好吧,感觉这句话提到了N次

个人理解:
动态 => 运行时
静态 => 编译时
这里突然想到了动态链接库dll(Linux *.so)静态链接库lib(Linux *.a)

hack点: 只有虚函数能override,此时参数列表和返回值应该一致,相当于是基类规定了接口,派生类里你要照着子类的接口规范来override虚函数(还有好像是说,派生类里有virtual字样,子类override对应函数就可以不写virtual字样了), 那种参数列表不一样的就是重载啦,和我们这个木有关系,好像是重载是编译时就确定了的吧

只有类的普通成员函数可以定义为虚函数,全局函数及静态成员函数(类拥有)不能声明为虚函数。

多态

我理解多态主要是由虚函数来体现的

当我们使用基类(静态类型)的引用或者指针调用基类中定义的一个函数时候,我们并不知道这个函数真正作用的对象,他可能是一个基类对象也可能是一个派生类的对象.如果这个函数是虚函数,那么直到运行时才会决定到底执行哪个版本.判断的依据是引用或者指针所绑定的对象的真实类型.

综合上面,只有当

  1. 通过指针或者引用
  2. 来调用虚函数的时候
    才可能出现运行时绑定,即为动态和静态类型不同

注意下文提到的各种转换,如果没有特别强调,说的都是对象指针类型,不是对象类型

一定要注意这两个条件
如果函数不虚也可以哦…不虚的函数也是编译时候就确定了,就是看静态类型
一个栗子,学会了个单词

#include<cstdio>
#include<iostream>
using namespace std;
class Base {
    public:
        void nf(){printf("normal f from Base\n");};
        virtual void vf(){printf("virtual f from Base\n");};
};
class Derived : public Base { //我今天才知道这个词是派生
    public:
        void nf(){printf("normal f from Derived\n");};
        virtual void vf(){printf("virtual f from Derived\n");};
};
int main() {
    Derived d1;
    Base * b1 = &d1;
    b1 -> nf();
    b1 -> vf();
    return 0;
}

/*
 * normal f from Base   //静态
 * virtual f from Derived   //动态绑定
 */

如果用引用的话也是一样的结果

int main() {
    Derived d1;
    Base & b1 = d1;
    b1.nf();
    b1.vf();
    return 0;
}

/*
 * normal f from Base   //静态
 * virtual f from Derived   //动态绑定
 */

如果不是用指针或者引用的话,就像下面提到的对象直接赋值,仔细看啊,就比前面的例子少了一个引用符号,结果就很悲剧了!

int main(){
    Derived d1;
    Base b1 = d1;
    b1.nf();
    b1.vf();
    return 0;
}

/* 
 * normal f from Base => 静态
 * virtual f from Base => 静态
 */

就是这个厉害的派生对象就废掉了,就变成普通的鸡肋对象了,而且这个时候即便再强制赋值回来也没啥用了 其实是根本赋值不回来了… 继续往下看

int main() {
    Derived d1;
    Base b1 = d1;
    Derived d2 = b1;
    b1.nf();
    b1.vf();
    d2.nf();
    d2.vf();
    return 0;
}

因为是基类转为子类(其实就没有这种操作),所以会提示你强制转化,然后你准备碰个运气强制一波

Derived d2 = (Derived)b1;

这个时候就会报一万个error

error: no matching function for call to ‘Derived::Derived(Base&)

这个时候我们彻彻底底的相信了,这个对象类型直接赋值是没有传说中的多态出现的,如果我们还要把他弄回去,,还会报错

那我们试一下如果是指针呢,向下转型虽然是危险的,但是不是可行呢

int main() {
    Base * b1 = new Base;   // 对了记得用了指针要new一下,不然这就是个野指针了,访问到不可思议的内存就会
    Derived * d1 = (Derived *)b1;
    b1 -> nf();
    b1 -> vf();
    return 0;
}
/* 
 * normal f from Base
 * virtual f from Base
 */

还有这种操作? 确实是可以的,但是么有什么意义

好吧我只有一个疑问了,就是赋值来来回回Derived=>Base=>Derived,这样子一圈下来还能安好咩

int main() {
    Derived * d1 = new Derived;
    Base * b1 = d1;
    Derived * d2 = (Derived *)b1;
    d1 -> nf();
    d1 -> vf();

    b1 -> nf();
    b1 -> vf();

    d2 -> nf();
    d2 -> vf();

    return 0;
}

/* normal f from Derived
 * virtual f from Derived
 * normal f from Base
 * virtual f from Derived
 * normal f from Derive     => 安好
 * virtual f from Derived   => 安好
 */

好吧,果然还是安好的,其实早就应该想到指针这样丢来丢去,根本不会影响到内存中Derived实例的结构

其实上面的这个nf是与多态无关的,静态时候决定,就是用谁的指针,就是调用谁的函数,所以刚刚还偷偷试了下
即便这样子,还是能输出这个nf,大概这就是命(划掉,静态)吧,编译时候就决定好了的东西

int main() {
    Derived * d1 = NULL;
    d1 -> nf();
    d1 -> vf();
    return 0;
}

派生类向基类的隐式转换

hack点: 基类不能向派生类隐式转换,即使是派生=> 基本类 => 派生这样子的也不可以就是说原来是把一个派生类对象地址丢给了基本类的指针变量(这一步合理合情合法),再把这个变量丢给派生类对象指针也不行,即便他指的其实是派生类对象(这里是说隐式转换,我们可暴力的进行转换,就像我上面的例子,只是说隐式转换的话编译器不给你过)

当然必要的时候也可以强制向下转型? 虽然很危险.(人家都说危险,我也觉得它危险好了)

hack点: 这些奇怪的各种各样为了实现多态的转换之类的,都是指针或者引用的操作,所以直接对象直接赋值是没啥用的,比如:如果用一个派生类对象给一个基类对象初始化或者赋值,只有基类的那一部分会被复制,派生类部分会直接被忽略(切割掉)

继承 (挖坑,然后填)

多重继承

感觉像是接口,java里就不能多重继承

多继承

虚继承