美文网首页
C++学习笔记

C++学习笔记

作者: 浪淘沙008 | 来源:发表于2022-06-07 14:25 被阅读0次

重载函数

定义:函数名相同,参数个数、参数类型、参数顺序不同的多个函数我们称为函数重载

构成条件:

  • 返回值不同不构成函数重载
  • 参数类型、数量、顺序不同也会构成函数重载
  • 调用函数时,实参的隐式类型转换可能会产生二义性

原理:c++采用了name mangling或者叫name decoration技术即:c++编译器默认会对符号名(如函数名)进行改编、修饰,有些地方翻译为“命名倾轧”。重载时会生成多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规则

  • debug模式:比较多的调试信息,生成的代码比较臃肿
  • release模式:比较少的调试信息,生成的代码比较精简,高效。其会对代码进行优化

默认参数

在函数中可以设置参数的默认值,其规则为:

  • 默认参数只能按照右到左的顺序
  • 如果函数同时有声明、实现,默认参数只能放在函数声明中
  • 默认参数的值可以是常量、全局符号(全局变量、函数名)

extern "C"

extern "C"的作用是告诉编译器该方法通过语言的方式进行编译,如果函数有声明,extern "C"可以在声明和实现处都存在,也可以只存在于声明处,不可仅存在于实现处。

extern "C" {
void func5();
void func2() {

}
}

void func5() {
    
}

extern "C" void func3() {
    
}

// 如果函数有声明,extern "C"可以在声明和实现处都存在,也可以只存在于声明处,不可仅存在于实现处
extern "C" void func4();
void func4() {
    
}

在C++和C混编时当引用C语言文件的头文件需要通过extern包裹,没有头文件的声明方法也需要extern的声明。当C语言头文件存在时需要对C和C++文件都进行支持,由于C语言文件导入C语言库不需要extern支持,所以需要对C库进行特殊处理以实现其对C及C++的同时支持,其支持代码如下:

// 下面两行语句防止重复包含
#ifndef math_h  // 如果没有定义abc
#define math_h  // 则定义abc,下次再进行定义时则无法重复定义

#include <stdio.h>

#ifdef __cplusplus  // #ifdef __cplusplus和#endif只有在C++环境下才会参与编译
extern "C" {
#endif

int sum(int v1, int v2);
int delta(int v1, int v2);

#ifdef __cplusplus
}
#endif

#endif /* math_h */

防止重复包含

// 下面两行语句防止重复包含
#ifndef math_h  // 如果没有定义abc
#define math_h  // 则定义abc,下次再进行定义时则无法重复定义

#endif /* math_h */

上面的代码可以防止导入的头文件被多次包含,或者通过#pragma once防止头文件的重复包含。但是#ifndef、#define、#endif受C/C++标准的支持,不受编译器限制,而有些编译器不支持#pragma once(较老编译器不支持,如GCC 3.4版本之前),兼容性不够好。同时#ifndef、#define、#endif可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。

内联函数和宏定义

在编译时,编译器会将内联函数调用直接替换成函数体代码,减少函数调用的开销。即有声明又有实现的地方有任何一个地方声明为内联函数,则该函数就是内联函数。如下:

inline int sum(int v1, int v2);
inline int sum(int v1, int v2)
{
    return v1 + v2;
}

内联函数和宏都可以减少函数调用的开销,但宏是简单的对代码进行替换,而内联函数是对函数进行展开替换,宏可能会产生计算上的错误,如'#define add(v) v + v'宏定义就会出现计算上的错误,当进行如下代码调用时就会出现计算结果的错误:

#define add(v) v + v

int a = 10;
int val = add(++a);

const和结构体及其指针的使用

定义如下结构体

struct Date {
    int year;
    int month;
    int day;
};

生成对应的结构体对象:
Date d = {2021, 2, 5};
cout << d.year << "年" << d.month << "月" << d.day << "日" << endl;
通过指针访问、修改其中的某个值:
Date *p = &d;   // 1
p->day = 10;        //2
(*p).year = 2022;       //3
cout << d.year << "年" << d.month << "月" << d.day << "日" << endl;

