C++类和对象
一:什么是对象,什么是类?
c++认为万事万物都为对象,对象上有其属性和性质。
例如:
人可以作为对象,属性有名称、年龄···,行为有走、跳、唱歌···。
具有相同性质的对象,抽象的称为类。
例如:
人属于人类,车属于车类。
二:封装
封装是面向对象三大特性之一。
意义:
- 将属性和行为作为一个整体,表现生活中的事物。
- 将属性和行为加以权限控制。
2.1 意义一
在设计类的时候,属性和行为写在一起,表现事物。
语法:class 类名{ 访问权限:属性 / 行为 };
例1:设计一个圆类,求圆的周长。
#include <iostream>
using namespace std;
double PI = 3.14;
//圆求周长的公式: PI * 2 * 半径
class Circle
{
//访问权限
//公共权限
public:
//属性
int m_r; //半径
//行为
//获取周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
//通过圆类创建一个具体的对象
//实例化:通过一个类创建一个对象的过程
Circle c1;
//给圆的半径赋值
c1.m_r = 10;
cout << "圆的周长:" << c1.calculateZC() << endl;
return 0;
}
例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。
#include <iostream>
using namespace std;
//设计学生类
class Student
{
//公共权限
public:
//属性
// 赋值法一:直接通过属性赋值
string m_name; // 姓名
int m_id; // 学号
//行为
//显示姓名和学号
void Showstudent()
{
cout << "姓名:" << m_name << " " << "学号:" << m_id << endl;
}
// 赋值法二:通过行为来给属性赋值
void setname(string name)
{
m_name = name;
}
void setid(int id)
{
m_id = id;
}
};
int main()
{
//创建一个具体的学生 实例化
Student s1;
//给学生属性进行赋值
s1.m_name = "张三";
s1.m_id = 212;
//显示学生属性
s1.Showstudent();
Student s2;
//给学生属性进行赋值
s2.setname("李四");
s2.setid(12);
s2.Showstudent();
return 0;
}
类中的属性和行为统一称为成员。
属性也被称为 成员属性 和 成员变量。
行为也被称为 成员函数 和 成员方法。
2.2 意义二
类在设计时,可以把属性和行为放在不同的权限下,加以控制。
访问权限:
- public 公共权限 (类内可以访问,类外可以访问)
- protected 保护权限 (类内可以访问,类外不可以访问)
- private 私有权限 (类内可以访问,类外不可以访问)
例3:
#include <iostream>
using namespace std;
class People
{
//公共权限
public:
string m_name; // 姓名
//保护权限
protected:
string m_car; // 汽车
//私有权限
private:
int m_passward; // 密码
public:
void func()
{
m_name = "张三";
m_car = "宝马";
m_passward = 1234;
}
};
int main()
{
//实例化对象
People p1;
p1.m_name = "李四";
//p1.m_car = "奔驰"; //会报错,访问不了
//p1.m_passward = 123; //会报错,访问不了
return 0;
}
2.3 struct和class的区别
默认的访问权限不同。
struct默认权限为公共。
class默认权限为私有。
#include <iostream>
using namespace std;
struct student
{
int m_id; // 默认公共权限
};
class people
{
int m_name; // 默认私有权限
};
int main()
{
return 0;
}
2.4 成员属性设计为私有
优点:1.将所有成员属性设计为私有,可以自己控制读写权限。
2.对于写权限,我们可以检测数据的有效性。
例4:
#include <iostream>
using namespace std;
// 1.自己控制读写权限
class people
{
public:
//设置姓名
void setname(string name)
{
m_name = name;
}
//获取姓名
string showname()
{
return m_name;
}
//获取年龄
int showage()
{
return m_age;
}
//设置偶像
void setidol(string idol)
{
m_idol = idol;
}
private:
string m_name; // 可读可写
int m_age=18; // 只读
string m_idol; // 只写
};
int main()
{
people p1;
p1.setname("张三");
cout << "姓名:" << p1.showname() << endl;
cout << "年龄:" << p1.showage() << endl;
p1.setidol("李四");
return 0;
}
#include <iostream>
using namespace std;
// 2.检测数据的有效性
class people
{
public:
//设置姓名
void setname(string name)
{
m_name = name;
}
//获取姓名
string showname()
{
return m_name;
}
//设置年龄
void setage(int age)
{
if (age < 0 || age > 150) //控制数据的有效性
{
cout << "输入年龄不符合条件" << endl;
return;
}
m_age = age;
}
//获取年龄
int showage()
{
return m_age;
}
//设置偶像
void setidol(string idol)
{
m_idol = idol;
}
private:
string m_name; // 可读可写
int m_age=18; // 可读可写(年龄只能在0-150内)
string m_idol; // 只写
};
int main()
{
people p1;
p1.setname("张三");
cout << "姓名:" << p1.showname() << endl;
cout << "年龄:" << p1.showage() << endl;
p1.setidol("李四");
p1.setage(130);
cout << "年龄:" << p1.showage() << endl;
return 0;
}
例5:设计立方体类,求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等。
#include <iostream>
using namespace std;
class Cube
{
public:
//设置长
void setL(int L)
{
m_L = L;
}
//获取长
int getL()
{
return m_L;
}
//设置宽
void setW(int W)
{
m_W = W;
}
//获取宽
int getW()
{
return m_W;
}
//设置高
void setH(int H)
{
m_H = H;
}
//获取高
int getH()
{
return m_H;
}
//利用成员函数来判断两个立方体是否相同
bool issame1(Cube& c)
{
if (m_H == c.getH() && m_L == c.getL() && m_W == c.getW())
{
return true;
}
return false;
}
private:
int m_L;
int m_H;
int m_W;
int calculateS()
{
return 2 * m_L * m_H + 2 * m_L * m_W + 2 * m_H * m_W;
}
int calculateV()
{
return m_L * m_W * m_H;
}
};
//利用全局函数来判断两个立方体是否相同
bool issame(Cube& c1, Cube& c2) //引用节省空间
{
if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW())
{
return true;
}
return false;
}
int main()
{
Cube c1;
c1.setH(10);
c1.setL(10);
c1.setW(10);
Cube c2;
c2.setH(10);
c2.setL(10);
c2.setW(10);
//全局函数判断
if (issame(c1, c2))
cout << "两个立方体是相同的" << endl;
else
cout << "两个立方体是不相同的" << endl;
//成员函数判断
bool ret = c1.issame1(c2);
if(ret)
cout << "两个立方体是相同的" << endl;
else
cout << "两个立方体是不相同的" << endl;
return 0;
}
例6:点和圆的关系。
#include <iostream>
using namespace std;
//点类
class Point
{
public:
void setX(int x) //设置x
{
m_X = x;
}
int getX() //获取x
{
return m_X;
}
void setY(int y) //设置y
{
m_Y = y;
}
int getY() //获取y
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle
{
public:
void setCenter(Point center) //设置圆心
{
m_Center = center;
}
Point getCenter() //获取圆心
{
return m_Center;
}
void setR(int r) //设置半径
{
m_R = r;
}
int getR() //获取半径
{
return m_R;
}
private:
//一个类中可以包含另一个类
Point m_Center;
int m_R;
};
void isInCircle(Circle& c,Point& p)
{
//计算两点之间的距离
int isdistance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
//计算半径的平方
int dis = c.getR() * c.getR();
//进行比较
if (isdistance == dis)
cout << "点在圆上" << endl;
else if (isdistance < dis)
cout << "点在圆内" << endl;
else
cout << "点在圆外" << endl;
}
int main()
{
Circle c; //圆
Point m; //圆心
m.setX(10);
m.setY(0);
c.setCenter(m);
c.setR(10); //半径
Point a; //点
a.setX(10);
a.setY(10);
isInCircle(c, a);
return 0;
}
三:对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不要也会删除自己信息数据保证安全。
- c++的面向对象来源于生活,每个对象都会有出厂设置以及对象销毁前的清理数据的设置。
3.1 构造函数和析构函数
对象的初始化和清理工作是编译器强制要我们做的事情,因此我们不提供构造和析构,编译器会提供,但编译器提供的是空实现。
构造函数:主要作用于创造对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法: 类名(){};
1.构造函数没有返回值也不写viod。
2.函数名称与类名相同。
3.构造函数可以有参数,因此可以发生重载。
4.程序在调用对象的时候会自动调用构造,无需手动调用,而且只会调用一次。
析造函数语法:~ 类名(){};
1.析造函数没有返回值也不写viod。
2.函数名称与类名相同,在名称前加上符号~。
3.构造函数不可以有参数,因此不可以发生重载。
4.程序在对象销毁前会自动调用析造,无需手动调用,而且只会调用一次。

此时我只是创建了一个对象,我并没有去调用,而这是编译器自动调用的。
3.2 构造函数的分类及调用
两种分类方式:
按参数分类:有参构造和无参构造。
按类型分类:普通构造和拷贝构造。
三种调用方法: 括号法 显示法 隐式转换法
#include <iostream>
using namespace std;
class People
{
public:
//构造函数的分类
People() //无参(默认)构造函数
{
cout << "无参构造函数的调用" << endl;
}
People(int a) // 有参构造函数
{
cout << "有参构造函数的调用" << endl;
}
People(const People& p) //拷贝构造函数
{
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~People()
{
cout << "析构函数的调用" << endl;
}
};
// 调用
void test()
{
//1.括号法
//People p1; //默认构造函数调用
//People p2(10); //有参构造函数调用
//People p3(p2); //拷贝构造函数调用
//2.显示法
People p1;
People p2 = People(10); //有参构造
People p3 = People(p2); //拷贝构造
//3.隐式转换法
People p4 = 10; // 相当于People p4 = People(10); 有参构造
People p5 = p4; // 拷贝构造
}
int main()
{
test();
return 0;
}
People(10) 为匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象,然后再执行后面的操作。
注意事项:
1.括号法调用默认构造函数的时候不要加(),因为编译器可能会将其识别为函数声明,不会认为在创建对象。
2.使用显示法时,不要利用拷贝构造函数来初始化匿名对象,编译器会认为People(p3) == People p3。
3.3 拷贝构造函数调用时机
c++中拷贝构造函数调用时机通常有三中:
1.使用一个已经创建完毕的对象来初始化一个新的对象。
2.值传递的方式给函数参数传值。
3.以值方式返回局部对象。
#include <iostream>
using namespace std;
class People
{
public:
//构造函数的分类
People() //无参(默认)构造函数
{
cout << "无参构造函数的调用" << endl;
}
People(int a) // 有参构造函数
{
m_age = a;
cout << "有参构造函数的调用" << endl;
}
People(const People& p) //拷贝构造函数
{
m_age = p.m_age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~People()
{
cout << "析构函数的调用" << endl;
}
int m_age;
};
//1.使用一个已经创建完毕的对象来初始化一个新的对象。
void test1()
{
People p1(20);
People p2(p1);
cout << "p2的年龄:" << p2.m_age << endl;
}
//2.值传递的方式给函数参数传值。
void dowork(People p)
{
}
void test2()
{
People p;
dowork(p);
}
//3.以值方式返回局部对象。
People dowork1()
{
People p1;
cout << (int*)&p1 << endl;
return p1;
}
void test3()
{
People p = dowork1();
cout << (int*)&p << endl;
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
3.4 构造函数调用规则
默认情况下,c++编译器至少给每
个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用的规则:
- 如果用户定义有参构造函数,c++不在提供无参构造,但是会提供默认拷贝函数。
- 如果用户定义拷贝构造函数,c++不在提供其他构造函数。
#include <iostream>
using namespace std;
//1.情况一
class People
{
public:
//构造函数的分类
People() //无参(默认)构造函数
{
cout << "无参构造函数的调用" << endl;
}
People(int a) // 有参构造函数
{
m_age = a;
cout << "有参构造函数的调用" << endl;
}
//People(const People& p) //拷贝构造函数
//{
// m_age = p.m_age;
// cout << "拷贝构造函数的调用" << endl;
//}
//析构函数
~People()
{
cout << "析构函数的调用" << endl;
}
int m_age;
};
void test1()
{
People p;
p.m_age = 18;
People p1(p);
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test1();
return 0;
}
#include <iostream>
using namespace std;
//2.情况二
class People
{
public:
//构造函数的分类
//People() //无参(默认)构造函数
//{
// cout << "无参构造函数的调用" << endl;
//}
People(int a) // 有参构造函数
{
m_age = a;
cout << "有参构造函数的调用" << endl;
}
People(const People& p) //拷贝构造函数
{
m_age = p.m_age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~People()
{
cout << "析构函数的调用" << endl;
}
int m_age;
};
void test1()
{
People p;
}
int main()
{
test1();
return 0;
}

此时People没有默认构造函数可用,编译器也不会提供。
3.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝工作,编译器提供的。
深拷贝:在堆区重新申请空间,进行拷贝操作。
#include <iostream>
using namespace std;
class People
{
public:
//构造函数的分类
People() //无参(默认)构造函数
{
cout << "无参构造函数的调用" << endl;
}
People(int a,int high) // 有参构造函数
{
m_age = a;
m_high=new int(high); //堆区手动开辟的空间要手动释放
cout << "有参构造函数的调用" << endl;
}
//析构函数
~People()
{
//析构代码,将堆区开辟数据做释放操作
if (m_high != NULL)
{
delete m_high;
m_high = NULL;
}
cout << "析构函数的调用" << endl;
}
int m_age;
int* m_high;
};
void test()
{
People p1(18,160);
cout << "p1的年龄:" << p1.m_age << " p1的身高:" << *p1.m_high <<endl;
People p2(p1);
cout << "p2的年龄:" << p2.m_age << " p2的身高:" << *p2.m_high << endl;
}
int main()
{
test();
return 0;
}

此时会报错,为什么呢?这就是浅拷贝的不足了-------堆区的内存重复释放

浅拷贝的问题要利用深拷贝来解决,为 p2.m_high 重新分配内存
#include <iostream>
using namespace std;
class People
{
public:
//构造函数的分类
People() //无参(默认)构造函数
{
cout << "无参构造函数的调用" << endl;
}
People(int a,int high) // 有参构造函数
{
m_age = a;
m_high=new int(high); //堆区手动开辟的空间要手动释放
cout << "有参构造函数的调用" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
People(const People& p)
{
cout << "拷贝构造函数的调用" << endl;
m_age = p.m_age;
//m_high = p.m_high; // 编译器默认实现的
//深拷贝操作
m_high = new int(*p.m_high);
}
//析构函数
~People()
{
//析构代码,将堆区开辟数据做释放操作
if (m_high != NULL)
{
delete m_high;
m_high = NULL;
}
cout << "析构函数的调用" << endl;
}
int m_age;
int* m_high;
};
void test()
{
People p1(18,160);
cout << "p1的年龄:" << p1.m_age << " p1的身高:" << *p1.m_high <<endl;
People p2(p1);
cout << "p2的年龄:" << p2.m_age << " p2的身高:" << *p2.m_high << endl;
}
int main()
{
test();
return 0;
}
3.6 初始化列表
作用:c++提供了初始化列表语法,用来初始化属性。
语法:构造函数():属性1(值1),属性2(值2)···{};
#include <iostream>
using namespace std;
class People
{
public:
//传统赋值
//People(int a, int b, int c)
//{
// m_a = a;
// m_b = b;
// m_c = c;
//}
//初始化列表初始化属性
People(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
{
}
int m_a;
int m_b;
int m_c;
};
void test()
{
People p(10,20,30);
cout << "m_a = " << p.m_a << " m_b = " << p.m_b << " m_c = " << p.m_c << endl;
}
int main()
{
test();
return 0;
}
3.7 类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该对象为 对象成员。
那么那个类先进行构造,析构呢?
#include <iostream>
using namespace std;
//手机类
class Phone
{
public:
Phone(string Pname)
{
cout << "Phone的构造函数" << endl;
m_Pname = Pname;
}
~Phone()
{
cout << "Phone的析构函数" << endl;
}
string m_Pname;
};
//人类
class People
{
public:
People(string name, string phone) :m_name(name), m_phone(phone)
{
cout << "People的构造函数" << endl;
};
~People()
{
cout << "People的析构函数" << endl;
}
string m_name;
Phone m_phone;
};
void test()
{
People p("张三", "苹果");
cout << p.m_name << "的手机是 "<< p.m_phone.m_Pname << endl;
}
int main()
{
test();
return 0;
}

由这个结果可以知道 :
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,而析构则相反。
3.8 静态成员
静态成员就是在成员函数和成员变量前加上关键词static。
静态成员分为:
1.静态成员变量:
- 所以对象共享同一份数据。
- 在编译阶段分配内存。
- 类内声明,类外初始化。
2.静态成员函数:
- 所以对象共享同一个函数。
- 静态成员函数只能访问静态成员变量。
3.8.1 静态成员变量
当没有在类外初始化静态成员变量时,我们无法使用这个变量。


访问方式:
1.通过对象来访问: 和上图中一样,创建一个具体的对象,用p.m_A来访问。
2.通过类名来访问:直接使用 People::m_A 进行访问。
3.8.2 静态成员函数
#include <iostream>
using namespace std;
//静态成员变量
class People
{
public:
static void func()
{
m_A = 10; // 静态成员变量
//m_B = 10; //报错,因为静态成员函数不可以访问非静态成员变量 无法区分到底是哪个对象的成员变量
cout << "func的调用" << endl;
}
static int m_A; //类内声明
int m_B;
};
int People::m_A = 100;
void test()
{
// 1.通过对象访问
People p;
p.func();
// 2.通过类名访问
People::func();
}
int main()
{
test();
return 0;
}
不管是静态成员变量,还是静态成员函数都有访问权限。
3.9 c++对象模型和this指针
3.9.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储。只有非静态成员变量属于类的对象上。
#include <iostream>
using namespace std;
// 空对象
class People
{
};
class Person
{
public:
int m_A; // 非静态成员变量 属于类的对象上
static int m_B; // 静态成员变量 不属于类的对象上
void func(){} // 非静态成员函数 不属于类的对象上
static void func1() {} // 静态成员函数 不属于类的对象上
};
int Person::m_B; //静态成员变量要在类外初始化
void test1()
{
People p;
// 空对象占用内存空间为 1
// C++编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置
// 每个空对象有独一无二的内存地址
cout << "p sizeof: " << sizeof(p) << endl;
}
void test2()
{
Person p;
cout << "p sizeof: " << sizeof(p) << endl;
}
int main()
{
//test1();
test2();
return 0;
}
3.9.2 this指针概念
由上一小节可以知道成员变量和成员函数分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用同一块代码
那么,这块代码怎么区分是哪一个对象调用自己呢?this指针可以解决这个问题
this指针:是隐含每一个非静态成员函数内的一种指针,指向被调用的成员函数所属的对象。
this指针不需要定义,直接使用即可。
this指针的用途:
1.当形参和成员变量同名时,可用this指针来区分
2.在类的非静态成员函数中返回对象本身,可以使用 return *this。
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//age = age; //此时答案是错的,因为区分不了形参和成员变量
this->age = age;
}
//如果这里不是Person& 而是 Person ,那么此时每返回一个值都会重新创建一个新的变量
Person& addage(Person& p)
{
this->age += p.age;
//this指向p2得指针,而*this指向p2对象本身
return *this;
}
int age;
};
void test1()
{
Person p(10);
cout << "p 的年龄:" << p.age << endl;
}
void test2()
{
Person p1(10);
Person p2(10);
p2.addage(p1).addage(p1).addage(p1);
cout << "p2 的年龄:" << p2.age << endl;
}
int main()
{
//test1();
test2();
return 0;
}
3.9.3 空指针访问成员函数
C++中空指针可以调用成员函数,但要注意有没有this指针
如果有this指针,要加以判断保证代码的健壮性
#include <iostream>
using namespace std;
class Person
{
public:
void showname()
{
cout << "this is Person" << endl;
}
void showage()
{
// 通过判断是否为空来防止错误
if (this == NULL)
{
return;
}
// 报错的原因是因为传入的指针为空
cout << "年龄:" << m_age << endl; //此时 m_age相当于this->m_age
}
int m_age;
};
void test1()
{
Person* p=NULL;
p->showname();
p->showage();
}
int main()
{
test1();
return 0;
}
3.9.4 const 修饰成员函数
常函数:
- 成员函数后加const ,我们称这个函数为常函数。
- 常函数内不可以修改成员属性。
- 成员函数声明时加关键词mutable后,在常函数中依然可以修改。
常对象:
- 声明对象前加const,称该对象为常对象。
- 常对象只能调用常函数。
#include <iostream>
using namespace std;
class Person
{
public:
//常函数
//this指针的本质是 指针常量 指针的指向是不可以修改的
//在成员函数后面加const 修饰的是this指针,让指针指向的值也不可以修改
void showPerson() const //相当于const Person* const this
{
m_B = 100;
//m_A = 100; //相当于this->m_A;
}
void func(){}
int m_A;
mutable int m_B; // 特殊变量,即使在常函数中也可以修改,加上关键字mutable
};
void test1()
{
Person p;
p.showPerson();
}
//常对象
void test2()
{
const Person p;
//p.m_A = 10; //报错,此时不可以修改
p.m_B = 10; //特殊变量 此时也可以修改
//常对象只能调用常函数
p.showPerson();
//p.func(); //报错 因为普通成员函数可以修改属性 而常对象不允许修改属性
}
int main()
{
//test1();
test2();
return 0;
}
四:友元
目的:让一个函数或类访问另一个类中私有成员。
友元的关键字为 friend
友元的实现方法:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.1 全局函数做友元
#include <iostream>
using namespace std;
class Building
{
//告诉编译器 Goodfriend全局函数是Building类的好朋友,可以访问类中的私有内容
friend void Goodfriend(Building* build);
public:
Building()
{
this->m_bedroom = "卧室";
this->m_sittingroom = "客厅";
}
public:
string m_sittingroom;
private:
string m_bedroom;
};
//全局函数
void Goodfriend(Building* build)
{
cout << "好朋友的全局函数正在访问:" << build->m_sittingroom << endl;
cout << "好朋友的全局函数正在访问:" << build->m_bedroom << endl;
}
void test()
{
Building b;
Goodfriend( &b);
}
int main()
{
test();
return 0;
}
4.2 类做友元
#include <iostream>
using namespace std;
//类做友元
class Building; //声明
class Goodfriend
{
public:
Goodfriend();
Building* building;
void visit(); //参观函数 访问Building中的属性
};
class Building
{
//告诉编译器 Goodfriend类是Building类的好友
friend class Goodfriend;
public:
Building();
public:
string m_sittingroom;
private:
string m_bedroom;
};
//类外写成员函数
Building::Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
Goodfriend::Goodfriend()
{
// 创建建筑物对象
building = new Building;
}
void Goodfriend::visit()
{
cout << "好友类正在访问:" << building->m_sittingroom << endl;
cout << "好友类正在访问:" << building->m_bedroom << endl;
}
void test()
{
Goodfriend g;
g.visit();
}
int main()
{
test();
return 0;
}
4.3 成员函数做友元
#include <iostream>
using namespace std;
class Building;
class Goodfriend
{
public:
Goodfriend();
Building* building;
void visit1(); // 让visit1函数可以访问Building中的私有成员
void visit2(); // 让visit2函数不可以访问Building中的私有成员
};
class Building
{
// 告诉编译器Goodfriend类中的visit1可以访问Building类中的私有成员
friend void Goodfriend::visit1();
public:
Building();
string m_sittingroom;
private:
string m_bedroom;
};
//类外实现成员函数
Building::Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
Goodfriend::Goodfriend()
{
building = new Building;
}
void Goodfriend::visit1()
{
cout << "visit1 函数正在访问:" << building->m_sittingroom << endl;
cout << "visit1 函数正在访问:" << building->m_bedroom << endl;
}
void Goodfriend::visit2()
{
cout << "visit2 函数正在访问:" << building->m_sittingroom << endl;
//cout << "visit2 函数正在访问:" << building->m_bedroom << endl; 报错
}
void test()
{
Goodfriend g;
g.visit1();
g.visit2();
}
int main()
{
test();
return 0;
}
五:运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适用不同的数据类型。
5.1 加号运算符重载
作用:实现两个自定义类型的相加运算。
#include <iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
// 1.成员函数实现 + 号运算符重载
/*Person operator+(Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}*/
};
//2.全局函数实现 +号运算符重载
Person operator+(Person& p1, Person& p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
// 不一定是同类型的相加,函数重载
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3= p1 + p2;
cout << "p3的m_A: " << p3.m_A << endl;
cout << "p3的m_B: " << p3.m_B << endl;
Person p4 = p1 + 100;
cout << "p4的m_A: " << p4.m_A << endl;
cout << "p4的m_B: " << p4.m_B << endl;
}
int main()
{
test();
return 0;
}
5.2 左移运算符重载
作用:可以输出自定义的数据类型。
#include <iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
// 1.成员函数实现 <<运算符重载
//void operator<<(Person& p) //简化为p << cout ,不符合我们平时写的 cout << p,所以不用成员函数实现
//{
//}
};
//2.全局函数实现 <<运算符重载
//返回值为ostream& 的原因是 这样可以发生链式反应,后面可以链接其他的语句
ostream& operator<<(ostream & cout, Person & p)
{
cout << "m_A= " << p.m_A << " m_B= " << p.m_B << endl;
return cout;
}
void test()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
cout << p1 << endl;
}
int main()
{
test();
return 0;
}
5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据。
#include <iostream>
using namespace std;
//自定义整型
class Integer
{
friend ostream& operator<<(ostream& cout,const Integer& i); //友元
public:
Integer()
{
m_Num = 0;
}
//重载前置++运算符 返回引用是为了对一个数据进行递增操作
Integer& operator++()
{
++m_Num;
return *this;
}
//重载后置++运算符
Integer operator++(int) //int表示占位参数,用于区分前置和后置
{
//先存结果
Integer temp = *this;
//再递增
m_Num++;
//后返回值
return temp;
}
private:
int m_Num;
};
//重载<<运算符
// myint++ 返回临时右值,非 const 左值引用不能绑定临时对象,不加 const 会编译报错
ostream& operator<<(ostream& cout,const Integer& i)
{
cout << "m_Num: " << i.m_Num;
return cout;
}
void test()
{
Integer myint;
cout << ++myint << endl;
cout << myint << endl;
}
void test1()
{
Integer myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
//test();
test1();
return 0;
}
5.4 赋值运算符重载
赋值运算符 operator=对属性进行值拷贝。
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题。
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{ // 重复释放 出错
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
// 重载 赋值运算符
Person& operator=(Person& p)
{
//m_Age = p.m_Age; //编译器提供的浅拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
int* m_Age;
};
void test()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值
cout << "p1的年龄: " << *p1.m_Age << endl;
cout << "p2的年龄: " << *p2.m_Age << endl;
cout << "p3的年龄: " << *p3.m_Age << endl;
}
int main()
{
test();
return 0;
}
5.5 关系运算符重载
#include <iostream>
using namespace std;
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
bool operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
string m_Name;
int m_Age;
};
void test()
{
Person p1("张三", 18);
Person p2("李四", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2是不相等的" << endl;
}
}
int main()
{
test();
return 0;
}
5.6 函数调用运算符重载
由于重载后使用的方式和函数调用很相似,因此称为仿函数。
#include <iostream>
using namespace std;
class Myprint
{
public:
void operator()(string test)
{
cout << test << endl;
}
};
//仿函数很灵活没有固定的写法
class Add
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test()
{
Myprint print;
print("hello world!");
}
void test2()
{
Add a;
cout << a(1, 3) << endl;
}
int main()
{
test();
test2();
return 0;
}
六:继承
继承是面向对象三大特性之一。
6.1 基本语法
#include <iostream>
using namespace std;
// 普通实现页面
class Java
{
public:
void header()
{
cout << "首页,公共课,登入,注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页,公共课,登入,注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
class Cpp
{
public:
void header()
{
cout << "首页,公共课,登入,注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test()
{
cout << "Java下载视频页面如下:" << endl;
Java j;
j.header();
j.footer();
j.left();
j.content();
cout << "-------------------------" << endl;
}
void test1()
{
cout << "Python下载视频页面如下:" << endl;
Python p;
p.header();
p.footer();
p.left();
p.content();
cout << "-------------------------" << endl;
}
void test2()
{
cout << "Cpp下载视频页面如下:" << endl;
Cpp c;
c.header();
c.footer();
c.left();
c.content();
cout << "-------------------------" << endl;
}
int main()
{
test();
test1();
test2();
return 0;
}
如果不用继承,虽然可以实现,但代码很长很乱,有大量重复代码,浪费空间。
#include <iostream>
using namespace std;
// 通过继承来实现页面
// 公共部分
class Base
{
public:
void header()
{
cout << "首页,公共课,登入,注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
};
//Java
class Java :public Base
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python
class Python :public Base
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//Cpp
class Cpp :public Base
{
public:
void content()
{
cout << "Cpp学科视频" << endl;
}
};
void test()
{
cout << "Java下载视频页面如下:" << endl;
Java j;
j.header();
j.footer();
j.left();
j.content();
cout << "-------------------------" << endl;
}
void test1()
{
cout << "Python下载视频页面如下:" << endl;
Python p;
p.header();
p.footer();
p.left();
p.content();
cout << "-------------------------" << endl;
}
void test2()
{
cout << "Cpp下载视频页面如下:" << endl;
Cpp c;
c.header();
c.footer();
c.left();
c.content();
cout << "-------------------------" << endl;
}
int main()
{
test();
test1();
test2();
return 0;
}
继承的好处:减少重复代码
语法:class 子类(派生类) : 继承方式 父类(基类)
从基类继承过来的表现其共性,而新增的成员体现了其个性。
6.2 继承方式
#include <iostream>
using namespace std;
// 1.公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base1
{
public:
void func()
{
m_A = 10; // 父类中的公共权限成员在子类中依然是公共权限
m_B = 10;// 父类中的保护权限成员在子类中依然是保护权限
//m_C = 10; // 父类中的私有权限成员 子类访问不到
}
};
void test1()
{
Son1 s;
s.m_A = 10;
//s.m_B = 100; //类外访问不到
}
// 2.保护继承
class Son2 :protected Base1
{
public:
void func()
{
m_A = 100; //父类中公共成员在子类中为保护成员
m_B = 100; //父类中保护成员在子类中为保护成员
//m_C = 100; //父类中私有成员 子类访问不到
}
};
void test2()
{
Son2 s;
//s.m_A = 10; //变为保护权限成员 类外访问不了
}
// 3.私有继承
class Son3 :private Base1
{
public:
void func()
{
m_A = 10; //父类中公共成员在子类中为私有成员
m_B = 10; //父类中保护成员在子类中为私有成员
//m_C = 10; //父类中私有成员 子类访问不到
}
};
void test3()
{
Son3 s;
//s.m_A = 100; //在Son3 中变为私有成员
}
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A = 10; //到Son3中 m_A变为私有 此时访问不到
//m_B = 10; //到Son3中 m_B变为私有 此时访问不到
}
};
int main()
{
return 0;
}
6.3 继承中的对象模型
从父类继承来的成员,那些属于子类对象中呢?
#include <iostream>
using namespace std;
// 公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //继承时被编译器隐藏了,因此访问不到,但会被继承下去
};
class Son :public Base1
{
public:
int m_D;
};
void test()
{
Son s;
//父类中所有非静态成员属性都会被子类继承下去
cout << "sizeof(s)= " << sizeof(s) << endl;
}
int main()
{
test();
return 0;
}
利用开发人员命令提示工具查看对象模型:
1. 跳转盘符 D:
2. 跳转文件路径 cd 具体路径
3.查看命名 cl /d1 reportSingleClassLayout类名 文件名
6.4 继承中构造和析构顺序

由图可知,先是父类的构造函数,然后再是子类的,析构函数则相反。
6.5 继承同名成员处理方式
访问子类同名成员时,直接访问就可以
访问父类同名成员时,需要加上作用域
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base 中func的调用" << endl;
}
void func(int a)
{
cout << "Base(int a) 中func的调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son 中func的调用" << endl;
}
int m_A;
};
// 同名成员属性
void test()
{
Son s;
cout << "Son m_A= " << s.m_A << endl; //200
//如果通过子类对象访问父类中同名成员,需要加作用域
cout << "Base m_A= " << s.Base::m_A << endl;
}
// 同名成员函数
void test1()
{
Son s;
s.func(); // 直接调用是子类中的同名函数
//s.func(100); //如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员
s.Base::func(); //加作用域
s.Base::func(100);
}
int main()
{
//test();
test1();
return 0;
}
6.6 继承同名静态成员处理
静态成员和非静态成员出现同名,处理方式一致。
#include <iostream>
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base func的调用" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son func的调用" << endl;
}
};
int Son::m_A = 200;
// 同名静态成员属性
void test1()
{
Son s;
// 1.通过对象访问
cout << "通过对象访问" << endl;
cout << "Son m_A= " << s.m_A << endl;
cout << "Base m_A=" << s.Base::m_A << endl;
// 2.通过类名访问
cout << "通过类名访问" << endl;
cout << "Son m_A= " << Son::m_A << endl;
// 第一个::代表通过类名方式访问,第二个::代表访问父类作用域下
cout << "Base m_A= " << Son::Base::m_A << endl;
}
// 同名静态成员函数
void test2()
{
Son s;
// 1. 通过对象访问
cout << "通过对象访问" << endl;
s.func();
s.Base::func();
// 2. 通过类名访问
cout << "通过类名访问" << endl;
Son::func();
Son::Base::func();
}
int main()
{
//test1();
test2();
return 0;
}
6.7 多继承语法
C++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2....
在开发的时候不建议使用多继承
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
class Son :public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test()
{
Son s;
cout << "sizeof(s) = " << sizeof(s) << endl;
// 当父类中出现同名成员,此时会有二义性,需要加作用域区分
cout << "Base1 m_A = " << s.Base1::m_A << endl;
cout << "Base2 m_A = " << s.Base2::m_A << endl;
}
int main()
{
test();
return 0;
}
6.8 菱形继承
概念:两个派生类继承同一个基类,又有某个基类同时继承这两个派生类。
# include <iostream>
using namespace std;
// 动物类
class Animal
{
public:
int m_Age;
};
// 利用虚继承可以解决菱形继承的数据浪费
// 在继承前加上关键字virtual 变成虚继承
// Animal类 称为 虚基类
// 羊类
class Sheep:virtual public Animal{};
// 驼类
class Tuo:virtual public Animal{};
// 羊驼类
class SheepTuo:public Sheep,public Tuo{};
void test()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
// 当菱形继承,两个父亲用有相同数据,需要加作用域区分
cout << "Sheep m_Age = " << st.Sheep::m_Age << endl;
cout << "Tuo m_Age = " << st.Tuo::m_Age << endl;
cout << "st m_Age = " << st.m_Age << endl;
}
int main()
{
test();
return 0;
}

