c++随笔-5

类基础

// 定义类
class CAnimal {   // 类关键字 class,类名 CAnimal
public:           // 成员访问控制符,public 表示对其他类可见
    CAnimal(){}   // 无参构造函数,创建类对象时自动调用,注意构造函数名即为类名,无返回值
    ~CAnimal(){}  // 析构函数,类对象销毁时自动调用,注意析构函数名即为 ~类名,无返回值

    void SetName(const std::string &name) {  // 类成员函数
        this->m_name = name;   // m_name = name 也可以
    }
private:  // private 表示对其他类不可见
    std::string m_name;   // 类成员变量
};   // 注意结尾分号,class XXX {}; 这是一个空类,编译时自动创建无参构造和析构函数

int main () {
    CAnimal animal;    // 类对象声明(栈)
    animal.SetName("cat");   // .(点),栈对象会引用对象调用类成员方式
    CAnimal &refAnimal = animal;
    refAnimal.SetName("cat2"); 
    CAnimal *panimal = new CAnimal;  // 类对象声明(堆或自由存储区)
    panimal->SetName("dog");  // ->(箭头),指针对象调用类成员方式
    delete panimal;

    return 0;
}
  • 面向过程编程(函数式编程)和面向对象编程(OOP)是两种不同的编程思想
  • OOP通过对实际事物的特征进行分析和抽象,形成不同事物特征的集合,c++中对于这种现象提供类来描述
  • 类定义好后在成员函数里,编译器默认会将类自己(this)作为指针参数传递进去,使用 this 指针表示,实际上类访问内部成员时不添加this也可以调用
  • c++的访问控制符有三种,public(公有的) private(私有的) protected(受保护的)
  • 不写访问控制符时,c++的类默认访问权限为私有的,struct 默认访问权限为公有的

函数重载

void Print() {
    std::cout << "Print()" << std::endl;
}
void Print(int, int) {
    std::cout << "Print(int, int)" << std::endl;
}
void Print(int, double) {
    std::cout << "Print(int, double)" << std::endl;
}
void Print(double, int) {
    std::cout << "Print(double, int)" << std::endl;
}

int main(int argc, char *argv[])
{
    // 函数重载规则
    // 1. 函数名称相同
    // 2. 函数形参不同(包括:形参数量、形参类型、形参排列顺序等)
    // 3. 函数返回值不作为函数重载的判断依据

    // 重载函数匹配规则
    // 1. 精确匹配:无强制或隐式类型转换,数组做函数参数退化为指针等情况
    // 2. 提升匹配:自动隐式转换(如bool 到 int、char到int、short 到int、int 到 double、float 到 double等等)
    // 3. 强制转换调用某个重载函数
    // 4. 无匹配函数
    // 5. 注意函数参数设置,防止出现函数调用二义性(有多个满足条件的调用,编译器不知道调用哪个)

	// 实际写的函数经过编译后函数名称根据情况,c++编译器会进行命名倾轧(即添加一些字符进行改变),不同重载函数最终在编译时也是不同的函数名称

	// 运算符重载
    // 1. c++中绝大多数运算符也可以重载,使用 operator 关键字,不能创建新运算符,重载需遵守运算符规则
	// 2. 不能重载的运算符 .(成员运算符) .*(成员指针运算符) ::(作用域运算符) ?:(条件运算符) sizeof(sizeof运算符) typeid(一个RTTI运算符) 四个强制类型转换运算符
    // 3. 重载运算符 () [] -> 或 = 时,只能重载为成员函数,不能重载为全局函数

    Print();
    Print(1, 1);
    Print(1, 4.0);
    Print(4.0, 1);
    return 0;
}

输出结果:
Print()
Print(int, int)
Print(int, double)
Print(double, int)


类成员变量初始化

class CAnimals {
public:
    CAnimals()
        : m_name("cat"){  // 无参构造,使用初始化列表赋值,这样比写在函数中初始化时间早,且 const 修饰的成员变量赋值不能写在函数中
        std::cout << "CAnimals()" << std::endl;
    }
    CAnimals(const std::string &name)
        : m_name(name){  // 有参构造
        std::cout << "CAnimals(const std::string &name)" << std::endl;
    }
    CAnimals(const CAnimals& other) {  // 拷贝复制构造,对象和对象之间的拷贝复制
        this->m_name = other.m_name;
        std::cout << "CAnimals(const CAnimals& other)" << std::endl;
    }
    CAnimals& operator = (const CAnimals& other) {    // 对象间的赋值运算(对赋值运算符 = 重载)
        if (this == &other)   // 避免自赋值
            return *this;
        this->m_name = other.m_name;
        std::cout << "CAnimals& operator = (const CAnimals& other)" << std::endl;
        return *this;
    }
    ~CAnimals() {
        std::cout << "~CAnimals" << std::endl;
    }

private:
    std::string m_name;  // std::string m_name = "";
};

