c++入门

C++

参数初始化

为 const 成员变量初始化

参数初始化顺序与初始化表列出表量的顺序无关,参数初始化顺序只与成员变量在类中声明的顺序有关

class book {
    public:
    	book(char* a, double p);
    private:
    	const char* title;
    	double price;
};

book::book(char *a, double p):title(a),price(p){
    this.title = a; // error
}

限制对象的创建

防止没有对数据进行初始化

class book
{
public:
    book(char *a, double p);
    void setprice(double a);
    double getprice();
    void settitle(char* a);
    char * gettitle();
    void display();
private:
    book(){}	// 将无参构造函数声明为pirvate,强制创建时给出参数。也可直接省略
    double price;
    char * title;
};

转型构造函数

class student
{
public:
    student(){}
    student(char * n){name = n;}
    explicit student(char * n){name = n;}  // 此关键字用于强制关闭隐式转换
private :
    char * name;
};

void fun(student s); //函数声明

fun("user_name");  // 强制转换,将字符串转为 student 对象。隐形转换

拷贝构造函数

系统会自动地为类生成一个拷贝构造函数,自动生成的拷贝构造函数功能简单,只能将源对象的所有成员变量一一复制给当前创建的对象。

// 参数必须是引用对象
book(book &b);
book(const book &b);
// 可以有其他参数,但必须给出默认值
book(const book &b, int price=100);

当对象中含有指针类型的元素时,默认直接将指针值赋给它。应当手动创建新的对象。

class Array
{
    public:
    	Array();
    	Array(const Array &arr);
    private:
    	int* nums;
};

void Array::Array(const Array &arr, int len=10) {
    this.nums = new int[10];
    for (int i = 0; i < len; ++i) {
        this.nums = arr.nums[i];
    }
}

const 常成员函数

const object 只能调用const函数,non-const object 可以调用const和non-const方法

常成员函数可以访问类中的任何成员变量,但是不能修改任何成员变量。

普通的成员函数不能访问常对象的成员变量,其它的成员变量都可以访问,普通的成员函数可以修改的成员变量有非 const 成员变量,一旦加上了 const 关键字加以修饰,初始化完成后就不能被修改了。

const 成员函数是不能调用类中非 const 成员函数的。

class book
{
public:
    book(){};
    book(char* a, double p = 5.0);
    void setprice(double a);
    void settitle(char* a);
    double getprice()const;   
    char * gettitle()const;
private:
    double price;
    char * title;
};

double book::getprice()const
{
    return price;
}

char * book::gettitle()const
{
    return title;
};

常成员函数可以重载

当成员函数的const和non-const版本同时存在时,const object只能调用const版本函数,non-const object只能调用non-cost版本。

int getval(int val){
    return val+3;
}
 
int getval(int val) const{
    return val - 3;
}
/*
可以出现在一个class中,互为重载
规则是 常对象(实例化时必须用const进行修饰)调用 常成员函数, 普通对象调用 普通成员函数
*/

const 对象

const 类名 对象名(实参名);
类名 const 对象名(实参名);

一旦将对象定义为常对象之后,该对象就只能调用类中的常成员函数了

常对象中变量不能被修改,但被 mutable 修饰后可用常成员函数修改。

class Class
{
public:
    mutable int num;
    void set_num(int n) const;
};

void Class::set_num(int n) const {
    this.num = n;
}
const Class myClass;
myClass.set_num(10) // 通过常成员函数修改常对象中被mutable修饰的变量

友元

可以访问本对象的私有属性

友元声明是单向的,同时要注意声明与定义的顺序。

class time
{
public:
    friend date; // 将date类设置为友元类
    friend void book::display(); // 将某个函数设为友元
};

oop

继承

enum language{cpp, java, python,javascript, php, ruby};

class Book
{
public:
    Book(){}
    Book(double price):price(price){}
    
    void setprice(double a);
    double getprice()const;
    void settitle(char* a);
    char * gettitle()const;
    void display();
private:
    double price;
    char * title;
};

class CodingBook: public Book
{
public :
    CodingBook(){}
    CodingBook(language lang, int price):Book{price}{  // 显示调用父类的构造函数
    }
    
    void setlang(language lang);
    language getlang(){return lang;}
private:
    language lang;
};

不可继承的:

  1. 基类的构造函数、析构函数和拷贝构造函数。
  2. 基类的重载运算符。
  3. 基类的友元函数

权限:

