C++进阶语法——OOP(面向对象)【学习笔记(四)】
文章目录
- 1、C++ OOP⾯向对象开发
- 1.1 类(classes)和对象(objects)
- 1.2 public、private、protected访问权限
- 1.3 实现成员⽅法
- 1.4 构造函数(constructor)和 析构函数(destructor)
- 1.4.1 构造函数(constructor)
- 1.4.2 析构函数(destructor)
- 1.5 代理构造函数(delegating constructor)
- 1.6 拷⻉构造函数(copy constructor)
- 1.7 浅拷贝(shallow copy)和深拷贝(deep copy)
- 1.7.1 浅拷⻉(shallow copy)
- 1.7.2 深拷⻉(deep copy)
- 1.8 在类中使⽤const
- 1.9 在类中使⽤static
- 1.10、 struct 和 class区别
1、C++ OOP⾯向对象开发
1.1 类(classes)和对象(objects)
简单介绍一下类:
• C++ 类(classes)
------->• 创建对象⽤的蓝图(blueprint)
------->• ⽤户⾃定义的数据类型
------->• 有成员属性(data)和成员⽅法(methods)
------->• 可以隐藏属性和⽅法(private)
------->• 可以提供公共接⼝(public)
• 示例: Account, Student, std::vector, std::string
简单介绍一下对象:
• C++ 对象(objects)
------->• 由类创建⽽来
------->• 表示类的⼀个具体的实例(Instance)
------->• 可以有很多个实例,每个都有独⽴的身份
------->• 每个对象都可以使⽤类中定义的⽅法
• Account对象示例:
------->• Jobs、Alice 的account是Account类的实例
------->• 每个实例都有它的余额、可以提现、存钱
虽然 int 不是类,这里把它看成一个类,int 后面的 high_score 和 low_score 可以看成 int 类实例化后的对象;
创建 Account 类后,实例化2个对象,分别是:jobs_account 和 alice_account;
std是类,后面跟着的容器 vector 和字符串 string 是对象;
如下图所示,
A、声明一个类:
B、创建类的对象:
还可以根据类创建一个指针,并且在堆上使用关键字 new 动态分配内存空间,使用后用 delete 删除释放内存空间,
一旦有了类的对象,就可以像 C++ 其他变量去使用
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
// 属性
string name {"None"};
double balance {0.0};
// 方法
bool deposit(double amount); // 存款
bool withdraw(double amount); // 取款
};
int main()
{
Account jobs_account; // 初始化属性
Account alice_account; // 初始化属性
Account accounts [] {jobs_account, alice_account}; // 数组
vector<Account> accounts_vec {jobs_account}; // vector
accounts_vec.push_back(alice_account);
Account *p_account = new Account(); // 指针
delete p_account;
return 0;
}
C、获取类的属性或⽅法:
使⽤点操作符:
如果是⼀个指向对象的指针,可以解引⽤或者使⽤箭头操作符,
需要注意: 使用 . 操作符的时候需要在前面加一个括号 () ,因为 . 操作符的优先级高于 * 解引用运算符的优先级,
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
public:
// 属性
string name {"None"};
double balance {0.0};
// 方法
bool deposit(double amount){
balance += amount;
cout << name << "刚存入" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
}; // 存款
bool withdraw(double amount){
if (balance >= amount){
balance -= amount;
cout << name << "刚取出" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
} else {
cout << name << "余额不足,取款失败" << endl;
return false;
}
}; // 取款
};
int main()
{
Account jobs_account;
jobs_account.name = "Jobs";
jobs_account.balance = 1000.0;
jobs_account.deposit(500.0);
Account *alice_account = new Account();
(*alice_account).name = "Alice";
alice_account->balance = 2000.0;
(*alice_account).deposit(1000.0);
alice_account->withdraw(500.0);
return 0;
}
1.2 public、private、protected访问权限
• public
----->• 可以被任何实体访问
• private
----->• 只能被本类的⽅法访问
• protected
----->• 可以被本类和⼦类(继承)的⽅法访问
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"None"};
double balance {0.0};
public:
// 方法
bool deposit(double amount){
balance += amount;
cout << name << "刚存入" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
}; // 存款
bool withdraw(double amount){
if (balance >= amount){
balance -= amount;
cout << name << "刚取出" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
} else {
cout << name << "余额不足,取款失败" << endl;
return false;
}
}; // 取款
};
int main()
{
Account jobs_account;
// jobs_account.name = "Jobs";
// jobs_account.balance = 1000.0;
// cout << jobs_account.name << "的余额为" << jobs_account.balance << "元" << endl;
jobs_account.deposit(500.0);
return 0;
}
1.3 实现成员⽅法
在类中定义成员方法,适用于代码量较少的程序,
在类外面定义成员方法,适用于代码量较大的程序,
代码:
编译器无法区分 set_name 函数是类内的函数还是其他函数,所以在 set_name 前面添加类名::,表示这是类的成员函数,
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"None"};
double balance {0.0};
public:
// 方法
// 设置余额
void set_balance(double amount){balance = amount;};
// 获取余额
double get_balance(){return balance;};
// 设置名称
void set_name(string name);
// 获取名称
string get_name();
// 存款
bool deposit(double amount);
// 取款
bool withdraw(double amount);
};
void Account::set_name(string name){
this->name = name; // this指针指向当前对象
}
string Account::get_name(){
return name;
}
bool Account::deposit(double amount){
balance += amount;
cout << name << "刚存入" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
}
bool Account::withdraw(double amount){
if (balance >= amount){
balance -= amount;
cout << name << "刚取出" << amount << "元,现在余额为" << balance << "元" << endl;
return true;
} else {
cout << name << "余额不足,取款失败" << endl;
return false;
}
}
int main()
{
Account alice_account;
alice_account.set_name("Alice's account"); // 设置名称
alice_account.set_balance(1000.0); // 设置余额
cout << alice_account.get_name() << "的余额为" << alice_account.get_balance() << "元" << endl;
alice_account.deposit(200.0);
alice_account.withdraw(500.0);
alice_account.withdraw(1500.0);
return 0;
}
上面的代码看着比较繁杂,我们可以用头文件和源文件编写。
为了防止头文件被多次导入造成一些冲突,一般添加如下语句,关于头文件被多次导入造成一些冲突可参考这个链接:CSDN链接,例如创建 Account.h 头文件:
#ifndef ACCOUNT_H
#define ACCOUNT_H
#endif // ACCOUNT_H
整个 Account.h 头文件代码如下,一般不在头文件写 using namespace std;
,一般显示地使用 std:string
,
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>
class Account
{
private:
// 属性
std::string name {"None"};
double balance {0.0};
public:
// 方法
// 设置余额
void set_balance(double amount){balance = amount;};
// 获取余额
double get_balance(){return balance;};
// 设置名称
void set_name(std::string name);
// 获取名称
std::string get_name();
// 存款
bool deposit(double amount);
// 取款
bool withdraw(double amount);
};
#endif // ACCOUNT_H
然后再根据头文件编写类的成员方法的定义,因为在源文件引入了头文件#include "Account.h"
,所以不需要再写 #include <string>
,这里也是显示地引入 std::cout
,std::endl
,
#include "Account.h"
#include <iostream>
void Account::set_name(std::string name){
this->name = name; // this指针指向当前对象
}
std::string Account::get_name(){
return name;
}
bool Account::deposit(double amount){
balance += amount;
std::cout << name << "刚存入" << amount << "元,现在余额为" << balance << "元" << std::endl;
return true;
}
bool Account::withdraw(double amount){
if (balance >= amount){
balance -= amount;
std::cout << name << "刚取出" << amount << "元,现在余额为" << balance << "元" << std::endl;
return true;
} else {
std::cout << name << "余额不足,取款失败" << std::endl;
return false;
}
}
最后是程序的 main.cpp 文件,直接编译 main.cpp 即可,
#include <iostream>
#include <string>
#include <vector>
#include "Account.h" // 引入头文件
using namespace std;
int main()
{
Account alice_account;
alice_account.set_name("Alice's account"); // 设置名称
alice_account.set_balance(1000.0); // 设置余额
cout << alice_account.get_name() << "的余额为" << alice_account.get_balance() << "元" << endl;
alice_account.deposit(200.0);
alice_account.withdraw(500.0);
alice_account.withdraw(1500.0);
return 0;
}
1.4 构造函数(constructor)和 析构函数(destructor)
1.4.1 构造函数(constructor)
• 特殊的成员⽅法
• 对象创建的时候⾃动调⽤
• 适⽤于实例参数初始化
• 函数名和类的名称⼀致
• ⽆需设置返回类型
• 可以被重载(overload)
1.4.2 析构函数(destructor)
• 特殊的成员⽅法
• 函数名和类的名称⼀致,前⾯跟着⼀个~波浪符号
• 对象销毁的时候⾃动调⽤
• 没有参数,没有返回类型
• 只有⼀个析构函数(不能重载)
• 适⽤于释放内存等资源
如果不手动创建构造函数和析构函数,那么C++会自动帮助创建构造函数和析构函数,只不过都是空的,
构造函数(constructor)和析构函数(destructor):
构造函数在栈上创建,当程序运行结束时,他们各自的析构函数会被调用,所以会调用4次析构函数,
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
std::string name {"account"};
double balance {0.0};
public:
void setName(string name){ this->name = name; }; // 设置名称
// 构造函数
Account(){
cout << "没有参数的构造函数被调用" << endl;
};
Account(std::string name){
cout << "带string name参数的构造函数被调用" << endl;
};
Account(double balance){
cout << "带double balance参数的构造函数被调用" << endl;
};
Account(string name, double balance){
cout << "带string name和double balance参数的构造函数被调用" << endl;
};
// 析构函数
~Account(){
cout << name << " 的析构函数被调用" << endl;
};
};
int main()
{
// 用{}表示作用域,{}内的程序运行后会调用析构函数
{
Account alice_account;
alice_account.setName("Alice's account"); // 设置名称
}
// 出栈:后入先出
{
Account jobs_account;
jobs_account.setName("Jobs's account");
Account bill_account("Bill's account");
bill_account.setName("Bill's account");
Account steve_account(1000.0);
steve_account.setName("Steve's account");
}
Account *mark_account = new Account("Mark's account", 1000.0);
mark_account->setName("Mark's account");
delete mark_account;
return 0;
}
输出:
没有参数的构造函数被调用
Alice's account 的析构函数被调用
没有参数的构造函数被调用
带string name参数的构造函数被调用
带double balance参数的构造函数被调用
Steve's account 的析构函数被调用
Bill's account 的析构函数被调用
Jobs's account 的析构函数被调用
带string name和double balance参数的构造函数被调用
Mark's account 的析构函数被调用
构造函数初始化列表:
:name {name} 中,冒号后面的 name 类成员属性的 name,{} 里面的 name 是函数的形参 name,
代码1:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
std::string name {"account"};
double balance {0.0};
public:
// 打印信息
void printInfo();
// 构造函数,初始化参数
Account(string name, double balance);
};
void Account::printInfo(){
cout << "name: " << name << ", balance: " << balance << endl;
}
// 构造函数内部初始化参数
Account::Account(string name, double balance){
this->name = name;
this->balance = balance;
}
int main()
{
Account *mark_account = new Account("Mark's account", 1000.0);
mark_account->printInfo(); // 打印信息
delete mark_account;
return 0;
}
输出:
name: Mark's account, balance: 1000
代码2:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
std::string name {"account"};
double balance {0.0};
public:
// 打印信息
void printInfo();
// 构造函数,初始化参数
Account();
Account(string name);
Account(string name, double balance);
};
void Account::printInfo(){
cout << "name: " << name << ", balance: " << balance << endl;
}
// 构造函数内部初始化参数
// Account::Account(){
// name = "None";
// balance = 0.0;
// }
// Account ::Account(string name){
// this->name = name;
// balance = 0.0;
// }
// Account::Account(string name, double balance){
// this->name = name;
// this->balance = balance;
// }
// 构造函数初始化列表
Account::Account()
:name{"none"}, balance{0.0}{
}
Account::Account(string name)
:name{name}, balance{0.0}{
}
Account::Account(string name, double balance)
:name{name}, balance{balance}{
}
int main()
{
Account alice_account;
alice_account.printInfo(); // 打印信息
Account jobs_account {"Jobs's account"};
jobs_account.printInfo();
Account bill_account {"Bill's account", 1000.0};
bill_account.printInfo();
return 0;
}
输出:
name: none, balance: 0
name: Jobs's account, balance: 0
name: Bill's account, balance: 1000
1.5 代理构造函数(delegating constructor)
• 重载的构造函数很相似
• 冗余的代码可能会导致错误
• C++ 允许使⽤代理构造函数
------>• 在⼀个构造函数初始化列表中调⽤另⼀个构造函数
代码: 建议使用 debug 查看程序运行过程
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
std::string name {"account"};
double balance {0.0};
public:
// 打印信息
void printInfo();
// 构造函数,初始化参数
Account();
Account(string name);
Account(string name, double balance);
};
void Account::printInfo(){
cout << "name: " << name << ", balance: " << balance << endl;
}
// 构造函数初始化列表
Account::Account()
:Account {"none",0}{
}
Account::Account(string name)
:Account {name, 0}{
}
Account::Account(string name, double balance)
:name{name}, balance{balance}{
}
int main()
{
Account alice_account;
alice_account.printInfo(); // 打印信息
Account jobs_account {"Jobs's account"};
jobs_account.printInfo();
Account bill_account {"Bill's account", 1000.0};
bill_account.printInfo();
return 0;
}
输出:
name: none, balance: 0
name: Jobs's account, balance: 0
name: Bill's account, balance: 1000
1.6 拷⻉构造函数(copy constructor)
• 当对象被拷⻉时,C++必须从已存在的对象复制出⼀个新的对象
• 何时使⽤拷⻉构造函数?
------->• 以值传递⽅式传递对象给函数(作参数)
------->• 函数以值⽅式返回对象
------->• 使⽤已存在的对象复制新的对象
• 如果不提供⾃⼰写的copy constructor,编译器会⾃动⽣成⼀个(可能不符合要求),如果是指针,拷贝的是地址,所以编译器自动生成的拷贝构造函数是浅拷贝,
A、拷⻉构造函数——值传递
B、拷⻉构造函数——以值⽅式返回
以值⽅式返回拷⻉构造函数返回的 an_account 副本
C、拷⻉构造函数——使⽤已存在的对象复制新的对象
D、拷⻉构造函数的声明
首先拷⻉构造函数也是构造函数,所以函数的名称和类的名称一样,函数的参数列表是使用引用的方式传递的,
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"account"};
double balance {0.0};
public:
string getName() {return name;} // 获取name
double getBalance() {return balance;} // 获取balance
// 构造函数
Account(string name = "none", double balance = 0.0);
// 析构函数
~Account();
// 拷贝构造函数
Account(const Account &source);
};
Account::Account(string name, double balance)
:name {name}, balance {balance}
{
cout << "2个参数的构造函数被调用,name:" << name << endl;
}
Account::~Account()
{
cout << "析构函数被调用,name:" << name << endl;
}
// 拷贝构造函数:根据已存在对象的属性来更新新对象的属性(name,balance)
Account::Account(const Account &source)
:name {source.name}, balance {source.balance} // 初始化列表
{
cout << "拷贝构造函数被调用,是" << source.name << "的拷贝" << endl;
}
// 打印账户信息
void printAccountInfo(Account acc)
{
cout << acc.getName() << "的余额是:" << acc.getBalance() << endl;
}
int main()
{
// 1.值传递的方式给函数传递参数
// Account alice_account;
// printAccountInfo(alice_account);
// 2.基于已存在的对象创建新的对象
Account alice_account {"Alice's account", 1000.0};
Account new_account {alice_account}; // 拷贝构造函数被调用
return 0;
}
输出:
2个参数的构造函数被调用,name:Alice's account
拷贝构造函数被调用,是Alice's account的拷贝
析构函数被调用,name:Alice's account
析构函数被调用,name:Alice's account
1.7 浅拷贝(shallow copy)和深拷贝(deep copy)
• 如果不提供⾃⼰写的copy constructor,编译器会⽣成默认的
------>• 将⽬标对象的值逐个拷⻉过来;
------>• 如果是指针,拷⻉的是值(指向的地址),⽽不是指向的对象,称为浅拷贝
------>• 在析构函数中释放内存时,其他对象中的指针可能还在指向被释放的资源,在析构函数释放内存资源时可能会报错,例如下面的代码,如果使用编译器默认的拷贝构造函数,在进行析构函数释放内存资源时可能会报错
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"account"};
double *balance {nullptr};
public:
double get_balance() {return *balance;}; // 获取余额
string get_name() {return name;}; // 获取名字
// 构造函数
Account(string name = "none", double balance = 0.0); // 有默认参数的构造函数
// 拷贝构造函数
Account(const Account &source);
// 析构函数
~Account();
};
Account::Account(string name, double balance)
{
this->name = name;
this->balance = new double {balance}; // 堆上分配内存
cout << "2个参数的构造函数被调用,name: " << name << endl;
}
// 拷贝构造函数
Account::Account(const Account &source)
:Account {source.name, *source.balance} // 代理构造函数
{
cout << "拷贝构造函数被调用,是" << source.name << "的拷贝" << endl;
}
Account::~Account(){
if (balance != nullptr)
delete balance; // 释放内存
cout << "析构函数被调用,name: " << name << endl;
}
int main()
{
// 演示浅拷贝和深拷贝
Account alice_account {"Alice", 1000.0};
Account new_account {alice_account}; // 拷贝构造函数被调用
// cout << new_account.get_balance() << endl; // 1000.0
return 0;
}
如果使用编译器默认的拷贝构造函数,在进行 alice_account 析构函数释放内存时会报错,如下图,如果使用自己写的拷贝构造函数就不会报错,
1.7.1 浅拷⻉(shallow copy)
如下图,alice_account 浅拷贝到 new_account,编译器会默认创建一个拷贝构造函数,它是逐个元素按值拷贝的,如果是指针,则是拷贝的地址。当调用析构函数的时候,由于后进先出的原则,new_account 上的地址被释放后,alice_account 的地址也会被释放,但地址已经被释放,所以会造成堆空间重复释放的问题,导致程序报错,
编译器默认生成的拷贝构造函数如下:
1.7.2 深拷⻉(deep copy)
在函数内部,在堆上分配一个新的 double 类型的内存空间,初始化为1000,并且把新的内存空间给1000,
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"account"};
double *balance {nullptr};
public:
double get_balance() {return *balance;}; // 获取余额
string get_name() {return name;}; // 获取名字
// 构造函数
Account(string name = "none", double balance = 0.0); // 有默认参数的构造函数
// 拷贝构造函数
Account(const Account &source);
// 析构函数
~Account();
};
Account::Account(string name, double balance)
{
this->name = name;
this->balance = new double {balance}; // 堆上分配内存
cout << "2个参数的构造函数被调用,name: " << name << endl;
}
// 拷贝构造函数
Account::Account(const Account &source)
:Account {source.name, *source.balance} // 代理构造函数
{
cout << "拷贝构造函数被调用,是" << source.name << "的拷贝" << endl;
}
Account::~Account(){
if (balance != nullptr)
delete balance; // 释放内存
cout << "析构函数被调用,name: " << name << endl;
}
int main()
{
// 演示浅拷贝和深拷贝
Account alice_account {"Alice", 1000.0};
Account new_account {alice_account}; // 拷贝构造函数被调用
// cout << new_account.get_balance() << endl; // 1000.0
return 0;
}
1.8 在类中使⽤const
• 常函数
------->• 函数名称后加const
------->• 函数体内不可以修改类成员属性
• 常对象
------->• 声明对象时前⾯加const
------->• 不可以修改常对象的成员属性
------->• 不能调用普通的成员方法,只能调⽤常函数
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
double balance {0.0};
public:
string name {"account"};
void set_new_name(string new_name) const{ // 修改名字
// name = new_name;
}
string get_name() const{ // 获取名字
return name;
}
// 构造函数
Account(string name = "none", double balance = 0.0);
~Account();
};
Account::Account(string name, double balance)
: balance{balance} ,name{name}{
cout << "构造函数" << endl;
}
Account::~Account()
{
cout << "析构函数" << endl;
}
int main()
{
// 常函数
// Account alice_account {"Alice", 1000.0};
// alice_account.set_new_name("Alice2"); // 修改名字
// cout << alice_account.get_name() << endl; // Alice2
// 常对象
const Account bob_account {"Bob", 2000.0};
// bob_account.name = "Bob2"; // 修改名字
cout << bob_account.get_name() << endl; // Bob2
return 0;
}
1.9 在类中使⽤static
• 静态成员变量
------->• 所有对象共有同⼀份数据(数据共享)
------->• 在类中声明,类外初始化
• 静态成员函数
------->• 所有对象共享同⼀个函数
------->• 只能访问静态成员变量
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Account
{
private:
// 属性
string name {"account"};
double balance {0.0};
static int num_accounts;
public:
static int get_num_accounts();
Account(string name, double balance);
~Account();
};
int Account::num_accounts {0};
Account::Account(string name, double balance)
:name{name}, balance{balance}{
num_accounts++;
}
Account::~Account()
{
num_accounts--;
}
int Account::get_num_accounts() // 不需要使用static关键字
{
// 静态成员方法只能访问静态成员变量
// name = "test";
return num_accounts;
}
int main()
{
cout << Account::get_num_accounts() << endl;
Account alice_account {"Alice's account", 1000.0};
cout << alice_account.get_num_accounts() << endl;
Account bob_account {"Bob's account", 2000.0};
cout << Account::get_num_accounts() << endl;
{
Account test_account {"test", 100.0};
cout << Account::get_num_accounts() << endl;
}
cout << Account::get_num_accounts() << endl;
return 0;
}
1.10、 struct 和 class区别
区别在于 class 成员权限默认是 private,而 struct 的成员权限默认是 public,