C++面向对象笔记(2):封装篇(上)

1.类的三种访问限定符

public、protected、private

2.实例化类的对象与访问对象成员

定义一个TV类:

class TV{
    public:
    char name[20];
    int type;
    
    void changeVol();
    void power();
}

从栈中实例化:

int main(void){
    TV tv;
    TV tv[20];
    
    return 0;
}

从堆中实例化:

int main(void){
    TV *p = new TV();
    TV *q = new TV[20];
    
    // todo(如果从堆中,一定要手动释放)
    delete p;
    delete []q;
    p = nullptr;
    q = nullptr;
    
    return 0;
}

注意:要区分是栈还是堆也很简单,堆我们需要手动分配、释放内存(new、delete)。

访问对象成员(栈):

int main(void){
    TV tv;
    tv.type = 0;
    
    return 0;
}

访问对象成员(堆):

int main(void){
    TV *p = new TV();
    p->type = 0;
    p->changeVol();
    
    delete p;
    p = nullptr;
    
    return 0;
}

访问对象成员(数组):

int main(void){
    TV *p = new TV[5];
    for(int i = 0; i < 5; i++){
        p[i]->type = 0;
        p[i]->changeVol();
    }
    
    delete []p;
    p = nullptr;
    
    return 0;
}

3.String字符串的使用

常用方法:

s.empty()
s.size()
s1+s2

字符串连接:

string s1 = "hello";
string s2("world");
string s3 = s1 + s2;
string s4 = "hello" + s2;
string s5 = "hello" + s2 + "world";

// 错误
string s6 = "hello" + "world";

注意: 字符串连接,只能用于“字符串+变量”,不能“字符串+字符串”

4.new和delete运算符-内存的创建与释放

new/delete 是C++的运算符;类似于malloc/free,程序运行(动态)得开辟内存空间(堆);

new 可以为内置类型的变量开辟空间,数组变量,类的对象开辟空间。

这是在上开辟内存,返回一个指向该内存空间的地址。

new/delete会调用类的构造函数析构函数(malloc不会)。

5.在类的外部定义类函数

注意:类内定义的函数优先选择编译为内联函数(inline)。

class Car{
    public:
    void run();
    void stop();
    void changeSpeed();
};
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}

如果需要 分文件 进行定义:

car.h

// 存放类的声明
class Car{
    public:
    void run();
    void stop();
    void changeSpeed();
};

car.cpp

// 存放类的定义,切记要包含(include)头文件
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}

6.内存区块与代码之间的联系

栈区int x = 0; int *p = nullptr;
堆区int *p = new int[20];
全局区存储全局变量及静态变量
常量区string str = “hello”;
代码区存储逻辑代码的二进制

7.构造函数

构造函数一定是没有返回值的。在参数上,如果没有参数,我们都称之其为默认构造函数

如果是默认构造函数(不带参数的构造函数),在实例化的时候,有如下几种写法:

// 在堆中创建
Coordinate *p1 = new Coordinate();
Coordinate *p2 = new Coordinate;

// 在栈中创建
Coordinate p2;

如果在堆中进行创建,那我们也需要手动进行释放,代码如下:

delete p1;
p1 = nullptr;// 不是必要,只是防止delete失败,所以这里再将指针置为安全状态

// 如果是类数组
delete[] p;
p1 = nullptr;

8.构造函数-初始化列表

初始化列表是连接到构造函数名后面的,初始化列表和构造函数一样能够初始化类中的属性,但是对于const修饰的常量,只能用 初始化列表 进行初始化。即如果需要初始化,必须写到 初始化列表 当中。

  1. 初始化列表先于构造函数执行
  2. 初始化列表,只能用于构造函数
  3. 初始化列表可以同时初始化多个数据成员

例子1,对const修饰的常量赋初值:

// 错误,采用构造函数
class Circle{
public:
    Circle():{m_dPi=3.14}
private:
    const double m_dPi;
};

// 正确,采用初始化列表
class Circle{
public:
    Circle():m_dPi(3.14){}
private:
    const double m_dPi;
};

例2,多参数赋值:

// ====== 类声明START ======
class Teacher{
public:
    // 构造函数(没有返回值),这里设定了默认参数
    Teacher(string name = "Jim",int age = 1,int max = 50);

    // 操作类的属性
    void setName(string name);
    string getName() const;
    void setAge(int age);
    int getAge();
private:
    // 属性
    string m_strName;   // 名字
    int m_iAge;         // 年龄
};
// ====== 类声明END ======

// ====== 类实现START ======
// 构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(string name,int age,int max):m_strName(name),m_iAge(age){
    cout << "This is the constructor!" << endl;
}
// 操作类的属性
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
void Teacher::setAge(int age) {m_iAge = age;}
int Teacher::getAge() {return m_iAge;}
// ====== 类实现END ======

int main(int argc,char *argv[]) {

    Teacher tea;
    cout << "Name:" << tea.getName() << endl;
    cout << "Age:" <<tea.getAge() << endl;

    return 0;
}

初始化列表的成员初始化顺序:
C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

// 构造函数的声明
class CMyClass {
    CMyClass(int x, int y);
    int m_x;
    int m_y;
};
// 构造函数的实现
CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){
    
}