int main(int argc, char *argv[])
{
    std::cout << "Create object a: " << std::endl;
    CAnimals a;
    std::cout << "Create object b: " << std::endl;
    CAnimals b("dog");
    std::cout << "Create object c: " << std::endl;
    CAnimals c(a);
    std::cout << "Create object d: " << std::endl;
    CAnimals d = b;
    std::cout << "Create object e: " << std::endl;
    CAnimals e;
    e = d;
    // 析构顺序为 e d c b a,与构造顺序相反
    return 0;
}

输出结果:
Create object a:
CAnimals()
Create object b:
CAnimals(const std::string &name)
Create object c:
CAnimals(const CAnimals& other)
Create object d:
CAnimals(const CAnimals& other)
Create object e:
CAnimals()
CAnimals& operator = (const CAnimals& other)
~CAnimals
~CAnimals
~CAnimals
~CAnimals
~CAnimals

  • 函数成员变量初始话在较新c++标准里可以直接在定义时赋值初始化
  • 类中没有显示写构造析构函数时、编译器默认会生成(默认无参构造,析构,拷贝复制构造和赋值运算函数,c++11及以上标准多了右值赋值)
  • 在构造函数中初始化,如果类中存在构造函数,编译器就不会自动生成默认无参构造,若需要无参构造就编码写出来
  • 函数形式参数不涉及到修改的尽量添加 const 修饰
  • 拷贝复制构造和赋值运算需要手动去重新实现的情况:类成员中有指针或引用变量时必须注意,根据实际情况是否需要实现,否则会有造成程序崩溃的隐患

友元

class CDemo;   // 类前置声明

class CAnimals {
public:
    void SetName(const std::string &name) {
        m_name = name;
    }

    void Print(const CDemo &obj);
private:
    friend void Print(const CAnimals& obj);  // 友元函数声明
    std::string m_name = "Cat";

    friend CDemo;    // 友元类声明
};

void Print(const CAnimals& obj) {
    std::cout << "CAnimals Print: " << obj.m_name << std::endl;
}

class CDemo {
public:
    void Print(const CAnimals& obj) {
        std::cout << "CDemo Print: " << obj.m_name << std::endl;
    }

    friend void CAnimals::Print(const CDemo &obj);  // 友元成员函数

    friend std::ostream &operator << (std::ostream &os, const CDemo& obj); // 重载 << 运算符
private:
    std::string m_name = "Dog";
};

std::ostream &operator << (std::ostream &os, const CDemo& obj) {
    return (os << "operator <<: " << obj.m_name << '\t');
}

void CAnimals::Print(const CDemo &obj) {
    std::cout << "CAnimals::Print: " << obj.m_name << std::endl;
}

int main(int argc, char *argv[])
{
    CAnimals obj;
    Print(obj);
    CDemo demo;
    demo.Print(obj);
    obj.Print(demo);

    std::cout << demo << "ok" << std::endl;
    return 0;
}

输出结果:
CAnimals Print: Cat
CDemo Print: Cat
CAnimals::Print: Dog
operator <<: Dog ok

  • 友元包括友元函数、友元类,友元成员函数,由于c++的访问控制符,对于某个类中的 private 权限的成员,类外边不可访问,只能类中访问
  • 友元会打破类的封装性(访问权限),合理设计使用
  • 重载 << 运算符最好作为友元函数

浅拷贝深拷贝

// 使用编译器创建的拷贝复制构造函数(浅拷贝)和赋值函数(浅拷贝),注意观察输出 address
class CAnimal {
public:
    CAnimal()
        : m_property(new char[11]){
        for (int i = 0; i < 10; ++i)
            m_property[i] = i + 97;
    }

    void Print() {
        printf("address: %p\n", &m_property[0]);
        for (int i = 0; i < 10; ++i)
            std::cout << m_property[i] << "\t";
        std::cout << std::endl;
    }
private:
    char *m_property = nullptr;
};

int main(int argc, char *argv[])
{
    CAnimal a;
    a.Print();
    CAnimal b = a;
    b.Print();
    CAnimal c(a);
    c.Print();
    CAnimal d;
    d = a;
    d.Print();
    return 0;
}

输出结果:
address: 0000000000fe17b0
a b c d e f g h i j
address: 0000000000fe17b0
a b c d e f g h i j
address: 0000000000fe17b0
a b c d e f g h i j
address: 0000000000fe17b0
a b c d e f g h i j

// class CAnimal 中添加拷贝复制构造函数(实现深拷贝),注意观察输出 address
CAnimal(const CAnimal& other) {
    if (other.m_property == nullptr)
        return;

    if (this->m_property != nullptr)
        delete [] this->m_property;

    this->m_property = new char[strlen(other.m_property + 1)];    // 分配空间
    strcpy(this->m_property, other.m_property);     // 拷贝内容
}

输出结果:
address: 0000000000fb17b0
a b c d e f g h i j
address: 0000000000fb17d0
a b c d e f g h i j
address: 0000000000fb1b20
a b c d e f g h i j
address: 0000000000fb17b0
a b c d e f g h i j