const是常量的意思是被其修饰的变量补课修改,如果修饰的是类、及饿哦固体(的指针),其成员变量不可修改。如上面第一句代码变为const Date *p = &d;, 则2、3句代码都会报错。

const 修饰内容的理解:

    int age = 10;
    int height = 30;
    // const修饰的是其右边的内容
    
    // *p0和*p1都是常量,不可通过地址修改age,但可以修改其指向的内容, 如p0 = &heitht;
    const int *p0 = &age;
    // 修饰的是指针为常量,
    int const *p1 = &age;
    //p2是常量,其指向的对象不可修改,但是其指向对象的值可以改变,即*p2可以修改,p2不可修改
    int * const p2 = &age;
    //p3和p4相同,p3和*p3都是常量,所以其指向地址、其指向地址的值都是常量,不能通过指针修改
    const int *const p3 = &age;
    int const * const p4 = &age;

引用

  • 引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
  • 一个引用占用一个指针的大小
  • 比指针更安全、函数返回值可以被赋值

注意点:

  • 引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
  • 对引用做计算,就是对引用所指向的变量做计算
  • 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
  • 可以利用引用初始化另一个引用,相当于某个变量的多个别名
  • 不存在【引用的引用、指向引用的指针、引用数组】
void swap(int &v1, int &v2)
{
   v1 = v1 + v2;
   v2 = v1 - v2;
   v1 = v1 - v2;
}

   // 引用的本质就是指针,可称为弱化的指针
   // 引用初始化时及要赋值,切后期指向不能修改
   int v1 = 10;
   int v2 = 20;
   int &v3 = v1;
   int &v4 = v2;

汇编

面向对象

类的定义:

  • C++中可以使用struct、class来定义一个类
  • 区别是struct定义的类其成员变量默认属性为public, 而class默认为私有的
struct Book {
    int pages;
    int price;
    
    Book(int p, int pri) {
        pages = p;
        price = pri;
    }
    
    void pagesNum() {
        cout << "书的总页数:" <<this->pages << endl;
    }
};

// 对象所占内存的大小为其所有成员变量所占内存大小的总和
class Person {
public:
    int age;
    string name;
    int height;
    
    void eat() {
        // this是指针, 在C++中调用方法时,编译器已经隐式的将调用者的地址传递给this指针,this指向函数调用者
        this->age = 10;
    }
};

int main(int argc, const char * argv[]) {
    
    // 结构体对象在栈空间
    
    Person per = Person();
    per.name = "gpf";
    per.eat();
    
    
    Person *p = &per;
    // Person *p = (Person *)&per.age;  //强制转换
    p->name = "ldd";
    p->eat();
    
    
    Book book = Book(20, 15);
    book.pagesNum();
    
    cout << sizeof(per) << endl;
    cout << sizeof(book) << endl;
    cout << &book.pages << endl;
    cout << &book.price << endl;
    
    // 函数代码存储在代码区,其中调用时其变量存储在栈区,随着函数调用结束变量也pop出栈
    
    return 0;
}

// 结构体和对象存在于栈空间,只有开辟堆空间内存的对象或结构体才会存在于堆空间内

内存

int main(int argc, const char * argv[]) {
    
    // 内存空间的申请,malloc申请的控件为堆空间
    int *p = (int *)malloc(4);
    *p = 10;
    cout << "*p = "<< *p << endl;
    free(p);
    
    char *cp = (char *)malloc(4);
    *cp = 11;
    *(cp + 1) = 10;
    *(cp + 2) = 12;
    *(cp + 3) = 13;
    
    cout << cp[0] << endl;
    cout << *(cp + 1) << endl;     cout << *(cp + 2) << endl;
    cout << *(cp + 3) << endl;
    free(cp);
    
    // 相对于C语言,C++能通过new和delete申请和释放堆空间,其中要搭配使用,不可混合使用
    int *p1 = new int;
    *p1 = 20;
    cout << "p1 = " << *p1 << endl;
    delete p1;
    
    // 或
    char *cp1 = new char[4];
    delete [] cp1;
    
    /**
     堆空间申请释放
     malloc/free
     new/delete
     new []/delete []
     */
    // 当申请内存空间成功时会返回内存空间的首地址。但是当内存紧张、剩余内存不够时会导致内存申请失败
    
    // 初始化连续多个内存为0
    int *p2 = (int *)malloc(sizeof(int) * 10);
    memset(p2, 0, sizeof(int) * 10);    //将从p2开始的内存连续sizeof(int) * 10个字节的每一个字节都设置为0
    
    /**
     int * p1 = new int; //未被初始化
     int *p2 = new int();   //被初始化为0
     int *p3 = new int(5);  //被初始化为5
     int * p4 = new int[3];     //数组元素未被初始化
     int * p5 = new int[3]();   //3个数组元素都被初始化为0
     int * p6 = new int[3]{};   //3个数组元素都被初始化为0
     int * p7 = new int[3]{5};  //数组首个元素被初始化为5,其余元素被初始化为0
     // 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化
     */
    
    return 0;
}

