08_C++类和对象

一、类和对象

类是对象的抽象,而对象是类的具体实例。

类是一种抽象的数据类型,并不是一个实体,也不占存储空间;对象是实体,占存储空间。

1. 类的声明和定义对象

class Student
{    
    public:
    protected:
    private:   
        int num;
        char name[20];
        char sex;
}; 

// 说明:
 1. 类的声明是以分号结束的;
 2. 除非定义类中访问权限,则默认为private;同一访问限定符可出现多次
private -->  类内可以访问,类外不可访问
public -->  类内外均可访问
protected -->  类内可以访问,类外不可访问,可被派生类的成员函数访问(家族限定符)

2. 类和结构体的异同

C++允许用struct来声明一个类

区别:struct默认访问权限为public,class默认访问权限为private。

二、类的成员函数

1. 成员函数的性质

(1)类中声明为public是类的对外接口;类中声明为private是类中其他成员的工具函数;

class MyCircle
{
    public:
        // double m_s = 0; 只有静态常量整形数据才可以在类中初始化
        // 这种形式在VS2013时编译通过,VS2010编译失败
        double m_s;
        double m_r;

        // 如果在VS2013编译器中定义
        double m_s = 3.14 * m_r * m_r;
        
        // 说明:
           1. 如果定义了类的对象,会为对象分配内存,成员变量初始化
              此时m_r是随机值,则m_s就是乱码

           2. MyCircle mc;
              mc.m_s 只是从变量所处的内存空间中取值,并没有执行3.14*m_r*m_r,因此
              获取的是乱码

};

(2)在类中声明函数原型,函数的定义可以在类外实现;此时需要加上作用域限定符号::,声明函数是属于哪个类;

(3)内置成员函数inline

为了减少调用函数过程中时间开销,将函数内置,调用函数时,并不真正执行函数过程(如保留返回地址等),而是嵌入程序调用点;一般针对规模小,没有循环等控制结构的函数设为内置函数。

① 类中声明定义的成员函数,默认为inline,可省略;

② 类外定义的成员函数,必须显式声明为Inline;

2. 成员函数的存储方式

同一类的不同对象,数据成员的值一般不相同,但是成员函数代码相同

每个对象所占用的存储空间只是数据成员所占的存储空间,成员函数代码段不占对象的存储空间。

三、对象成员的引用

// 以下三种方法:
//(1)通过对象名和成员运算符访问。
Student stu;
stu.name = "zhangsan";

//(2)通过指向对象的指针访问。
Student * p;
p = &stu;
(*p).name = "zhangsan";
// p->name = "zhangsan";

//(3)通过对象的引用访问。
Student &t = stu;
t.name = "zhangsan";  // t与stu共同占用一段存储单元(即t是stu的别名)

四、类的封装性和信息隐藏

类的声明和成员函数定义的分离

 源文件main.cpp和student.cpp分别进行编译,得到两个目标文件main.obj和student.obj(目标文件),然后将它们和其他系统资源链接起来,形成可执行文件main.exe。

在实际工作中,并不是将一个类的声明做成一个头文件,而是将若干个常用的功能相近的类声 明集中在一起,形成类库。 类库有两种:① C++编译器系统提供的标准类库;② 用户根据自己的需要做成的用户类库, 提供给自己和自己授权的人使用,称为“自定义类库”。

类库包括两组组成部分:① 类声明文件(.h)② 类实现文件编译的目标文件(.obj)。用户必须把类库装在自己的计算机系统中(一般装到C++编译系统所在的子目录下),并在程序中用 #include指令将有关的类声明的头文件包含在程序中,就可以在程序中使用这些类。在用户源文件经过编译后,与系统库(是目标文件)相连接。在用户程序中包含类声明头文件,类声明头文件 就成为用户使用类库的有效方法和公用接口,用户只有通过头文件才能使用有关的类,实现了接口和实现的分离。

五、类对象的初始化

1. 初始化:构造函数

构造函数是C++提供来处理对象的初始化的特殊成员函数。

