C++语言程序设计——知识点复盘(第四章 类与对象)

目录

面向对象程序设计的基本特点

        1、抽象

        2、封装

        3、继承

        4、多态

类和对象

类的成员函数 

1、成员函数的实现

2、目的对象。

3、带默认形参值的成员函数

4、内联成员函数

构造函数

析构函数

程序实例:圆(Circle)类

类的组合

程序实例:线段(Line)类

前向引用声明

结构体

联合体


面向对象程序设计的基本特点

1、抽象

包括数据抽象行为抽象,分别得到数据成员和函数成员。

2、封装

就是将抽象得到的数据和函数相结合,形成。同时通过对成员的访问权限合理控制,增强数据的安全性和建合伙程序编写工作。

3、继承

在原有类特性的基础上,进行更具体、更详尽的说明。只有继承,才可以在一般概念的基础上,派生出特殊概念。

4、多态

指一段程序能够处理多种类型对象的能力。包括强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。虚函数是多态性的精华。


类和对象

在面向对象程序设计中,程序模块是由构成的。而用类定义的变量称为对象(或实例),这个定义的过程也称为类的实例化

注:1、普通变量也是对象。

        2、对象所占据的内存空间只用于存放数据成员,函数成员不在每一个对象中存储副本,每个函数的代码在内存中只占据一份空间。

class 类名称{
    public:
        外部接口
    protected:
        保护型成员
    private:
        私有成员
};

我总结的访问控制的权限如下,由上到下权限逐增:

私有:类本身(对象不算)+友元类

保护:...+派生类

公有:任何

注:1、如果私有成员紧接着类名,则关键字private可以省略。

        2、修饰访问属性的关键字可以多次出现,但是一个成员只能具有一种访问属性。

        3、设计一个类是为了使用它,因此必须有外部接口。


类的成员函数 

1、成员函数的实现

返回值类型 类名::函数成员名(参数表){
    函数体
}

2、目的对象。

即被调用的函数。在成员函数中引用其它对象的属性和调用其它对象的方法时,都需要使用“.”操作符。注意,在类的成员函数中,即可以访问目的对象的私有成员,又可以访问当前类其它对象的私有成员。

3、带默认形参值的成员函数

类成员函数的默认值,只能写在类定义中,而不能写在类定义以外的函数实现中。

class Clock{
    public:
        void setTime(int h=0,int m=0,int s=0);
        ...
};

4、内联成员函数

显示声明:函数体实现时,在函数返回值类型前面加上inline。

class Clock{
    public:
        void showTime();
    ...
};
inline void Clock::showTime(){
    cout<<h<<":"<<m<<":"<<s<<endl;
}    

隐式声明:将函数直接在类内实现。

class Clock{
    public:
        void showTime(){
            cout<<h<<":"<<m<<":"<<s<<endl;
        }
    ...
};   

显式和隐式声明的效果完全相同。


构造函数

在对象被创建是利用特定的值构造对象,将对象初始化为一个特定的状态。无参数的构造函数称为默认构造函数

构造函数的性质:

1、构造函数的函数名与类名相同,且没有返回值;

2、通常被声明为公有函数;

3、在创建对象的时候被自动调用;

4、如果类中没有写构造函数,编译器会自动生成一个隐含的默认构造函数(除非用Clock()=default),该构造函数的参数列表和函数体皆为空。

5、作为类的成员函数,构造函数可以直接访问类的所有数据成员。

复制构造函数是一种特殊的构造函数,其形参是本类的对象的引用。其作用是使用一个已经存在的对象,去初始化同类的一个新对象。如果程序员没有定义类的复制构造函数,系统就会在必要时自动生成一个隐含的复制构造函数。

class 类名 {
    public:
	    类名(形参表);//构造函数
	    类名(类名&对象名);//复制构造函数
	...
};
类名::类名(类名&对象名){//复制构造函数的实现
    函数体
}

下面是一个复制构造函数的例子:点(Point)类的定义