/**
 每个应用在内存中都有自己独立的内存空间,其内存空间一般有以下几个区域:
 
 * 代码段(代码区):用于存放代码
 * 数据区(全局区):用于存放全局变量等
 * 栈空间:每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间; 自动分配和回收
 * 堆空间:需要主动去申请和释放。在程序运行的过程中,为了能够自由控制内存的生命周期,
    
 指针存在于栈空间
 */

常用函数

构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
构造函数与类同名,不需要返回值,可以有参数,可以重载,可以有多个构造函数
一旦创建了构造器方法,则必须用其中一个自定义函数来创建对象
在某些特定的情况下编译器才会为类生成空的无参的构造函数
在对象的成员对象中,通过malloc、 new、 new []生成的成员变量需要手动释放,否则会产生内存泄漏

析构函数:

  • 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
  • 函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
  • 通过malloc分配的对象free的时候不会调用析构函数
  • 构造函数、析构函数要声明为public,才能被外界正常使用
  • 对于对象持有的存在于堆区的对象需要在析构函数中进行释放,否则会发生内存泄漏

命名空间

// 多次声明的同名命名空间会进行合并
struct Book {
public:
    int page;
    double price;
    
    void eat();
    void speakInfo();
    
    Book(int p = 100, double pri = 10) {
        page = p;
        price = pri;
    }
    

    ~Book() {
        cout << "析构函数" << endl;
    }
    
};


struct Person {
public:
    Book book1;
    Book *book2;
    
    /**
     通过malloc、 new、 new []生成的成员变量需要手动释放,否则会产生内存泄漏
     */
    Person() {
        book1 = Book();
        book2 = new Book;
    }
    
    ~Person() {
        delete book2;
    }
};

namespace GP {
struct Person {
    int g_age;
};
};

// 调用方式
  using namespace GP;
  // 或
 GP::Person p = GP::Person();
 p.g_age = 10;

继承

/ 继承时子类会把父类的公共成员变量和公共方法继承下来,其内存中父类的成员变量会排在前面
// class的继承默认为private继承, struct的继承默认public继承
/**
 成员访问权限、继承方式有3种:
 public:公共的,任何地方都可以访问(struct默认)
 protected:子类内部、当前类内部可以访问
 private:私有的,只有当前类内部可以访问(class默认)
 
 */

class Animal {
public:
    int m_age;
    Animal() {
        cout << "Animal:Animal()" << endl;
    }
    void eat();
};

class Person:public Animal {
public:
    double m_height;
    void speak();
    Person():Person(10, 20) {
        cout << "Person:Person()" << endl;
    }
    Person(int age, double height) {
        m_age = age;
        m_height = height;
    }
    
    
};

// 继承时可以设置继承的方式
struct Dog:public Animal {
    
};

// 子类的构造函数默认会先调用父类的无参构造函数,子类的析构函数会后调用父类析构函数
    // 如果子类的构造函数显式的调用了父类的有参构造函数,就不会再去调用默认调用父类的无参构造函数

多态

多态:多态是面向对象非常重要的一个特性

  • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
  • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数