此时,继承的不是两个数据而是两个指针vbptr,这两个指针会通过偏移量找到唯一的数据。
vbptr : virtual base pointer 这个指针会指向对应的vbtable
七:多态
多态是面向对象三大特性之一。
7.1 多态的基本概念
多态分为两类:
- 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 动态多态:派生类 和 虚函数 实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
# include <iostream>
using namespace std;
// 动物类
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
// 猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
// 狗类
class Dog:public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &a) //Animal &a = c 父子之间允许转换
{
a.speak();
}
void test()
{
Cat c;
doSpeak(c);
Dog d;
doSpeak(d);
}
int main()
{
test();
return 0;
}
多态的内部原理:
- Animal内部存在一个虚函数指针vfptr(virtual function pointer)指向虚函数表vftable,表内记录Animal 类虚函数的地址。
- Cat类 继承且没有重写Animal类 的虚函数时,Cat类 内部存在一个虚函数指针指向虚函数表vftable,表内记录Animal类 虚函数的地址。
- 当Cat类 重写Animal类 的虚函数,Cat类 的虚函数表内部会替换成Cat类 的虚函数地址。
动态多态的满足条件:
1. 有继承关系
2. 子类重写父类的虚函数
动态多态的使用:父类的指针 或 引用 指向子类对象
多态的好处:
- 代码组织结构清晰。
- 可读性强。
- 利于前期和后期的扩展以及维护。
例:设计实现两个操作数进行运算的计算器类。
#include <iostream>
using namespace std;
// 普通写法
class Calculator
{
public:
int getresult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
// 如果想扩展新的功能,需要修改源码
// 在真正的开发中 提倡 开闭原则
// 开闭原则: 对扩展进行开放,对修改进行关闭
}
int m_Num1;
int m_Num2;
};
void test()
{
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getresult("+") << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getresult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getresult("*") << endl;
}
int main()
{
test();
return 0;
}
#include <iostream>
using namespace std;
// 利用多态实现
// 实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getresult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
// 加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
int getresult()
{
return m_Num1 + m_Num2;
}
};
// 减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
int getresult()
{
return m_Num1 - m_Num2;
}
};
// 乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
int getresult()
{
return m_Num1 * m_Num2;
}
};
void test()
{
// 加法
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getresult() << endl;
delete abc;
// 加法
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getresult() << endl;
delete abc;
// 乘法
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getresult() << endl;
delete abc;
}
int main()
{
test();
return 0;
}
7.2 纯虚函数和抽象类
多态中,父类的虚函数一般是没有意义的,主要是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
语法:virtual 返回值类型 函数名(参数类型)= 0 ;
当类中有纯虚函数时,这个类被称为抽象类。
抽象类的特点:
- 无法实例化对象。
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
#include <iostream>
using namespace std;
class Base
{
public:
// 纯虚函数
virtual void func() = 0;
};
class Son :public Base
{
public:
void func()
{
cout << "func 的调用" << endl;
}
};
void test()
{
//Base b; //报错,不允许使用抽象类实例化
//Son s; //子类必须重新父类的纯虚函数,函数内什么都不写都可以
Base* base = new Son;
base->func();
delete base;
}
int main()
{
test();
return 0;
}
7.3 虚析构和纯虚析构
如果子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构函数。此时需要将父类中的析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构的共性:
- 可以解决父类指针释放子类对象。
- 都需要有具体的函数实现。
虚析构和纯虚析构的区别:如果是纯虚析构,那么该类为抽象类,无法实例化对象。
虚析构语法:~virtual 类名(){}
纯虚析构语法:~virtual 类名()= 0;
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak() = 0;
Animal()
{
cout << "Animal构造函数调用" << endl;
}
~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
string *m_Name;
};
void test1()
{
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test1();
return 0;
}

此时没有Cat的析构函数,说明堆区的数据没有释放。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak() = 0;
Animal()
{
cout << "Animal构造函数调用" << endl;
}
// 利用虚析构可以解决释放子类对象时不干净的问题
//virtual ~Animal()
//{
// cout << "Animal析构函数调用" << endl;
//}
//纯虚析构
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
string *m_Name;
};
void test1()
{
Animal* animal = new Cat("Tom");
animal->speak();
// 父类的指针在析构的时候不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄露
delete animal;
}
int main()
{
test1();
return 0;
}
虚析构和纯虚析构 需要声明也需要实现。
更多推荐
所有评论(0)