class Point{
public:
	Point(int x = 0, int y = 0) {//构造函数
		x = x;
		y = y;
	}
	Point(Point &p);//复制构造函数
	int getX() { return x; }
	int getY() { return y; }
private:
	int x, y;//横、纵坐标
};
Point::Point(Point &p) {
	x = p.x;
	y = p.y;
}

普通构造函数是在对象创建时被调用,而复制构造函数在以下3种情况下都会被调用。

(1)当用类的一个对象去初始化该类的另一个对象时。

Point a(1,2);
Point b(a);//用对象a初始化对象b,复制构造函数被调用
Point c=a;//用对象a初始化对象c,复制构造函数被调用

上面两种初始化方式只是写法上不同,效果完全相同。 

(2)函数的形参是类的对象,进行形参与实参结合时。

void fun(Point p){//形参是类的对象
    cout<<p.getX()<<endl;
}
int main(){
    Point a(1,2);
    fun(a);//形实结合时调用复制构造函数
    return 0;
}

注:只有把对象用值传递是才会调用复制构造函数,引用传递不会调用。因此传递比较大的对象时,引用传递比值传递更高效。

(3)函数的返回值是类的对象,函数执行完成返回调用者时。

Point fun(){
    Point a(1,2);
    return a;//函数的返回值是类对象,返回函数值时调用复制构造函数
}

注:需要注意的是,当类的数据成员中有指针类型时,默认的复制构造函数实现的只能是浅复制。浅复制会带来数据安全方面的隐患,要实现正确的复制,也就是深复制,必须编写复制构造函数。 


析构函数

作用和构造函数相反,用来完成对象被删除前的一些清理工作,在对象的生存期即将结束的时刻被自动调用。它的调用完成后,对象也就消失了,相应的内存空间也被释放。

析构函数的性质:

(1)通常也是类的一个共有函数成员,它的名称是由类名前面加“~”构成,没有返回值。

(2)不接受任何参数。

(3)可以是虚函数。

(4)函数体为空未必不做任何事。


程序实例:圆(Circle)类

const float PI = 3.1415926;

class Circle {
public:
	Circle(float r);//构造函数
	float perimeter();//计算周长
	float area();//计算面积
private:
	float r;//圆的半径
};
Circle::Circle(float r) {//构造函数的实现
	r = r;
}
float Circle::perimeter() {
	return 2 * PI*r;
}
float Circle::area() {
	return PI*r*r;
}

类的组合

是指一个类内嵌其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。因为部件对象是复杂对象的一部分,因此在创建对象时,既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化。

组合类构造函数定义的一般形式为:

类名::类名(形参表):内嵌对象(形参表),内嵌对象(形参表),...
{类的初始化}

其中,“:”后面的部分称作初始化列表,其作用是对内嵌对象进行初始化。上面Circle类的构造函数也可以这样写:

Circle::Circle(float r):r(r){};

在创建一个组合类的对象是,不仅调用它自身的构造函数,而且还将调用其内嵌对象的构造函数。顺序如下:

(1)调用内嵌对象的构造函数,调用顺序按照内嵌对象在组合类的定义中出现的次序(注意不是初始化列表中出现的顺序)。

(2)执行本类构造函数的函数体。

注:如果有些内嵌对象没有出现在构造函数的初始化列表中,那么在第(1)步中,该内嵌对象的默认构造函数将被执行。但是有些数据成员的初始化,必须在构造函数的初始化列表中进行。包括以下两类:<1>只有带参构造函数的内嵌对象;<2>引用类型的数据成员

析构函数的调用执行顺序与构造函数正好相反。

如果要为组合类编写复制构造函数,则需要为内嵌成员对象的复制构造函数传递参数。例如,假设A类中包含B类的对象b作为成员,A类的复制构造函数形式如下:

A::A(A &a):b(a.b){...}

程序实例:线段(Line)类

#include<iostream>
#include<cmath>
using namespace std;

