头图

0. 复习

0.1 堆的申请释放

申请:new
释放:delete
使用new和delete的好处:
1.使用new,new可以识别类型,申请什么类型,返回的就是什么类型的指针,就无需强制转换了。
2.new会调用构造函数,delete会调用析构函数
和malloc,free有什么区别:
malloc和free是函数,delete是运算符
//假如想要申请10个int
int* p = new int[10]{1,2,3,4,5,6};
//.....
delete []p;
p = nullptr;
//假如申请的是1个int,初始值是50
int* p = new int(50);
delete p;
p = nullptr;

0.2 函数重载

函数重载:在相同的作用域内,函数名相同,参数不同可以构成重载
用处:构成重载之后,调用函数的时候,编译器能够根据我们传递不同的参数自动的选择调用哪一个函数。
好处:不用去记忆过多的函数名了,这个也被称为 接口复用
名称粉碎:编译器根据函数名和参数,构建一个新的函数名。从编译器看来,同名函数的不同参数实际是不同的名称。

0.3 默认参数

我们可以给函数设置默认参数,就是默认值,不传参的时候,自动传递默认值。注意事项:
1.默认参数只能从右往左设置。
2.中间不能中断
3.一个函数如果有声明,也有定义,默认参数应该写在声明中。
4.有默认参数的同时,还有这个函数的重载,容易造成二义性问题

0.4 引用

给一个变量起一个别名
对于别名的操作和对于变量的操作,此时就是一样的。
别名和变量名公用内存空间
引用可以代替指针的一部分功能
有什么特点呢??
1.必须初始化
2.一经引用,就不能再引用其他对象了。
3.能够使用引用的地方,不应该使用指针。因为引用更为安全。
char cCh = 'a';
char& m = cCh;

0.5 输入输出

有一个命名空间的概念:
输出:

#include<iostream>
1.  using namespace  std;
2.  using std::cout;
3.  std::cout<<"hello world";

输入:

int  nNum = 0;
std::cin>>nNum;
输出的时候,换行:
std::endl;  

0.6 类的基本语法

0.6.1 类的基本定义方式

XXXX.h
class 类名
{
public:         //   公有的
int  m_nNum1 ;   // 成员变量,属性,类中的数据
void  Fun1();        // 成员函数,方法,行为
protected:   //   保护的
int  m_nNum2 ;   // 成员变量,属性,类中的数据
void  Fun2();        // 成员函数,方法,行为
private:        //   私有的
int  m_nNum3 ;   // 成员变量,属性,类中的数据
void  Fun3();        // 成员函数,方法,行为
};
XXXX.cpp
void  类名::Fun1()
{
        //代码
}  
void  类名::Fun2()
{
        //代码
}  
void  类名::Fun3()
{
        //代码
}  

总结:
1..h中些类的声明,成员函数的定义一般不写在类内。
.cpp中写成员函数的实现
2.使用的时候,包含类的头文件即可。
3.使用类定义出来的变量,叫做对象。
4.命名规范:

a. 类名一般以C开头
b. 成员变量的名字,一般以m_开头

5.类的大小只和成员变量有关。和普通的成员函数没有关系。
6.当我们定义了一个类对象,实际上就开辟了一块内存,里面放置成员变量

0.6.2 this指针

图片.png
类的大小是不包含函数的。定义类对象,里面只有数据。
一个类的所有对象,使用的都是同一份函数。
一个函数是如何区分这么多对象的呢???
调用成员函数的时候,会隐含着传递对象的地址进去。这个就是this指针。
this指针就代表着调用这个函数的对象地址。
我们看着成员函数是 3 个参数,实际上是 4 个参数,含有一个自己的地址
默认传递自己地址 是编译器做的,不需要我们自己干预。
图片.png

0.6.3 类和结构体有什么区别???

C++中,类和结构体几乎没有区别的,结构体也能封装函数,也有三种权限。但是还是依然保留着 C语言中使用结构体的习惯,结构体中只放置数据,不放置函数。
唯一的区别:
类的默认权限是 私有的
结构体的默认权限是 公有的