// class CAnimal 中添加赋值函数(实现深拷贝),注意观察输出 address
CAnimal &operator = (const CAnimal& other) {
    if (this == &other)  // 判断自赋值
        return *this;

    if (this->m_property != nullptr)   // 清空原内容
        delete [] this->m_property;

    this->m_property = new char[strlen(other.m_property + 1)];  // 分配空间
    strcpy(this->m_property, other.m_property);     // 拷贝内容
    return *this;
}

输出结果:
address: 00000000006f17b0
a b c d e f g h i j
address: 00000000006f17d0
a b c d e f g h i j
address: 00000000006f1b20
a b c d e f g h i j
address: 00000000006f1b40
a b c d e f g h i j

  • c++编译器默认创建(类中未显式写对应的构造时)的拷贝复制构造和赋值函数默认是浅拷贝
  • 浅拷贝:只复制某个对象的指针或引用,对象成员变量之间的赋值,若有指针或引用,仅仅只是指向的地址之间的赋值,两个指针或引用指向同一块内存空间
  • 深拷贝:深拷贝会完全复制整个对象,不共享内存空间,两个对象互不影响
  • 如果对象中没有指针或引用等资源时,深拷贝和浅拷贝没有什么区别

简单String类

class CMyString {
public:
    CMyString() {}
    CMyString(const char *data) {
        if (!data)
            return;

        m_pdata = new char[strlen(data) + 1];
        strcpy(m_pdata, data);
    }
    CMyString(const CMyString& other) {
        if (other.m_pdata == nullptr)
            return;

        m_pdata = new char[strlen(other.m_pdata) + 1];
        strcpy(m_pdata, other.m_pdata);
    }
    CMyString &operator=(const CMyString& other) {
        if (this == &other || other.m_pdata == nullptr)  // 此处编写时规则是 other.m_pdata 为空时,当前对象不改变,可修改
            return *this;

        if (m_pdata != nullptr)
            delete [] m_pdata;

        m_pdata = new char[strlen(other.m_pdata) + 1];
        strcpy(m_pdata, other.m_pdata);
        return *this;
    }
    CMyString operator + (const CMyString& other) {  // 重载 + 运算符
        if (other.m_pdata == nullptr)
            return *this;

        CMyString ret;
        int len = strlen(this->m_pdata) + strlen(other.m_pdata) + 1;
        char *p = new char[len];
        strcpy(p, this->m_pdata);
        strcpy(p + strlen(this->m_pdata), other.m_pdata);
        ret = p;
        delete [] p;
        return ret;
    }
    char &operator [] (size_t index) {    //  重载 [] 运算符
        if (index < 0 || index >= strlen(m_pdata))
            throw "over range";

        return m_pdata[index];
    }
    ~CMyString() {    // 析构函数
        if (m_pdata)
            delete [] m_pdata;
    }

    void Print() {   // 为打印方便,内置此打印函数
        if (m_pdata == nullptr)
            return;

        int len = strlen(m_pdata);
        for (int i = 0; i < len; ++i)
            std::cout << m_pdata[i] << '\t';
        std::cout << std::endl;
    }

private:
    char *m_pdata = nullptr;
};

int main(int argc, char *argv[])
{
    CMyString a;
    a = "HelloWorld";    // char * 类型初始化,调用 char * 类型构造函数
    a.Print();
    CMyString b("HelloWorld");
    b.Print();
    CMyString c = a;
    c.Print();
    CMyString d;
    d = b;
    d.Print();
    CMyString e = (d + b);
    e.Print();
    std::cout << "e[0]: " << e[0] << std::endl;
    e[0] = 'S';    // 修改值
    e.Print();
    return 0;
}

输出结果:
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d H e l l o W o r l d
e[0]: H
S e l l o W o r l d H e l l o W o r l d

  • 注意重载运算符

static

class CStatic {
public:
    static int m_id;   // 静态成员变量声明

    static int GetId () {  // 只能访问 static 修饰的内容
        return m_id;
    }
};

int CStatic::m_id = 10;    // 类内声明(一般在 .h 文件)、类外初始化(一般在 .cpp文件)

int main(int argc, char *argv[])
{
    // 静态成员变量和静态成员函数调用方式,属于类而不属于某个对象,所以使用 类名::静态内容
    std::cout << "Get id: " << CStatic::m_id << std::endl;
    std::cout << "Get id: " << CStatic::GetId() << std::endl;
}

输出结果:
Get id: 10
Get id: 10

  • static 修饰的变量或函数只能在当前文件可访问(如 a.h、a.cpp 中),其它文件不可访问(如 b.h、b.cpp 中访问不了 a.h 中 static 修饰的内容)
  • static 可修饰全局变量、局部变量、全局函数、局部函数、类成员变量和函数等
  • static 修饰的内容存储在静态数据区,所以其生命周期是整个程序运行时,程序退出才销毁
  • static 修饰的变量具有默认初始化值
  • static 修饰的类静态成员函数是属于整个类而非类的对象,没有this指针,所以仅能访问类的静态数据和静态成员函数
  • 静态数据成员初始化的格式 <数据类型><类名>::<静态数据成员名> = <值>

你可能感兴趣的:(c++基础,c++,开发语言)