① 构造函数名与类名相同;② 没有任何返回类型的声明;③ 构造函数可以有参数,可以发生重载;④ 一般情况下,C++编译器会自动调用构造函数,有需要可手动调用;

构造函数的分类

两个特殊的构造函数

(1)默认无参构造函数

当类中没有定义任何构造函数时,编译器默认提供一个构造函数,其函数体为空

Test t;  // 如果没有定义任何构造函数,调用的是默认构造函数,其函数体为空
    // 不能写成 Test t();
Test t;  // 如果定义了构造函数,调用的是无参构造函数
Test t();  // 如果定义了构造函数,调用的是无参构造函数

(2)默认拷贝构造函数

当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值赋值(浅拷贝)

2. 构造函数的调用

(1)构造函数的调用

 (2)拷贝构造函数的调用

 各类构造函数的应用案例

【对象初始化操作】和【= 等号操作】 是两个不同的概念

结论:① 函数的返回值是一个元素(复杂类型的),返回的是一个新的匿名对象,所以会调用对象的拷贝构造函数。

② 匿名对象的去和留,关键是看如果承接这个返回值:如果是赋值给同类型的对象(第一种),匿名对象会立马被析构;如果是初始化同类型对象(第二种),会直接转化成对象。

3. 构造函数调用规则

(1)当类中没有定义任何一个构造函数时,C++编译器会提供默认无参构造函数和默认拷贝构造函数;

(2)当类中定义拷贝构造函数时,C++编译器不会提供默认无参构造函数;

(3)当类中定义了任何非拷贝构造函数(即类中提供了有参构造函数或无参构造函数),C++编译器不会提供默认无参构造函数;

(4)默认拷贝函数是成员变量简单赋值(浅拷贝)

总结:只要写了构造函数,就必须调用到(重点)。

4. 参数初始化列表

(1)对象初始化列表出现的原因

必须这样做:

① 如果一个类成员,其本身是一个类或者是一个结构体,而且这个成员没有默认构造函数,此时要对这个类进行初始化,就必须使用初始化列表。

② 若类成员中若有const修饰,必须在对象初始化时,给const成员赋值,使用初始化列表。

③ 当类成员中含有const对象或引用,必须通过初始化列表进行初始化。因为这两种对象要在声明时完成初始化,而构造函数中,做的是对他们赋值,这样是不允许的。

(2)初始化列表的初始化

参数初始化表不在函数体内对数据成员初始化,而是在函数的首部实现,初始化列表还可以传递参数。

(3)调用顺序

先执行初始化列表的初始化,再执行构造函数体

初始化列表有多项时,按照定义顺序,而不是按照初始化列表的顺序进行初始化

例如:先定义 A objA2; 再定义 A objA1

(4)注意概念

初始化:被初始化的对象正在创建

赋值:被赋值的对象已经存在,拥有内存空间

 5. 析构函数

作用与构造函数相反。当对象生命期结束时,会自动执行析构函数。先构造的后析构

析构函数名字是类名前加上"~"符号。有以下几种情况:

① 函数调用结束,函数中定义的对象在释放前,自动执行析构函数。

② 静态局部对象在函数调用结束不释放,在程序结束才调用对象本身的析构函数。

③ 全局对象,程序结束才调用对象的析构函数。

④ 如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析 构函数。

析构函数的是在撤销对象占用的内存之前完成一些清理工作,不是删除对象,是使这部分内存可以被 程序分配给新对象使用。 析构函数不返回任何值,没有函数类型,没有函数参数,不能被重载。一个类可以有多个构造函数, 只能有一个析构函数。

六、深拷贝和浅拷贝

不同于基本数据类型,引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存中。深浅拷贝是只针对Object和Array这样的引用数据类型。

1. 浅拷贝问题的分析

(1)结果:

 (2)分析:

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

在析构对象时,需要释放指针p指向的内存空间 free(p),p=NULL;析构对象t1后,t2.p就成为野 指针,再次释放t2.p时,程序会报错。

(3)拷贝构造函数和赋值运算的区别

拷贝构造函数:class(const class & obj)

赋值运算符:class& operator=(const class& obj)