多态的要素:

  • 子类重写父类的成员函数(override)
  • 父类指针指向子类对象
  • 利用父类指针调用重写的成员函数

子类指针不可指向父类对象,因为可能调用一些父类不拥有的成员变量,或者可以进行强制转换,但是存在隐患

class Animal {
public:
    int age;
    void eat() {
        cout << "Animal:eat()" << endl;
    }
};

class Person:public Animal {
public:
    string name;
    void eat() {
        cout << "Person:eat()" << endl;
    }
};

class Dog: public Animal {
public:
    void eat() {
        cout << "Dog:eat()" << endl;
    }
};

class Cat: public Animal {
public:
    void eat() {
        cout << "Cat:eat()" << endl;
    }
};
====================
Animal *a0 = new Person();
    Animal *a1 = new Dog();
    Dog *a2 = (Dog *)new Cat();
    a0->eat();
    a1->eat();
    a2->eat();

当通过继承复写的方式的方式实现多态时只会根据指针类型进行调用,无法实现多态,可通过虚函数的方式实现多态
C++中的多态通过虚函数(virtual function)来实现
虚函数:被virtual修饰的成员函数
只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual关键字)
拥有纯虚函数的类称为抽象类,其不能创建对象,但是其子类可以

class Animal {
public:
    int age;
    virtual void eat() {
        cout << "Animal:eat()" << endl;
    }
    // 带有虚函数的对象应该将其析构函数也设置为虚函数,否则子类无法正常释放
    virtual ~Animal() {
        cout << "Animal:delete()" << endl;
    }
    
    // 纯虚函数,在父类中不做实现,在子类中实现
    virtual void speak() = 0;
    
};

class Person:public Animal {
public:
    string name;
    void eat() {
        Animal::eat();  // 调用父类的方法
        cout << "Person:eat()" << endl;
    }
    
    void speak() {
        cout << "Person:speak language" << endl;
    }
    
    ~Person() {
        cout << "Person:delete()" << endl;
    }
};

class Dog: public Animal {
public:
    void eat() {
        cout << "Dog:eat()" << endl;
    }
    void speak() {
        cout << "Dog:汪汪汪" << endl;
    }
    
    ~Dog() {
        cout << "Dog:delete()" << endl;
    }
};

class Cat: public Animal {
public:
    void eat() {
        cout << "Cat:eat()" << endl;
    }
    void speak() {
        cout << "Cat:喵喵喵" << endl;
    }
    
    ~Cat() {
        cout << "Cat:delete()" << endl;
    }
};


 // 多态:根据传入对象的不同最终调用函数的不同就称为多态
    
    // 父类指针指向子类对象
    Animal *a0 = new Person();
    Animal *a1 = new Dog();
    Dog *a2 = (Dog *)new Cat();
    a0->eat();
    a1->eat();
    a2->eat();
    // 子类指针不可指向父类对象,因为可能调用一些父类不拥有的成员变量,
或者可以进行强制转换,但是存在隐患
//    Person *p = new Animal();
    //默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
    //多态是面向对象非常重要的一个特性
    // 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
    // 在运行时,可以识别出真正的对象类型,调用对应子类中的函数
    // 多态的要素
    // 子类重写父类的成员函数(override)
    // 父类指针指向子类对象
    // 利用父类指针调用重写的成员函数
    // C++中的多态通过虚函数实现的(virtual function)
    //虚函数:被virtual修饰的成员函数
    //只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数
(也就是说子类中可以省略virtual关键字)
    
    // 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,
这个虚表也叫虚函数表
   
    // 子类调用父类的方法
    

多继承

// C++允许一个类可以有多个父类(比建议使用,会增加程序设计复杂度

class Animal {
     int m_age;
};

class Student: virtual public Animal {
public:
    int m_score;
    int m_height;
    Student(int score):m_score(score){}
    void study() {
        cout << "Student::study()" << endl;
    }
    
    virtual void eat() {
        cout << "Student::eat()" << endl;
    }
};

class Worker: virtual public Animal {
public:
    int m_salary;
    int m_height;
    Worker(int salary):m_salary(salary){}
    void work() {
        cout << "Worker::work()" << endl;
    }
    virtual void eat() {
        cout << "Worker::eat()" << endl;
    }
};

