面向对象的特点:封装、继承、多态。万事万物皆为对象,对象上有其属性和行为(方法)。
1、封装
将属性与行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制(public、private等)
C++封装
语法:
class 类名{
访问权限:
属性(变量)列表
行为(方法)列表
}
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 圆
class Circle
{
//访问权限
public:
//属性
//半径
int radius;
//方法
//计算周长
double circumference()
{
return 2 * PI * radius;
}
//计算面积
double area()
{
return (PI * radius) * (PI * radius);
}
};
int main()
{
//类和对象
//创建对象:通过一个类创建一个对象的过程,即实例化
Circle c1;
//给对象属性赋值
c1.radius = 2;
//调用对象方法
cout << "半径为 " << c1.radius << " 时,圆周长 = " << c1.circumference() << ", 圆面积 = " << c1.area() << endl;
system("pause");
return 0;
}
输出结果
半径为 2 时,圆周长 = 12.56, 圆面积 = 39.4384
Go语言封装
Go语言中没有class关键字,即没有类这个概念,可以通过函数方法表达类的概念,通过函数方法指定某个结构体来实现类。
语法:
func (变量名 结构体名)函数方法名(参数列表) 返回值列表 {
函数体
}
//结构体名可以是结构体值类型,也可以是结构体指针类型
package main
import "fmt"
const PI = 3.14
//结构体 - 圆
type Circle struct {
//半径
radius int
}
//结构体方法
//计算周长
func (c *Circle) circumference() float64 {
return 2 * PI * float64(c.radius)
}
//计算面积
func (c *Circle) area() float64 {
return (PI * float64(c.radius)) * (PI * float64(c.radius))
}
func main() {
//创建结构体对象,同时给属性赋值
c1 := Circle{
radius: 2,
}
//调用对象方法
fmt.Printf("半径为 %d 时,圆周长 = %f, 圆面积 = %f\n", c1.radius, c1.circumference(), c1.area())
}
输出结果
半径为 2 时,圆周长 = 12.560000, 圆面积 = 39.438400
2、访问权限
C++访问权限
C++访问权限有三种
- public 公共权限 成员 类内可以访问,类外也可以访问
- protected 保护权限 成员 类内可以访问,类外不可以访问,继承时,子类可以访问父类成员
- private 私有权限 成员 类内可以访问,类外不可以访问,继承时,子类不可以访问父类成员
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 人
class Person
{
public:
//姓名
string name;
protected:
//手机号
string phone;
private:
//账号密码
string password;
public:
void setPerson()
{
name = "Tracy";
phone = "13600000000";
password = "123";
}
};
int main()
{
//类 - 封装 - 访问权限
Person p1;
//调用对象公共访问方法
p1.setPerson();
cout << "对象访问权限:能够访问public权限:姓名:" << p1.name << endl;
cout << "对象访问权限:不能访问protected权限:手机号" << endl;
cout << "对象访问权限:不能访问private权限:密码" << endl;
system("pause");
return 0;
}
输出结果
对象访问权限:能够访问public权限:姓名:Tracy
对象访问权限:不能访问protected权限:手机号
对象访问权限:不能访问private权限:密码
Go语言权限访问
Go语言里访问权限也没有public、protected、private关键字,访问权限如下:
- 同包下不同文件的全局资源可以随意访问
- 不同包下资源,首字母大写时可以被不同包访问(可以当作public),首字母小写则不能补不同包访问(可以当作protected)
Go语言结构体对象可以访问自己的所有成员。
3、C++ struct与class区别
struct 成员默认权限是public,class 成员默认权限是private
#include <iostream>
using namespace std;
#define PI 3.14
//类
class C
{
int id; //默认权限为private
};
//结构体
struct S
{
int id; //默认权限为public
};
int main()
{
//类 - 封装 - struct与class区别:struct 成员默认权限是public,class 成员默认权限是private
//访问类成员
C c1;
//c1.id = 5; //报错:不可访问
cout << "访问类成员 c1.id = 5时,报错:不可访问" << endl;
//访问结构体成员
S s1;
s1.id = 5; //正常访问
cout << "访问结构体成员,正常访问, s1.id = " << s1.id << endl;
system("pause");
return 0;
}
输出结果
访问类成员 c1.id = 5时,报错:不可访问
访问结构体成员,正常访问, s1.id = 5
4、C++成员属性私有性
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,可以检查数据的有效性
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 人
class Person
{
private: //以下3个成员属性都设置为私有属性
//姓名
string name;
//手机号
string phone;
//账号密码
string password;
public:
//成员属性姓名:设置可读可写
string getName()
{
return name;
}
void setName(string _name)
{
name = _name;
}
//成员属性手机号:设置只读
string getPhone()
{
return phone;
}
//成员属性账号密码:设置只写,同时检查设置的密码是否大于等于6位
void setPassword(string _pwd)
{
if (_pwd.length() < 6)
{
cout << "账号密码长度大于等于6位" << endl;
return;
}
password = _pwd;
}
};
int main()
{
//类 - 封装 - 成员属性私有性
Person p1;
//调用成员属性姓名:设置可读可写
p1.setName("Tracy");
cout << "成员属性姓名:" << p1.getName() << endl;
//调用成员属性手机号:设置只读
cout << "成员属性手机号:" << p1.getPhone() << endl;
//调用属性账号密码:设置只写,同时检查设置的密码是否大于等于6位
p1.setPassword("123");
system("pause");
return 0;
}
输出结果
成员属性姓名:Tracy
成员属性手机号:
账号密码长度大于等于6位
5、C++构造函数与析构函数
5.1、构造函数与析构函数
一个对象或变量没有初始状态,对其使用后果是未知的,同样的,使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。
C++利用构造函数和析构函数解决以上问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我 们做的事情,因此如果不提供构造函数和析构函数,编译器会提供构造函数和析构函数的空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名( 参数列表 ) { 函数体 }
- 没有返回值,也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以有重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
析构函数语法:~类名() { 函数体 }
- 没有返回值,也不写void
- 函数名称与类名相同,且在函数名称前加上符号~
- 没有参数,因此不可能发生重载
- 程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 圆
class Circle
{
public:
//半径
int radius;
//构造函数
/*
没有返回值,也不写void
函数名称与类名相同
构造函数可以有参数,因此可以有重载
程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
*/
Circle()
{
cout << "Circle 构造函数调用" << endl;
}
//析构函数
/*
没有返回值,也不写void
函数名称与类名相同,且在函数名称前加上符号~
没有参数,因此不可能发生重载
程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会调用一次
*/
~Circle()
{
cout << "~Circle 析构函数调用" << endl;
}
//计算周长
double circumference()
{
return 2 * PI * radius;
}
//计算面积
double area()
{
return (PI * radius) * (PI * radius);
}
};
void f1()
{
Circle c1; //创建对象时,调用构造函数
c1.radius = 2;
cout << "半径为 " << c1.radius << " 时,圆周长 = " << c1.circumference() << ", 圆面积 = " << c1.area() << endl;
//程序在对象销毁前会自动调用析构函数
}
int main()
{
//类和对象 - 构造函数与析构函数
f1();
system("pause");
return 0; //程序
}
输出结果
Circle 构造函数调用
半径为 2 时,圆周长 = 12.56, 圆面积 = 39.4384
~Circle 析构函数调用
5.2、构造函数分类及调用
构造函数分类:
- 按照参数分类:无参构造函数(默认)和有参构造函数
- 按照类型分类:普通构造函数 和 拷贝构造函数
构造函数调用:
构造函数调用 注意:
Circle c1(); //此处编译器会认为是一个函数声明,而不是创建对象,若不实现函数声明,编译时会报错
Circle(c6); //此处编译器会认为 Circle c6; 即创建一个对象 c6,但c6对象已存在,所以编译时会报错
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 圆
class Circle
{
public:
//半径
int radius;
/*
* 构造函数分类:
按照参数分类:无参构造函数(默认)和有参构造函数
按照类型分类:普通构造函数 和 拷贝构造函数
*/
//构造函数 - 无参
Circle()
{
cout << "Circle 无参构造函数调用" << endl;
}
//构造函数 - 有参
Circle(int _radius)
{
radius = _radius;
cout << "Circle 有参构造函数调用,参数 _radius = " << _radius << endl;
}
//拷贝构造函数
Circle(const Circle& _circle)
{
//将参数拷贝给自己成员
radius = _circle.radius;
cout << "Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = " << radius << endl;
}
//析构函数
~Circle()
{
cout << "~Circle 析构函数调用" << endl;
}
//计算周长
double circumference()
{
return 2 * PI * radius;
}
//计算面积
double area()
{
return (PI * radius) * (PI * radius);
}
};
void f1()
{
//构造函数调用
//1、括号法
Circle c1; //调用默认构造函数,即无参的构造函数
Circle c2(3); //调用有参构造函数
Circle c3(c2); //调用拷贝构造函数
cout << "c2 半径为 " << c2.radius << " 时,圆周长 = " << c2.circumference() << ", 圆面积 = " << c2.area() << endl;
cout << "c3 半径为 " << c3.radius << " 时,圆周长 = " << c3.circumference() << ", 圆面积 = " << c3.area() << endl;
//注意:调用默认构造函数时,不要加()
// Circle c1(); //此处会认为是一个函数声明,而不是创建对象,编译时报错
//2、显示法
Circle c4; //调用默认构造函数,即无参的构造函数
Circle c5 = Circle(3); //调用有参构造函数
Circle c6 = Circle(c5); //调用拷贝构造函数
Circle(5); //匿名对象:当前行执行结束后,系统会立即回收掉匿名对象
cout << "匿名对象执行结束后,系统立即回收掉匿名对象" << endl;
//注意:不要使用拷贝构造函数初始化匿名对象
//Circle(c6); //此处编译器会认为 Circle c6; 即创建一个对象 c6,但c6对象已存在,所以编译时会报错
//3、隐式转换法
Circle c7 = 6; //相当于 Circle c7 = Circle(6); 调用有参构造函数
Circle c8 = c7;//相当于 Circle c8 = Circle(c7); 调用拷贝构造函数
}
int main()
{
//类和对象 - 构造函数与析构函数
f1();
system("pause");
return 0; //程序
}
输出结果
Circle 无参构造函数调用
Circle 有参构造函数调用,参数 _radius = 3
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 3
c2 半径为 3 时,圆周长 = 18.84, 圆面积 = 88.7364
c3 半径为 3 时,圆周长 = 18.84, 圆面积 = 88.7364
Circle 无参构造函数调用
Circle 有参构造函数调用,参数 _radius = 3
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 3
Circle 有参构造函数调用,参数 _radius = 5
~Circle 析构函数调用
匿名对象执行结束后,系统立即回收掉匿名对象
Circle 有参构造函数调用,参数 _radius = 6
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 6
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
~Circle 析构函数调用
5.3、拷贝构造函数调用时机
- 使用一个已经创建完的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream>
using namespace std;
#define PI 3.14
//类 - 圆
class Circle
{
public:
//半径
int radius;
//构造函数 - 无参
Circle()
{
cout << "Circle 无参构造函数调用" << endl;
}
//构造函数 - 有参
Circle(int _radius)
{
radius = _radius;
cout << "Circle 有参构造函数调用,参数 _radius = " << _radius << endl;
}
//拷贝构造函数
Circle(const Circle& _circle)
{
//将参数拷贝给自己成员
radius = _circle.radius;
cout << "Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = " << radius << endl;
}
//析构函数
~Circle()
{
cout << "~Circle 析构函数调用" << endl;
}
//计算周长
double circumference()
{
return 2 * PI * radius;
}
//计算面积
double area()
{
return (PI * radius) * (PI * radius);
}
};
//1、使用一个已经创建完的对象来初始化一个新对象
void f1()
{
Circle c1(6);
Circle c2(c1);
}
void callCircle(Circle _circle) {
}
//2、值传递的方式给函数参数传值
void f2()
{
Circle c1(5);
callCircle(c1);
}
Circle returnCircle()
{
Circle c1(8);
cout << "returnCircle函数内c1地址:" << &c1 << endl;
return c1;
}
//3、以值方式返回局部对象
void f3()
{
Circle c1 = returnCircle();
cout << "调用returnCircle函数后返回值地址:" << &c1 << endl;
}
int main()
{
//类和对象 - 构造函数 - 拷贝构造函数调用时机
/*
使用一个已经创建完的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
*/
cout << "--- 1、使用一个已经创建完的对象来初始化一个新对象 ---" << endl;
f1();
cout << endl << "--- 2、值传递的方式给函数参数传值 ---" << endl;
f2();
cout << endl << "--- 3、以值方式返回局部对象 ---" << endl;
f3();
system("pause");
return 0; //程序
}
输出结果
--- 1、使用一个已经创建完的对象来初始化一个新对象 ---
Circle 有参构造函数调用,参数 _radius = 6
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 6
~Circle 析构函数调用
~Circle 析构函数调用
--- 2、值传递的方式给函数参数传值 ---
Circle 有参构造函数调用,参数 _radius = 5
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 5
~Circle 析构函数调用
~Circle 析构函数调用
--- 3、以值方式返回局部对象 ---
Circle 有参构造函数调用,参数 _radius = 8
returnCircle函数内c1地址:000000AB8FEFF3A4
Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = 8
~Circle 析构函数调用
调用returnCircle函数后返回值地址:000000AB8FEFF4E4
~Circle 析构函数调用
5.4、构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
- 如果用户定义了有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
- 如果用户定义了拷贝构造函数,C++不再提供其他构造函数
#include <iostream>
using namespace std;
//类 - 圆
//构造函数只有有参构造函数
class Circle
{
public:
//半径
int radius;
//构造函数 - 有参
Circle(int _radius)
{
radius = _radius;
cout << "Circle 有参构造函数调用,参数 _radius = " << _radius << endl;
}
//析构函数
~Circle()
{
cout << "~Circle 析构函数调用" << endl;
}
};
//类 - 圆
//构造函数只有拷贝构造函数
class Circle2
{
public:
//半径
int radius;
//拷贝构造函数
Circle2(const Circle2& _circle)
{
radius = _circle.radius;
cout << "Circle 拷贝构造函数调用,常量引用参数拷贝结果 radius = " << radius << endl;
}
//析构函数
~Circle2()
{
cout << "~Circle 析构函数调用" << endl;
}
};
void f1()
{
//构造函数只有有参构造函数
//Circle c1; //使用无参构造函数创建对象,报错:不存在默认构造函数
Circle c2(5); //使用有参构造函数创建对象,正常
Circle c3(c2); //使用拷贝构造函数创建对象,正常
cout << "构造函数只有有参构造函数,使用拷贝构造函数创建对象时,c3.radius = " << c3.radius << endl;
}
void f2()
{
//构造函数只有拷贝构造函数,没有提供默认的构造函数
//Circle2 c1; //使用无参构造函数创建对象,报错:不存在默认构造函数
}
int main()
{
//类和对象 - 构造函数调用规则
/*
默认情况下,C++编译器至少给一个类添加3个函数:
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
如果用户定义了有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
如果用户定义了拷贝构造函数,C++不再提供其他普通构造函数
*/
cout << "--- 1、如果用户定义了有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数 ---" << endl;
f1();
cout << endl << "--- 2、如果用户定义了拷贝构造函数,C++不再提供其他构造函数 ---" << endl;
f2();
system("pause");
return 0; //程序
}
输出结果
--- 1、如果用户定义了有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数 ---
Circle 有参构造函数调用,参数 _radius = 5
构造函数只有有参构造函数,使用拷贝构造函数创建对象时,c3.radius = 5
~Circle 析构函数调用
~Circle 析构函数调用
--- 2、如果用户定义了拷贝构造函数,C++不再提供其他构造函数 ---
5.5、深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
注:
如果属性有堆区开辟空间的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
浅拷贝存在的问题
以下一成员属性在堆区开辟空间
#include <iostream>
using namespace std;
//类 - 圆
class Circle
{
public:
//半径
int radius;
//长度(仅作示例)
int* length;
Circle(int _radius, int _length)
{
radius = _radius;
length = new int(_length); //堆区开辟空间
}
//析构函数
~Circle()
{
//将堆区开辟的空间释放
if (length != NULL)
{
delete length;
length = NULL;
}
cout << "~Circle 析构函数调用" << endl;
}
};
int main()
{
//类和对象 - 构造函数 - 深拷贝与浅拷贝
/*
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
*/
cout << "--- 浅拷贝存在的问题 ---" << endl;
Circle c1(2, 5);
Circle c2(c1);
cout << "--- 析构函数手动释放堆区空间时,抛出异常 ---" << endl;
//原因:默认的拷贝构造函数为浅拷贝,即简单复制,当c2空间释放了,length指针堆上分配的空间也被释放,再释放c1时,调用析构函数,释放length时,由于已经被释放,因此抛异常
system("pause");
return 0; //程序
}
输出结果后,关闭程序,抛出异常:
原因:默认的拷贝构造函数为浅拷贝,即简单复制,当c2空间释放了,length指针堆上分配的空间也被释放,再释放c1时,调用析构函数,释放length时,由于已经被释放,再次重复释放,因此抛异常。
解决方案
提供拷贝构造函数,实现深拷贝
#include <iostream>
using namespace std;
//类 - 圆
class Circle
{
public:
//半径
int radius;
//长度(仅作示例)
int* length;
Circle(int _radius, int _length)
{
radius = _radius;
length = new int(_length); //堆区开辟空间
}
//解决浅拷贝的问题:拷贝构造函数实现深拷贝
Circle(const Circle& _circle)
{
radius = _circle.radius;
//length = _circle.length; //编译器默认提供浅拷贝
length = new int(*_circle.length); //堆区开辟空间,实现深拷贝
}
//析构函数
~Circle()
{
//将堆区开辟的空间释放
if (length != NULL)
{
delete length;
length = NULL;
}
cout << "~Circle 析构函数调用" << endl;
}
};
int main()
{
//类和对象 - 构造函数 - 深拷贝与浅拷贝
/*
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
*/
cout << "--- 解决浅拷贝存在的问题:拷贝构造函数实现深拷贝 ---" << endl;
Circle c1(2, 5);
Circle c2(c1);
system("pause");
return 0; //程序
}
5.6、初始化列表
初始化列表用来初始化属性
语法:
构造函数(): 属性1(值1),属性2(值2)...{} //初始化默认值
构造函数(参数列表): 属性1(参数1),属性2(参数2)...{}
#include <iostream>
using namespace std;
//类 - 圆 - 初始化列表方式一
class Circle
{
public:
//半径
int radius;
//初始化列表,为成员属性设置初始值
Circle() :radius(5)
{
cout << "初始化列表Circle() :radius(5), radius = " << radius << endl;
}
};
//类 - 圆 - 初始化列表方式二
class Circle2
{
public:
//半径
int radius;
//初始化列表,为成员属性设置初始值
Circle2(int _radius) :radius(_radius)
{
cout << "初始化列表Circle2(int _radius) :radius(_radius), radius = " << radius << endl;
}
};
int main()
{
//类和对象 - 构造函数 - 初始化列表
Circle c1;
Circle2 c2(3);
system("pause");
return 0; //程序
}
输出结果
初始化列表Circle() :radius(5), radius = 5
初始化列表Circle2(int _radius) :radius(_radius), radius = 3
5.7、类对象作为类成员,构造函数与析构函数先后执行顺序
- 构造顺序:先调用对象成员的构造函数,再调用本类的构造函数
- 析构顺序:与构造顺序相反,即先调用本类的析构函数,再调用对象成员的析构函数
#include <iostream>
using namespace std;
class Point
{
public:
int x;
int y;
//构造函数
Point(int _x, int _y) :x(_x), y(_y)
{
cout << "Point 构造函数" << endl;
}
//析构函数
~Point()
{
cout << "Point 析构函数" << endl;
}
};
//类 - 圆
class Circle
{
public:
//半径
int radius;
//圆心 - 类对象成员
Point point;
//构造函数
// Point point(_x, _y);
Circle(int _radius, int _x, int _y):radius(_radius),point(_x,_y)
{
cout << "Circle 构造函数" << endl;
}
//析构函数
~Circle()
{
cout << "Circle 析构函数" << endl;
}
};
int main()
{
//类和对象 - 构造函数 - 类对象作为类成员,构造函数与析构函数先后执行顺序
/*
构造顺序:先调用对象成员的构造函数,再调用本类的构造函数
析构顺序:与构造顺序相反,即先调用本类的析构函数,再调用对象成员的析构函数
*/
Circle(2, 3, 4);
system("pause");
return 0; //程序
}
输出结果
Point 构造函数
Circle 构造函数
Circle 析构函数
Point 析构函数