class Point {//点类
public:
	Point(int x = 0, int y = 0) {
		x = x;
		y = y;
		cout << "finish the constructor of Point" << endl;
	}
	Point(Point &p);
	int getX() { return x; };
	int getY() { return y; };
private:
	int x, y;
};
Point::Point(Point &p) {
	x = p.x;
	y = p.y;
	cout << "finish the copy constructor of Point" << endl;
}

class Line {//线段类
public:
	Line(Point p1, Point p2);
	Line(Line &l);
	double getLen() { return len; }
private:
	Point p1, p2;
	double len;//长度
};
Line::Line(Point p1, Point p2) :p1(p1), p2(p2) {//组合类的构造函数【5】【6】
	double x = (double)(p1.getX() - p2.getX());
	double y = (double)(p1.getY() - p2.getY());
	len = sqrt(x*x + y*y);
	cout << "finish the constructor of Line" << endl;//【7】
}
Line::Line(Line &l) : p1(l.p1), p2(l.p2) {//组合类的复制构造函数【8】【9】
	len = l.len;
	cout << "finish the copy constructor of Line" << endl;//【10】
}

int main() {
	Point p1(1, 1), p2(4, 5);//【1】【2】
	Line l(p1, p2);//【3】【4】
	Line l2(l);
	return 0;
}

运行结果为:

finish the constructor of Point
finish the constructor of Point
finish the copy constructor of Point
finish the copy constructor of Point
finish the copy constructor of Point
finish the copy constructor of Point
finish the constructor of Line
finish the copy constructor of Point
finish the copy constructor of Point
finish the copy constructor of Line

从上往下每条执行结果的位序已在代码中标出,其中【1】【2】是普通的调用Point类的构造函数数;【3】【4】是把对象作为形参,形实结合;【5】【6】是用对象初始化对象;【7】是简单地输出语句;【8】【9】是用对象初始化对象(引用传递不调用复制构造函数);【10】是简单地输出语句。这个例子综合性较强,值得反复练习。 


前向引用声明

是在引用未定义的类之前,将该类的名字告诉编译器,是编译器知道那是一个类名。 例如:

class A {
public:
	void f(B b);//编译错误!
};
class B {
public:
	void g(A a);
};

这样写将引起编译错误,因为‘B’为未知符号,改为下面这样就正确了:

class B;//前向引用声明
class A {
public:
	void f(B b);
};
class B {
public:
	void g(A a);
};

如果“B b”不是出现在形参列表里,而是作为声明语句单独出现,那么仍然错误,因为不能涉及类的细节(比如关于内存的占用情况)。那么为什么可以放在形参列表里呢?因为只有调用函数时才会为形参分配内存,那已经发生在B的完整定义之后,所以是可以的。

class A {
public:
	B b;//错误!B类的定义不完善,不知道b占多少个字节
};
class B {
public:
	void g(A a);
};

还有下面这个例子也值得思考:

class A {
public:
	void method() {
		x.fun();//错误!不清楚B类的细节
	}
private:
	B &x;//正确!只是用B起了个别名
};
class B {
public:
	void fun();
private:
	A &y;
};

总之,当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。


结构体

结构体是一种特殊的类。和类的唯一区别在于,类成员的默认访问权限是private,而结构体成员的默认访问权限是public。其它部分和C语言中的结构体相同,这里不展开介绍。


联合体

联合体也是一种特殊的类。它的特点有二:1、成员的默认访问权限是public;2、联合体的全部数据成员共享同一组内存单元。

union Mark{
    char grade;//等级制的成绩
    bool pass;//是否通过
    int percent;//百分制的成绩
};

正是由于联合体的成员共用相同的内存单元,因此同时至多只有一个是有意义的,分配内存时安最多的字节数分配(比如上面的例子就是按照int型分配4个字节)。联合体可以不声明名称,称为无名联合体,通常用作类或结构体的内嵌成员。另外,联合体有下面一些限制:

1、不能继承,也不支持多态。

2、联合体的各个对象成员,不能有自定义的构造函数、自定义的析构函数和重载复制赋值运算符,不仅联合体的对象成员不能有,对象成员的对象成员也不能有,以此类推。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值