// 如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表分别指向对应的虚表地址
// 对于父类的同名方法和成员变量子类会全部继承,在调用的过程中需要指定调用的是哪个父类的方法
// 其内存根据继承的先后来进行排序
class Undergraduate: public Student, public Worker {
public:
    int m_grade;
    int m_age;
    Undergraduate(int score, int salary, int grade): m_grade(grade), Student(score), Worker(salary){
        
    }
    
    void eat() {
        cout << "Undergraduate::eat()" << endl;
    }
    
    void play() {
        cout << "Undergraduate::play()" << endl;
    }
};

/**
 菱形继承:Animal中的m_age属性会被Student和Worker同时继承,当Undergraduate继承Student和Worker时会导致Undergraduate存在两个m_age,这种继承方式叫做菱形继承。可以通过虚继承来解决这种情况
 虚继承:继承的子类不会重新分配新的、独立的成员变量,而是会共用父类的成员变量,多继承的子类也会使用同一个成员变量,从而解决菱形继承。其中Animal称为虚基类
 
 */


    Undergraduate u = Undergraduate(10, 20, 30);
    u.Student::m_height = 10;
    u.m_age = 10;
    u.eat();
    u.Student::eat();

static

class Car {
public:
    // 静态成员变量,在内存中只存在一份且不存在于对象的内存中
    static int m_price;
    static void run() {
        // 当通过类进行调用时this没有什么意义
        cout << "run()" << endl;
    }
};

class SCar {
public:
    // 新的静态变量会重新开辟内存,和父类的成员变量不同
    static int m_price;
};

/**
 静态成员:被static修饰的成员变量\函数
 可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员)
 
静态成员变量
 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存  对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的  必须初始化,必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离(在实现.cpp中初始化)
 
静态成员函数
内部不能使用this指针(this指针只能用在非静态成员函数内部)
不能是虚函数(虚函数只能是非静态成员函数)  内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数 非静态成员函数内部可以访问静态成员变量\函数
构造函数、析构函数不能是静态
当声明和实现分离时,实现部分不能带static
  
 */


// 单例模式:设计模式的一种,保证某个类永远只创建一个对象
// 1.构造函数私有化
// 2.定义一个私有的static成员变量指向唯一的那个单例对象
// 3.提供一个公共的访问单例对象的接口
class Rocket {
private:
    Rocket() {
        
    }
    ~Rocket() {
        
    }
    static Rocket * ms_rocket;
public:
    static Rocket *sharedRocket() {
        if (ms_rocket == NULL) {
            ms_rocket = new Rocket();
        }
        return ms_rocket;
    }
    static void deleteRocket() {
        if (ms_rocket != NULL) {
            delete ms_rocket;
            ms_rocket = NULL;
        }
    }
    
};


// 静态成员变量必须进行初始化,且需要放到外部进行初始化
int Car::m_price = 0;
int SCar::m_price = 0;

Rocket * Rocket::ms_rocket = NULL;

 // 静态成员:被static修饰的成员变量/函数
    
    Car car1;
    car1.m_price = 10;
    Car car2;
    car2.m_price = 20;
    
    Car car3;
    car3.m_price = 30;
    
    Car *car4 = new Car();
    
    // 对静态成员函数调用
    car3.run();
    Car::run();
    car4->run();
    
    // 对静态成员变量进行访问
    cout << car3.m_price << endl;
    cout << Car::m_price << endl;
    cout << car4->m_price << endl;
    delete car4;
    
    Rocket * rocket1 = Rocket::sharedRocket();
    Rocket * rocket2 = Rocket::sharedRocket();
    Rocket * rocket3 = Rocket::sharedRocket();
    Rocket * rocket4 = Rocket::sharedRocket();
    cout << rocket1 << endl;
    cout << rocket2 << endl;
    cout << rocket3 << endl;
    cout << rocket4 << endl;
    Rocket::deleteRocket();

拷贝构造函数