**** 区别是拷贝构造是对象没有创建,而赋值时对象已经创建了。***

 2. 浅拷贝问题的解决

3. 拷贝构造函数详解(重点)

参考以下大佬的博文:

一文看懂C++类的拷贝构造函数所有用法(超详细!!!)_一只努力学习的程序猿的博客-CSDN博客_拷贝构造函数用法https://blog.csdn.net/qq_43519886/article/details/105170209?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163929957616780357240217%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163929957616780357240217&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click_v2~default-5-105170209.pc_search_es_clickV2&utm_term=C%2B%2B%E6%8B%B7%E8%B4%9D%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187

七、对象指针和数组

1. 指向对象的指针

一个对象的存储空间的起始地址就是对象的指针,例如:
Student stu;
Student * pt;
pt = &stu;
(*pt).name; // pt指向对象的数据成员 pt->name
(*pt).show(); // pt指向对象的成员函数 pt->show();

2. 指向对象成员的指针

对象中的成员也有地址,定义指向对象成员的指针变量。

(1)指向对象数据成员

Student stu;
char * pt = stu.name;

(2)指向对象成员函数

1.普通函数的指针变量的定义方法
void (*p)();
p = fun;  // 将fun函数入口地址赋值给指针p
(*p)();  // 调用fun函数

2. 成员函数的指针变量的定义方法
void (Time::*p)();
p = &Time::fun;

// 说明:()优先级高于*  括号不能省略 void Time::*p() 表示返回值为void型指针的函数。
// 注意:可以和C语言用法一样,省略&,但是C++中不建议这样使用


Time t;
p = &t.fun;  // × 错误。因为成员函数不是存放在对象的空间中,而是存放在对象外的
                 // 空间中的,所有对象共用函数代码段。

3. 对象数组

对象数组的每一个元素都是同类的对象。

// 在建立对象时,同样要调用构造函数。
(1)如果构造函数只有一个参数
Student stus[3] = {60, 70, 80}; // 3个实参传递给3个数组元素的构造函数

(2)如果构造函数有多个参数,上述赋初值容易引发歧义,如下:
Studnet stu1(1001, 18, 87);
Studnet stu2(1002, 19, 76);
Studnet stu3(1003, 15, 84);
Student Stus[3] = {stu1, stu2, stu3};

八、共同数据的保护

增加数据的安全性。让数据在一定范围内共享,又保证它不被任意修改,此时定义为常量数据。

1. 常对象

在定义对象时加关键字const,指定对象为常对象。常对象必须有初始化。

类名 const 对象名[(实参表)];

const 类名 对象名[(实参表)];

(1)常对象只能调用常成员函数(除了系统隐式调用的构造函数和析构函数除外),防止普通成员函数修改对象中的数据成员的值。

(2)常对象中的数据成员都是常数据成员;只有加const声明的成员函数才是常成员函数。

(3)常对象初始化是在定义的时候进行的,调用构造函数使用初始化列表。

2. 常对象成员

(1)常数据成员

如果没在声明时初始化,只能通过构造函数的参数初始化表对其进行初始化,任何其他函数都不能对其赋值。

(2)常成员函数

常成员函数内部不允许进行数据成员的修改,不能调用非const成员函数。

const是函数类型的一部分,在定义和声明时都不能省略。

例如:void get_Time() const;

为什么常成员函数不能调用非const成员函数?

因为类中的成员函数都会隐式传递类的指针this。对于const成员函数,其中const修饰的是this指针所指向的内存空间的形参const Calss * this,this是常量指针,不能改变指向内存空间的内容,所以解释了【常成员函数不允许进行数据成员的修改】;非const成员函数形参传递Class * this,调用该成员函数时,有可能改变数据成员的值,所以解释了【常成员函数不能调用非成员函数】

数据成员非const成员函数const成员函数
非const数据成员访问  ✔  修改  ✔访问  ✔  修改  ✖
const数据成员访问  ✔  修改  ✖访问  ✔  修改  ✖
const对象调用  ✖调用  ✔  修改  ✖

3. 指向对象的常指针