访问 public protected private
同一个类 yes yes yes
派生类 yes yes no
外部的类 yes no no

继承方式只影响外界(包括子类的子类)通过子类对父类成员的访问权限。

  1. public继承,父类成员的访问权限全部保留至子类;
  2. protected继承,父类public成员的访问权限在子类中降至protected;
  3. private继承,父类public、protected成员的访问权限在子类中均降至private。

1) public继承方式

  • 基类中所有 public 成员在派生类中为 public 属性;
  • 基类中所有 protected 成员在派生类中为 protected 属性;
  • 基类中所有 private 成员在派生类中不可访问。

2) protected继承方式

  • 基类中的所有 public 成员在派生类中为 protected 属性;
  • 基类中的所有 protected 成员在派生类中为 protected 属性;
  • 基类中的所有 private 成员在派生类中仍然不可访问。

3) private继承方式

  • 基类中的所有 public 成员在派生类中均为 private 属性;
  • 基类中的所有 protected 成员在派生类中均为 private 属性;
  • 基类中的所有 private 成员在派生类中均不可访问。

复合

template <class T>
class queue {
protected:
    deque<T> c; // 组合操作
}

委托

Delegation / Composition by reference

// pimpl
class String {
public:
    void fun(); // 此处只是对外的接口,具体的实现由指针指向的对象实现
private:
    stringRep* rep; // 创建对象时没要创建出引用目标,在需要时才创建
}

改变访问属性

enum language{cpp, java, python,javascript, php, ruby};

class book
{
public:
    void setprice(double a);
    double getprice()const;
    void settitle(char* a);
    char * gettitle()const;
    void display();
private:
    double price;
    char * title;
};

class codingbook: public book
{
public :
    void setlang(language lang);
    language getlang(){return lang;}
private:
    language lang;
    using book::setprice;  // 访问属性改为 private
};

继承下的构造函数

默认调用父类的无参构造函数,

注意:保证父类有无参构造函数,或显性调用

#include<iostream>
using namespace std;

class base
{
public:
    base(){
        x = 0; y = 0; 
        cout<<"base default constructor"<<endl;
    }
    base(int a, int b){
        x = a; 
        y = b; 
        cout<<"base constructor"<<endl;
    }
private:
    int x;
    int y;
};

class derived: public base
{
public:
    derived():base(){	// 调用父类的构造函数
        z = 0; 
        cout<<"derived default constructor"<<endl;
    }
    derived(int a, int b, int c):base(a,b){		// 调用父类的构造函数
        z = c; 
        cout<<"derived constructor"<<endl;
    }
private:
    int z;
};

int main()
{
    derived A;
    derived B(1,2,3);
    return 0;
}

virtual

non-virtual void fun();
virtual virtual void fun(); 可以被子类重新定义
pure virtual virtual void fun() = 0; 一定要被子类重新定义

虚基类

解决菱形继承的问题,D会得到两份A的属性。使用 D.B::varD.C::var 指向的是同一个变量。

使用virtual继承后则没有这个问题

graph TB A --继承--> B A --继承--> C B & C --继承--> D
#include <iostream>
using namespace std;

class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class B: virtual public A		// 使用 virtual 关键字
{
public:
    void sety(int a){y = a;}
    int gety(){return y;}
private:
    int y;   
};

class C: virtual public A		// 使用 virtual 关键字
{
public:
    void setz(int a){z = a;}
    int getz(){return z;}
private:
    int z;
};

class D: public B, public C
{
    //......
};

int main()
{
    D test;
    test.setx(10);
    cout<<test.getx()<<endl;
    return 0;
}

多态

  • 必须存在继承关系;
  • 继承关系中必须有同名的虚函数;
  • 存在基类类型的指针或引用,通过该指针或引用调用虚函数。

virtual 关键字仅用于函数声明,如果函数是在类外定义,则不需要再加上 virtual 关键字了。

class base
{
public:
    virtual void display(){cout<<"I'm base class!"<<endl;}
};

class derived: public base
{
public:
    virtual void display(){cout<<"I'm derived class!"<<endl;}  // 派生类中的 virtual 可省略
};

int main()
{
    base * p;
    derived test;
    p = &test;
    p->display();	// 输出 I'm derived class!
    return 0;
}
// 如果没有virtual关键字则输出 I'm base class!

虚函数表