class Car {
    int m_price;
    int m_length;
public:
    Car(int price = 0, int length = 0):m_price(price), m_length(length) {
        cout << "Car(int prive = 0, int length = 0)" << endl;
    }
    
    // 拷贝构造函数
    // 构造函数的格式是固定的,要接收一个const引用作为参数
//    Car(const Car &car) {
//        cout << "Car(const Car &car)" << endl;
//    }
    
    // 自定义拷贝构造函数
    Car(const Car &car) :m_price(car.m_price) {
        cout << "Car(const Car &car)" << endl;
    }
    
    void display() {
        cout << "prive=" << m_price << ", lengtth=" << m_length << endl;
    }
};

class Person {
    int m_age;
public:
    Person(int age = 0):m_age(age) {
        cout << "Person(int age = 0)" << endl;
    }
    Person(const Person &per) {
        
    }
};

class Student: public Person {
    int m_score;
public:
    Student(int age = 0, int score = 0):Person(age), m_score(score) {
        cout << "Student(int age = 0, int score = 0)" << endl;
    }
    
    Student(const Student &student):Person(student),m_score(student.m_score) {
        
    }
};


int main(int argc, const char * argv[]) {
    /**
     拷贝构造函数是构造函数的一种
     当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
     */
    
    Car car1;
    Car car2(100);
    Car car3(100, 5);
    
    // 利用已经存在的car对象创建一个新的car对象
    // car4初始化时会调用拷贝构造函数
    Car car4(car3);
    car4.display();
    
    cout << &car3 << "\n" << &car4 << endl;
    
    
    // 字符串所占用的空间是其长度加一,结束符为'\n',当字符串进行打印时遇到'\n'会停止打印
    
    return 0;
}

深浅拷贝

class Car {
    int m_price;
    const char *m_name;
public:
    Car(int price = 0, const char *name = NULL): m_price(price), m_name(name) {
        
    }
    
    void display() {
        cout << "prive is " << m_price << ", name is " << m_name << endl;
    }
};

Car * g_car;
void test() {
    //其中name的值位于栈空间,当car为全局变量时可能会产生name被释放,但是car中的内存仍指向之前的栈空间,形成野指针
    char name[] = {'b', 'm', 'w', '\0'};
    g_car = new Car(100, name);
}


int main(int argc, const char * argv[]) {
    // 浅拷贝:对指针的拷贝,并不会生成新的对象,只是复制其存在的地址
    // 编译器默认的copy都是浅copy
    
    // 定义字符串的方式
    const char *name = "bmw";
    
    // 字符串以\0为结束,下面这种生成字符串的方式如果没有\0会一直往下输出或错误输出
//    strlen(name) 字符串的长度,其在计算长度时不会把\0计入
    char name2[] = {'b', 'm', 'w', '\0'};
    
    test();
    g_car->display();
    return 0;
}

深拷贝

class Car {
    int m_price;
    char *m_name;
    
    // 定义私有拷贝函数
    void copy(const char *name = NULL) {
        if (name == NULL) return;
        // 申请新的堆空间
        m_name = new char[strlen(name) + 1] {};
        // 拷贝字符串数据到新的堆空间
        strcpy(m_name, name);  // 将name拷贝到m_name的空间
    }
    
public:
    // const 可以接收const参数和非const参数
    Car(int price = 0, const char *name = NULL): m_price(price) {
        copy(name);
    }
    
    Car(const Car &car):m_price(car.m_price) {
        copy(car.m_name);
    }
    
    ~Car() {
        if (m_name == NULL) return;
        delete m_name;
        m_name = NULL;
    }
    
    void display() {
        cout << "prive is " << m_price << ", name is " << m_name << endl;
    }
};

Car * g_car;
void test() {
    //其中name的值位于栈空间,当car为全局变量时可能会产生name被释放,但是car中的内存仍指向之前的栈空间,形成野指针
    char name[] = {'b', 'm', 'w', '\0'};
    g_car = new Car(100, name);
}