0.6.4 面向对象

根据程序需要,抽象出一个,类中设计成员变量和成员函数。设计完毕之后,他是一个数据类型。
我们使用类定义出来的变量,就是对象
面向对象有三大特性:封装,继承,多态。
使用成员的方式:

a. 通过对象使用    要受到权限控制的
         obj.m_nNum = 100;
         obj.Fun();
b. 在类自己的成员函数中使用自己的成员  不受权限控制
i. this->m_nNum = 200;
ii. this->Fun();

1.1 构造函数

1.1.1 构造的基本语法和调用规则

特点:

a. 构造函数的作用,是用于编写初始化规则的
b. 构造函数名和类名是一致的
c. 构造函数没有返回值,也不需要写返回值类型
d. 构造函数可以有参数,一个类可以实现多个构造函数,他们是可以重载的
e. 构造函数是自动调用的,在定义对象的时候,就会自动调用,什么算是定义对象呢??
i. 局部对象
ii. 全局对象,静态局部对象
iii. 堆对象
#include <iostream>class CStudent {
class CStudent {
public:
    CStudent()
    {
        std::cout << "我是无参构造函数" << std::endl;
        strcpy_s(this->m_szName, "");
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName,int nId,int nScore)
    {
        std::cout << "我是有参构造函数" << std::endl;
        strcpy_s(this->m_szName, szName);
        this->m_nId = nId;
        this->m_nScore = nScore;
    }
    void PrintfStu();
    void SetStu(const char* szName, int nId, int nScore);
private:
    char  m_szName[20]; //姓名
    int m_nId;          //学号
    int m_nScore;       //分数
};
void CStudent::PrintfStu()
{
    printf("%s ", this->m_szName);
    printf("%d ", this->m_nId);
    printf("%d ", this->m_nScore);
}
void CStudent::SetStu(const char* szName, int nId, int nScore)
{
    //pstu->szName = szName
    strcpy_s(this->m_szName, szName);
    this->m_nId = nId;
    this->m_nScore = nScore;
}

//定义了全局对象,自动调用无参构造
//全局对象的构造函数调用,在main函数之前就会调用
CStudent g_obj;
int main()
{
    //定义了局部对象,自动调用无参构造
    CStudent obj1;
    //定义了局部对象,自动调用有参构造
    CStudent obj2("xiaoming", 19, 20);
    //定义了5个局部对象,2个有参,3个无参
    CStudent arr[5] = { {"xiaobai",20,79},{"xiaohei",21,80} };//这里是否定义了对象,是否调用构造??
    //定义了堆中的对象,自动调用无参构造,new会调用构造函数
    CStudent* p1 = new  CStudent;//这里是否定义了对象,是否调用构造??

    //这里就只申请了空间,malloc不会调用构造函数
    CStudent* p2 = (CStudent*)malloc(sizeof(CStudent));//这里是否定义了对象,是否调用构造??

    //定义了10个堆中的对象,自动调用2个有参构造,8个无参构造,new会调用构造函数
    CStudent* p3 = new  CStudent[10]{//这里是否定义了对象,是否调用构造??
    {"xiaobai",20,79},
    {"xiaohei",21,80}
    };
}

1.1.2 关于初始化的问题

图片.png

怎么算式初始化呢???
需要写一个初始化列表

#include <iostream>
class CNoteBook
{
public:
    CNoteBook(double fPrice, int nType):m_fPrice(fPrice), m_nType(nType)
    {

    }
private:
    double m_fPrice;
    int m_nType;
};