C++ 通过虚成员函数表 vtable 实现多态,虚函数表中存储的是类中虚函数的入口地址。普通类中是没有虚函数表的,只有在具有虚函数的类中(无论是自身添加的虚函数还是继承过来的虚函数)才会具有虚函数表。通常,虚函数表的首地址将会被存入对象的最前面(在 32 位的操作系统中,存储地址是用 4 个字节,因此这个首地址就会占用对象的前四个字节的空间)。对象的最前端是 vtable,虚函数表的内容是函数的入口地址。

之后定义了一个基类类型的指针 p,当通过 p 调用虚函数 v1 或 v2 时,系统会先去 p 所指向的对象的前四个字节中寻找到虚函数表地址,之后在内存中找到该虚函数表,然后在表中找到对应函数的入口地址,就可以访问这个函数。

当 p 指针指向的是基类对象时,基类的虚函数表将会被访问,基类中虚函数将会被调用。当 p 指针指向的是派生类对象时,访问的是派生类的虚函数表,派生类的虚函数表中存的是派生类中的虚函数入口地址,因此调用的是派生类中的虚函数。

使用多态会降低程序运行效率,使用多态的程序会使用更多的存储空间,存储虚函数表等内容,而且在调用函数时需要去虚函数表中查询函数入口地址,这会增加程序运行时间。在设计程序时,程序设计人员可以选择性的使用多态,对于有需要的函数使用多态,对于其它的函数则不要采用多态。

虚析构函数

class base
{
public:
    base();
    ~base();
private:
    int * a;
};

class derived: public base
{
public:
    derived();
    ~derived();
private:
    int * b;
};

base::base()
{
    cout<<"base constructor!"<<endl;
    a = new int[10];
}

base::~base()
{
    cout<<"base destructor!"<<endl;
    delete[] a;
}

derived::derived()
{
    cout<<"derived constructor!"<<endl;
    b = new int[1000];
}

derived::~derived()
{
    cout<<"derived destructor!"<<endl;
    delete[] b;
}

int main()
{
    base* p;
    p = new derived;
    delete p;
    return 0;
}

// 执行结果:
// base constructor!
// derived constructor!
// base destructor!

// 因为派生类的析构函数未被调用,将基类的析构函数声明为virtual即可

抽象基类和纯虚成员函数

它无法实例化,也即无法用于创建对象。

派生类必须定义所有虚函数,否则其也是抽象类。

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

dynamic_cast

使用 static_cast 可以实现类型强制转换,可以将基类、派生类相互转换。但是,没有安全检查。例如:当派生类中定义了基类中没有的函数,调用强转来的对象的函数会引起错误。dynamic_cast会对这种情况进行检查。如有错误,返回0。

class base
{
public :
    virtual void m(){cout<<"m"<<endl;}
};

class derived : public base
{
public:
    void f(){cout<<"f"<<endl;}
};


int main()
{
    derived * p;
    p = dynamic_cast<derived *>(new base);
    if(p)
    {
        p->m();
        p->f();       
    }
    else
        cout<<"Convert not safe!"<<endl;
    return 0;
}
// Convert not safe!
// 因为是不安全的,所以p的值为0

dynamic_cast<derived *>(new base)	// 返回 0
dynamic_cast<base *>(new derived)	// 返回 base 对象

任意两个不相关的多态类类型之间也不能转换

typeid

操作符 typeid 返回的是一个 type_info 类(用于描述数据类型的一个系统类)对象的引用。这个操作符可以用于表达式和类型名(包括自定的数据类型,比如类)。

#include <typeinfo>

class base
{
public :
    virtual void m(){cout<<"base"<<endl;}
};

class derived : public base
{
public:
    void m(){cout<<"derived"<<endl;}
};

base * p = new derived;
表达式
typeid(p) == typeid(base*) true
typeid(p) == typeid(derived*) false
typeid(*p) == typeid(base) false
typeid(*p) == typeid(derived) true

模板

template< class T, class S, class R>
class test
{
public:
    S fun( R r);
private:
    T x;
};

模板的类参数可以有一个或多个,同时也可以有普通数据类型参数,称为函数式参数。

template< class T , int S>
class array
{
public:
    array();
    T & operator[]( int );
    const T & operator[] ( int )const;
    int getlen()const{ return length; }
    ~array();
private:
    int length;
    T * num;
};

template< class T , int S>
array<T, S>::array()
{
    num = new T[S];
    length = S;
}

array<int, 100> A;
array<string, 10> S;
// 只提供一个参数是不允许的
posted @ 2022-06-16 09:24  某某人8265  阅读(23)  评论(0编辑  收藏  举报