int main(int argc, const char * argv[]) {
    // 浅拷贝:对指针的拷贝,并不会生成新的对象,只是复制其存在的地址
    // 深拷贝:将指针指向的内容拷贝到新的存储空间
    // 编译器默认的copy都是浅copy
    
    // 当对象中存在指针类型性的变量且需要进行深拷贝的时候需要自己实现拷贝构造函数
    
    // 定义字符串的方式
    const char *name = "bmw";
    
    // 字符串以\0为结束,下面这种生成字符串的方式如果没有\0会一直往下输出或错误输出
//    strlen(name) 字符串的长度,其在计算长度时不会把\0计入
    char name2[] = {'b', 'm', 'w', '\0'};
    
    test();
    g_car->display();
    return 0;
}

对象作为参数和返回值/匿名对象

class Car {
public:
    Car() {
        cout << "Car() -" << this << endl;
    }
    
    Car(const Car &car) {
        cout << "Car(const Car &) -" << this << endl;
    }
    
    void run() {
        cout << "run()" << endl;
    }
    
};

void test1(Car car) {
    
}

void test2(Car &car) {
    
}

// 作为返回值
Car test3() {
    Car car;
    return car;
}

int main(int argc, const char * argv[]) {
    
    // 使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象
    
//    Car car1;
    
    // 调用test1时调用了car1的拷贝构造函数,即产生了中间对象
//    test1(car1);
    // 为了防止中间变量的产生,可以通过指针或者引用类型作为变量来防止中间变量的生成
//    test2(car1);
    
//    Car car2;
//    car2 = test3();
    
    // 不会产生多余对象(编译器进行优化)
//    Car car2 = test3();
    
    // 匿名对象(临时对象)
    
    // 此对象为匿名对象,创建后无用立即释放
//    Car car;
    
    // 匿名对象作为参数只进行一次构造,编译器对其进行了优化
//    test1(Car());
    
    
    // 隐式构造
    
    
    return 0;
}

隐式构造/explicit

class Person {
    int m_age;
public:
    Person() {
        cout << "Person() - " << this << endl;
    }
    // 可以通过关键字explicit禁止调隐式构造
//    explicit Person(int age) :m_age(age) {
//        cout << "Person(int) - " << this << endl;
//    }
    
    Person(int age) :m_age(age) {
        cout << "Person(int) - " << this << endl;
    }
    Person(const Person &person) {
        cout << "Person(const Person &person) - " << this << endl;
    }
    ~Person() {
        cout << "~Person() - " << this << endl;
    }
    void display() {
        cout << "display() - age is " << m_age << endl;
    }
};

void test1(Person person) {

}

Person test2() {
    return 40;
}

int main() {
    test1(30);
    test2();
    // C++中存在隐式构造的现象:某些情况下会隐式调用单参数的构造函数
    // 可以通过关键字explicit禁止调隐式构造
    
    /*Person p1;
    Person p2(10);
    Person p3 = p2;*/

    // Person p1 = 20;
    // Person p2(20);

    Person p1;
    p1 = 40;

    // Person p1 = 40;

    getchar();
    return 0;
}

编译器生成构造函数

C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如:

  • 成员变量在声明的同时进行了初始化
  • 有定义虚函数
  • 虚继承了其他类
  • 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)
  • 父类有构造函数(编译器生成或自定义)

总结一下:对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数

友元

class Point {
    int m_x;
    int m_y;
    // 通过friend声明的函数称为友元函数,其作用是允许访问对象私有变量
    friend Point add(Point, Point);
    // 通过friend声明的类为友元类,其类内部可以访问声明类内部的私有变量
    friend class Math;
public:
    int getX() { return m_x;};
    int getY() { return m_y;};
    Point(int x, int y) :m_x(x), m_y(y) {};
    
    void display() {
        cout << "(" << m_x << "," << m_y <<")" << endl;
    }
};

class Math {
    void test() {
        Point p1(10, 20);
        p1.m_y = 10;
        p1.m_x;
    }
};

Point add(Point p1, Point p2) {
    return Point(p1.m_x + p2.m_x,  p1.m_y + p2.m_y);
}

int main(int argc, const char * argv[]) {
    
    return 0;
}

相关文章

网友评论

      本文标题:C++学习笔记

      本文链接:https://www.haomeiwen.com/subject/msdemrtx.html