class CStudent {
public:
    CStudent() 
        :m_nId(0), m_nScore(0), m_szName{""}, 
        m_nNum(58),m_nNum2(m_nId),m_objNoteBook(5000.38,3)//这三个是放在初始化列中比较合适
    {
        std::cout << "我是无参构造函数" << std::endl;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_szName{""},
        m_nNum(58), m_nNum2(m_nId), m_objNoteBook(5000.38, 3)
    {
        std::cout << "我是有参构造函数" << std::endl;
        strcpy_s(this->m_szName, szName);
    }
    void PrintfStu();
    void SetStu(const char* szName, int nId, int nScore);
private:
    char  m_szName[20]; //姓名
    int m_nId;          //学号
    int m_nScore;       //分数
    CNoteBook m_objNoteBook ;// 成员是一个对象,这个对象没有无参构造,就必须传参
    const int m_nNum;       //const 也必须初始化
    int& m_nNum2;           //引用必须初始化
};
void CStudent::PrintfStu()
{
    printf("%s ", this->m_szName);
    printf("%d ", this->m_nId);
    printf("%d ", this->m_nScore);
}
void CStudent::SetStu(const char* szName, int nId, int nScore)
{
    //pstu->szName = szName
    strcpy_s(this->m_szName, szName);
    this->m_nId = nId;
    this->m_nScore = nScore;
}

int main()
{
    CStudent obj1;
    CStudent obj2("xiaoming", 19, 21);
}

总结:
1.在初始化列表中初始化,才算是初始化
2.引用成员,const成员,没有无参构造的 类对象成员 都需要在初始化列表中去初始化。

1.2 析构函数

1.2.1 析构函数基本语法和调用规则

特点:

a.析构函数,用于对象去释放资源的
b.析构函数的函数名:  ~  类名
c.析构没有返回值,也不需要写返回值类型
d.析构函数不能有参数,析构函数不能重载,一个类只能有1个析构函数
e.析构函数在对象被销毁的时候,自动调用
i.局部对象,离开作用域的时候,自动销毁,参数是对象的话,等同于局部对象
ii.全局对象,静态局部对象  程序结束的时候,会自动销毁
iii.堆对象,delete释放堆的时候,会自动销毁
iv.只要销毁,就会要调用析构函数
#include <iostream>class CStudent {
class CStudent {
public:
    CStudent()
    {
        std::cout << "我是无参构造函数" << std::endl;
        strcpy_s(this->m_szName, "");
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
    {
        std::cout << "我是有参构造函数" << std::endl;
        strcpy_s(this->m_szName, szName);
        this->m_nId = nId;
        this->m_nScore = nScore;
    }
public://析构函数
    ~CStudent()
    {
        std::cout << "我是析构函数" << std::endl;
    }


private:
    char  m_szName[20]; //姓名
    int m_nId;          //学号
    int m_nScore;       //分数
};

//定义了全局对象,自动调用无参构造
//全局对象的构造函数调用,在main函数之前就会调用
CStudent g_obj;


void Fun()
{
    //定义了局部对象,自动调用无参构造
    CStudent obj1;
    //定义了局部对象,自动调用有参构造
    CStudent obj2("xiaoming", 19, 20);
    //定义了5个局部对象,2个有参,3个无参
    CStudent arr[5] = { {"xiaobai",20,79},{"xiaohei",21,80} };//这里是否定义了对象,是否调用构造??
    //定义了堆中的对象,自动调用无参构造,new会调用构造函数
    CStudent* p1 = new  CStudent;//这里是否定义了对象,是否调用构造??

    //这里就只申请了空间,malloc不会调用构造函数
    CStudent* p2 = (CStudent*)malloc(sizeof(CStudent));//这里是否定义了对象,是否调用构造??

    //定义了10个堆中的对象,自动调用2个有参构造,8个无参构造,new会调用构造函数
    CStudent* p3 = new  CStudent[10]{//这里是否定义了对象,是否调用构造??
    {"xiaobai",20,79},
    {"xiaohei",21,80}
    };
    delete p1;
    free(p2);
    delete []p3;
}
int main()
{
    Fun();
}

1.2.2 析构函数的用处

可以用来,释放需要释放的资源

#include <iostream>
class CStudent {
public:
    CStudent()
        :m_nId(0), m_nScore(0), m_pName( nullptr)
    {
        std::cout << "我是无参构造函数" << std::endl;
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_pName(nullptr)
    {        
        int nLenth = strlen(szName) + 1;
        m_pName = new char[nLenth] {0};
        strcpy_s(m_pName, nLenth, szName);
    }

public://析构函数
    ~CStudent()
    {
        std::cout << "我是析构函数" << std::endl;
        if (m_pName!=nullptr)
        {
            delete[]m_pName;
            m_pName = nullptr;
        }
    }
private:
    char*  m_pName; //姓名
    int m_nId;          //学号
    int m_nScore;       //分数
};
int main()
{
    CStudent objl("xiaoming",20,90);

}

1.3 需要注意的地方

1.一个类必须有一个构造函数,必须有一个析构函数。
2.如果我们不实现的话,编译器会自动提供构造函数与析构函数。没有功能
3.如果我们自己写了构造函数析构,编译器就不会提供了
4.构造函数的作用:用来初始化成员变量的数据。
5.析构函数的作用:一般用来清理资源。比如我们可以在析构中释放堆空间。
6.这两个函数的主要特点:他们都是自动调用的
7.构造函数和析构的调用顺序,一般是相反的。

1.4 拷贝构造函数(复制构造函数)

拷贝构造函数,它的作用是一个对象去初始化另外一个对象,此时会调用拷贝构造函数
图片.png

拷贝构造函数,如果我们不实现的话,编译器也会默认实现一个。我们刚才实现的拷贝构造函数的功能和编译器默认实现的功能是一样的。
既然,编译器提供了一个,我们还有必要自己实现么????
有必要,编译器实现的是 浅拷贝
有些时候,比如成员中有指针指向堆空间的时候,我们需要实现一个 深拷贝

图片.png

#include <iostream>
class CStudent {
public:
    char* m_pName; //姓名
    int m_nId;          //学号
    int m_nScore;       //分数
public:
    CStudent()
        :m_nId(0), m_nScore(0), m_pName(nullptr)
    {
        std::cout << "我是无参构造函数" << std::endl;
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_pName(nullptr)
    {
        int nLenth = strlen(szName) + 1;
        this->m_pName = new char[nLenth] {0};
        strcpy_s(this->m_pName, nLenth, szName);
    }
public://析构函数
    ~CStudent()
    {
        std::cout << "我是析构函数" << std::endl;
        if (this->m_pName != nullptr)
        {
            delete[] this->m_pName;
            this->m_pName = nullptr;
        }
    }
    //这就是拷贝构造函数
    CStudent(CStudent& other)
    {
        //this->m_pName = other.m_pName;
        int nLenth = strlen(other.m_pName) + 1;
        this->m_pName = new char[nLenth] {0};
        strcpy_s(this->m_pName, nLenth, other.m_pName);


        this->m_nId = other.m_nId;
        this->m_nScore = other.m_nScore;
    }
};
int main()
{
    int a = 10;
    int b = a;//这叫做用一个整型变量初始化另外一个整型变量
    CStudent obj1("xiaoming",10,90);
    CStudent obj2 = obj1;//这就叫做用一个对象初始化另外一个对象
    obj1.m_pName[0] = 'n';
}

总结:当我们需要实现深拷贝的时候,需要实现拷贝构造函数。
拷贝构造函数什么时候,会被调用呢???
一个对象初始化,另外一个对象的时候、
直接初始化
函数传参的时候,也是一个对象初始化另外一个对象
图片.png

转换构造(了解即可)

只有一个参数的构造函数,也叫做转换构造

class CNoteBook
{
public:
    //转换构造有的时候比较方便,但是更多的时候,容易引起歧义,容易让人误以为notebook2是 double类型
    //可以通过explicit禁止转换
    explicit CNoteBook(double fPrice) :m_fPrice(fPrice)
    {

    }
private:
    double m_fPrice;
};
int main()
{
    CNoteBook notebook1(10.5);

    CNoteBook notebook2 = 20.8;

    notebook2 = 50;

}

瞿小凯
1.3k 声望593 粉丝