将指针变量声明为const类型,这样的指针变量始终保持为初始值,不能改变

Time t1(10, 12, 15);
Time t2;
Time * const ptr1 = &t1;  // 定义常变量指针,赋初值
// ptr1 = &t2;  // 错误 ptr1不能改变指向
t1.hour = 20;
// 打印 (*ptr).hour为20

什么时候会用到常指针?

(1)如果想将一个指针变量始终指向一个对象,可以定义为常指针;

(2)往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,始终指向原来的内存区域;

4. 指向常对象的指针

指向常对象的指针常用于函数形参,保护形参指针所指向的对象的值,在函数执行过程中不被修改

Time t1(10, 12, 15);  // t1是非const型对象
const Time * ptr = &t1;  // 合法
t1.hour = 18;
(*ptr).hour = 18;  // 非法,不能改变ptr指向的内容

如果希望任何情况下,t1的值都不能改变,应定义为常对象,如:
const Time t1(10, 12, 15);

5. 常量指针和指针常量

const char * ptr; (常量指针)

char * const ptr; (指针常量)

形参实参是否合法改变实参的值
非const型 指针非const 变量地址
非const型 指针const 变量地址
const型 指针非const 变量地址
const型 指针const 变量地址

 6. 对象的常引用

变量名和引用名都指向同一个内存单元。

可以把函数的形参声明为const(常引用),函数原型为:

void fun(const Time &t);

九、对象的动态建立和释放

1. new和delete基本语法

C++提供运算符new和delete来取代C语言中的库函数malloc和free,用于动态分配释放内存空间。

new int; // 开辟一个存放整数存储空间,返回指向该存储空间的地址(指针)
new int(100); // 开辟...指定该整数初始值为100,返回...
new char[10];  // 开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];  // 开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float * p = new float(3.14159);
// 开辟一个存放单精度数的空间,并指定该实数的初值,返回该空间的地址赋给指针变量p

如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

2. 类对象的动态建立和释放

C++中,可以用new运算符动态建立对象,用delete运算符撤销对象。

 用malloc和delete,new和delete分别搭配可正常使用 值得注意的是,free将指向的内存释放后,指针本身的值并未改变,不过变成了野指针,释放完成后 最好把指针赋值为NULL;VS2013测试时,delete后指针的值改变了,都统一设置为0x00008123

0x00008123位于Zero Pages(第0~15页,地址范围0x0~ 0x0000FFFF)中,因而如果被访问到会导致程序触发存取违例,因而这 个地址可以被视为安全的。相对于更加频繁出现的访问0x00000000造成的空指针引用崩溃,如果程序员看到程序是访问了 0x00008123崩溃了,那么立马就应该知道是发生了释放后引用的问题。 如果在vs2011之前的环境下测试的话,delete的值可能并不会改变。

 十、对象的赋值和复制

1. 对象的赋值

同类的对象之间可以互相赋值。

对象之间的赋值可以通过赋值运算符“=”进行。本来,赋值运算符“=”只能用来对单个的变量赋值,通过对赋值运算符的重载,扩展为两个同类对象之间的赋值。

 说明:(1)对象的赋值只是对其中数据成员的赋值。(2)类的数据成员中不能动态分配的数据,否则在赋值时可能出现严重后果。

例如:某类的对象a中有动态申请的数据,a赋值给同类对象a,则a,b指向同一块内存地址,这样无论a或b释放内存都会导致另一个访问违例崩溃。

2. 对象的复制

多个完全相同的对象,对象“克隆”方法。

(1) 一般形式 ① 类名 对象2(对象1)
Box box2(box1)
// 作用是用已有的对象box1去克隆出一个新对象box2

在建立对象box2时调用一个特殊的构造函数——复制构造函数
Box::Box(const Box & b)
{
    height = b.height;
    width = b.width;
}

(2) 一般形式 ② 类名 对象2 = 对象1
Box box2 = box1;
// 作用是对象1去初始化对象2,调用的是——复制构造函数

对象的赋值:对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值
对象的复制:对已有对象“克隆”,从无到有建立一个新对象

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值