你可能以为上面的代码将会首先做m_y=y,然后做m_x=m_y,最后m_xm_y有相同的值。

但是编译器先初始化m_x,然后是m_y,因为它们是按这样的顺序 声明 的。

结果是m_x将有一个不可预测的值。

有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员;第二个是,如果你决定使用初始化列表,总是按照它们 声明的顺序 罗列这些成员。这将有助于消除混淆。

转载自:必须在构造函数初始化式里进行初始化的数据成员有哪些?

9.拷贝构造函数

格式:类名(const类名&变量名)变量名可写可不写

拷贝构造函数和构造函数一样,也可以在函数名后面添加初始化列表,如果没有去实现它,系统也会自动生成一个默认的。

拷贝构造函数的参数是确定的,不能重载。

在下面的3中情况下,系统自动调用 拷贝构造函数 ,此时不会调用 构造函数

  1. 一个对象作为函数参数,以值传递的方式传入函数体;
  2. 一个对象作为函数返回值,以值传递的方式从函数返回;
  3. 一个对象用于给另外一个对象进行初始化(常称为赋值初始化);

例1,使用 赋值初始化 来创建新的对象 :

class stu{
public:
    // 构造函数
    stu(){
        cout << "stu" << endl;
    }
    // 拷贝构造函数
    stu(const stu& stu01){
        cout << "stu01" << endl;
    }
};

int main(int argc,char *argv[]){
    stu stu01;			// 调用构造函数
    stu stu02 = stu01;	// 调用拷贝构造函数
    stu stu03(stu01);	// 调用拷贝构造函数
    return 0;
}

/* 输出:
stu
stu01
stu01
*/

这里创建了3个实例,但是实际上只有第一次调用了构造函数,之后两次调用的都是拷贝构造函数。

例2,当类作为 函数参数 传递时,将触发拷贝构造函数:

class stu{
public:
    // 构造函数
    stu(){
        cout << "stu" << endl;
    }
    // 拷贝构造函数
    stu(const stu&){
        cout << "stu01" << endl;
    }
};

// 当作为函数参数传递时
void test(stu t){
}

int main(int argc,char *argv[]){
    stu stu01;		// 调用构造函数 
    test(stu01);	// 调用拷贝构造函数
    return 0;
}
/* 输出:
stu
stu01
*/

例3,当对象作为 返回值 时,触发拷贝构造函数:

// 请看笔记中的封装篇(下)中的第3章:对象指针 
// 其中有相关的实例代码

拷贝构造函数中,如何获取引用&的值、如何手写拷贝构造函数:

在本例中,部分类的属性操作方法的后面添加const,将其变为const成员函数(常对象函数),以便我们在拷贝构造函数中使用 &引用 来调用他们获取数据。

// ====== 类声明START ======
class Teacher{
public:
    // 构造函数(没有返回值),这里设定了默认参数
    Teacher(string name = "Jim",int max = 50);
    // 拷贝构造函数
    Teacher(const Teacher& tea);

    // 操作类的属性
    void setName(string name);
    string getName() const;
    int getMax() const;

private:
    // 属性
    string m_strName;   // 名字
    const int m_iMax;   // 最多带的学生数
};
// ====== 类声明END ======

// ====== 类实现START ======
// 构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(string name,int max):m_strName(name),m_iMax(max){
    cout << "02" << endl;
}

// 拷贝构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(const Teacher &teaRaw):m_strName(teaRaw.getName()),m_iMax(teaRaw.getMax()) {
    cout << "03" << endl;
    cout << "teaRaw.getName() = " << teaRaw.getName() << endl;
    cout << "teaRaw.getMax() = " << teaRaw.getMax() << endl;
}

// 操作类的属性
// name
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
// max
int Teacher::getMax() const {return m_iMax;}
// ====== 类实现END ======

int main(int argc,char *argv[]) {

    // 将调用构造函数
    Teacher tea;
    cout << "Name:" << tea.getName() << endl;
    cout << "Max:" << tea.getMax() << endl;

    // 将调用拷贝构造函数(在这一步中,将展示如何获取引用&的值)
    Teacher tea01 = tea;
    cout << "Name:" << tea01.getName() << endl;
    cout << "Max:" << tea01.getMax() << endl;

    return 0;
}

/* 输出:
02
Name:Jim
Max:50
03
teaRaw.getName() = Jim
teaRaw.getMax() = 50
Name:Jim
Max:50
*/

这样我们就能够在 拷贝构造函数 中获取到tea这个对象的值了。

这里需要再次强调,如果在类的属性由const修饰,意味着我们只能在 初始化 的时候为其赋值,其它的时刻任何赋值操作都会报错,所以我们不能在构造函数/拷贝构造函数的{ }为其赋值,只能在 初始化列表 中赋值,且之后再无法修改。

注意: 关于const 成员函数的声明,const 关键字只能放在函数声明的尾部,看起来确实是怪怪的。

10.析构函数

格式:~类名(),不能加任何参数。

成员函数除了析构函数之外,都可以有重载。

本篇为视频教程笔记,视频如下:

C++远征之封装篇(上)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值