C++学习笔记4--类(未完待续5/6)
发布日期:2021-05-08 01:59:56 浏览次数:20 分类:精选文章

本文共 5931 字,大约阅读时间需要 19 分钟。

目录:(直接使用ctrl+F搜索标题)

1.C++中类的定义
2.class与struct的区别
3.对象的创建
4.初始化表达式
5.对象的销毁
5.1析构函数在哪些时候会被调用呢?
6.拷贝构造函数
6.1拷贝构造函数的调用时机
7.隐含的this指针
8.赋值运算符函数
9.特殊数据成员的初始化
9.1常量数据成员
9.2引用数据成员
9.3类对象成员
9.4静态数据成员
10.特殊的成员函数
10.1静态成员函数
10.2const成员函数
11.对象的组织
11.1const对象
11.2指向对象的指针
11.3对象数组
11.3.1对象数组的声明:
11.3.2对象数组的初始化:可以在声明时进行初始化。
11.4堆对象
12.单例模式

正式开始:

1.C++中类的定义

1)概念:C++用类来描述对象,类是对现实世界中相似事物的抽象,比如同是“双轮车”的摩托车和自行车,有共同点,也有许多不同点。“车”类是对摩托车、自行车、汽车等相同点的提取与抽象

2)基本形式:
在这里插入图片描述
public:进行修饰的成员表示的是该类可以提供的接口、功能、或者服务
protected:进行修饰的成员,其访问权限是开放给其子类
private:进行修饰的成员是不可以在类之外进行访问的,只能在类内部访问,可以说封装性就是由该关键字来体现

class Computer{   public:  //成员函数  void setBrand(const char * brand) {       strcpy(_brand, brand); }   void setPrice(float price) {       _price = price; } private:  //数据成员  char _brand[20];  float _price;};int main(int argc, char * argv[]){     Computer pc;//定义了一个Computer类的对象,这个对象叫pc  pc.setBrand("Huawei Matebook14");  pc.setPrice(5600);  return 0;}

3)还可以在类外面增加成员函数

类似格式如下:
在这里插入图片描述
实际例子:
在这里插入图片描述

2.class与struct的区别

1)在C++中,与C相比,struct的功能已经进行了扩展。class能做的事儿,struct一样能做,他们之间唯一的区别,就是默认访问权限不同。class的默认访问权限是private,struct的默认访问权限是public

2)下面是c语言中的结构体

struct Computer{     //成员函数, 其访问权限是public  void setBrand(const char *brand) {       strcpy(_brand, brand); }  void setPrice(float price) {       _price = price; }   //数据成员, 其访问权限是public  char _brand[20];  float _price;};

3)下面是C++中的类

class Computer2{     //成员函数, 其访问权限是private  void setBrand(const char *brand) {       strcpy(_brand, brand); }   void setPrice(float price) {       _price = price; }   //数据成员, 其访问权限是private  char _brand[20];  float _price;};

3.对象的创建

1)在之前的Computer类中,通过自定义的公共成员函数setBrand和setPrice实现了对数据成员的初始化。实际上,C++为类提供了一种特殊的成员函数----构造函数来完成相同的工作。构造函数有一些独特的地方:

a)函数的名字与类名相同
b)没有返回值
c)没有返回类型,即使是void也不能有
2)构造函数在对象创建时自动调用,用以完成对象成员变量等的初始化及其他操作(如为指针成员动态申请内存等);如果程序员没有显式定义它,系统会提供一个默认构造函数。下面我们用一个点Point来举例:

class Point{   public:  //即使不写,编译器也会自动提供一个 	 Point()//构造函数:完成对象成员变量等的初始化及其他操作(如为指针成员动态申请内存等)	 {   	 	 cout << "Point()" << endl;		 _ix = 0;   		 _iy = 0; 	  }   		void print() 	{       	 cout << "(" << _ix     	 << "," << _iy     	 << ")" << endl; 	} private:  	int _ix;  	int _iy;};int main(void){     Point pt; pt.print();   return 0;}

3)编译器自动生成的缺省(默认)构造函数是无参的,实际上,构造函数可以接收参数,在对象创建时提供更大的自由度。我们在上面的Point类中可以加入一个新的构造函数\

class Point{   public://...  Point(int ix, int iy)//构造函数可以接收参数 {       cout << "Point(int,int)" << endl;    _ix = ix;    _iy = iy; }//....  };int main(void){     Point pt(1, 2);  pt.print();   Point pt2(11, 12);  pt2.print();   return 0;}

4)上面的例子同时出现了无参构造函数,和有参构造函数,这说明了构造函数是可以重载的。在上面,我们说编译器会自动给Point类生成一个默认构造函数,这是有前提条件的,就是类中没有定义任何构造函数。现在假设Point类中只显式定义了一个有参构造函数,则编译器不会再自动提供默认构造函数,如果还希望通过默认构造函数创建对象,则需要显式定义一个默认构造函数

4.初始化表达式

1)在上面的例子中,构造函数对数据成员进行初始化时,都是在函数体内进行的。除此以外,还可以通过初始化列表完成。初始化列表位于构造函数形参列表之后,函数体之前,用冒号开始,如果有多个数据成员,再用逗号分隔,初始值放在一对小括号中。例子如下:

class Point{   public://...  Point(int ix = 0, int iy = 0) 	: _ix(ix) 	, _iy(iy) {       cout << "Point(int = 0,int = 0)" << endl; }//...};

2)如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。如在“对象的创建”部分的两个构造函数中的_ix和_iy都是先执行默认初始化后,再在函数体中执行赋值操作。可能有同学会觉得在初始化列表中进行成员初始化不习惯,但有些时候成员必须在初始化列表中进行,否则会出现编译报错

3)注意:每个成员在初始化列表之中只能出现一次,其初始化的顺序不是由成员变量在初始化列表中的顺序决定的,而是由成员变量在类中被声明时的顺序决定的。(举例说明)

class Foo{   public:  Foo(int a) 	: _iy(a) //在初始化列表中, _iy好像先被初始化 	, _ix(_iy) {       cout << "Foo(int)" << endl; }private:  int _ix;  //在声明时,_ix在前  int _iy;};

5.对象的销毁

1)构造函数在创建对象时被系统自动调用,而析构函数(Destructor)在对象被撤销时被自动调用,相比构造函数,析构函数要简单的多。析构函数有如下特点:

a)与类同名,之前冠以波浪号,以区别于构造函数。
b)析构函数没有返回类型,也不能指定参数。因此,析构函数只能有一个,不能被重载。
c)对象超出其作用域被销毁时,析构函数会被自动调用。
2)析构函数在对象撤销时自动调用,用以执行一些清理任务,如释放成员函数中动态申请的内存等。如果
程序员没有显式的定义它,系统也会提供一个默认的析构函数。例如:()

class Point{   public://...  ~Point() {       }//...};

3)由于Point类比较简单,数据成员中没有需要进行清理的资源,所以即使不显式定义析构函数,也没关

系。我们再举一个例子

class Computer{   public:  Computer(const char *brand, double price) : _brand(new char[strlen(brand) + 1]()) , _price(price) {       cout << "Computer(const char *, double)" << endl; }   ~Computer() {       cout << "~Computer()" << endl;    delete [] _brand;    _brand = nullptr; }  private:  char *_brand;  double _price;};

以上的Computer中,有一个数据成员是指针,而该指针在构造函数中初始化时已经申请了堆空间的资源,则当对象被销毁时,必须回收其资源。此时,编译器提供的默认析构函数是没有做回收操作的,因此就不再满足我们的需求,我们必须显式定义一个析构函数,在函数体内回收资源。

4)析构函数除了在对象被销毁时自动调用外,还可以显式手动调用,但一般不建议这样使用。
5.1析构函数在哪些时候会被调用呢?
5.1.1. 对于全局定义的对象,每当程序开始运行,在主函数main接受程序控制权之前,就调用构造函数创建全局对象,整个程序结束时,自动调用全局对象的析构函数。
5.1.2. 对于局部定义的对象,每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数。
5.1.3 对于关键字static定义的静态局部变量,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数
5.1.4 对于用new运算符创建的对象,每当创建该对象时调用构造函数,当用delete删除该对象时,调用析构函数

6.拷贝构造函数

1)C++中经常会使用一个变量初始化另一个变量,如

在这里插入图片描述
2)我们希望这样的操作也能作用于自定义类类型,如
在这里插入图片描述
这两组操作是不是一致的呢?第一组好说,而第二组只是将类型换成了Poin类型,执行Point pt2 = pt1;语句时,pt1对象已经存在,而pt2对象还不存在,所以也是这句创建了pt2对象,既然涉及到对象的创建,就必然需要调用构造函数,而这里会调用的就是复制构造函数,又称为拷贝构造函数。当我们进行测试时,会发现我们不需要显式给出拷贝构造函数,就可以执行第二组测试。这是因为如果类中没有显式定义拷贝构造函数时,编译器会自动提供一个缺省的拷贝构造函数。其原型是:
在这里插入图片描述
3)那缺省(默认)的拷贝构造函数是如何实现的呢?很简单,我们来实现一下Point类的拷贝构造函数:

Point::Point(const Point &rhs)	: _ix(rhs._ix)	, _iy(rhs._iy){    }

4)由于Point的成员比较简单,缺省的拷贝构造函数已经可以满足需求了,所以可以不显式定义。接下来,我们把目光转向Compute类,如果Computer类使用缺省拷贝构造函数,会发生什么问题呢?我们先来看看缺省的拷贝构造函数的实现

Computer::Computer(const Computer &rhs)	: _brand(rhs._brand)	, _price(rhs._price){    }//执行构造初始化Computer pc1("Huawei Matebook14", 5699);//Computer类使用缺省拷贝构造函数Computer pc2 = pc1;

从上面的定义来看,pc与pc对象的数据成员_brand都会指向同一个堆空间的字符串,这种只拷贝指针地址的方式,我们称为浅拷贝。当其中一个对象被销毁时,另外一个对象就获取不到相应的brand值了。此时需要重新显式定义拷贝构造函数,让两个指针不指向同一块堆空间:

Computer::Computer(const Computer & rhs): _brand(new char[strlen(rhs._brand) + 1]()), _price(rhs._price){     strcpy(_brand, rhs._brand);}

这种拷贝指针所指空间内容的方式,我们称为深拷贝。因为两个对象都拥有各自的独立堆空间字符串,一个对象销毁时就不会影响另一个对象

6.1拷贝构造函数的调用时机
6.1.1. 当用一个已经存在的对象初始化另一个新对象时,会调用拷贝构造函数。
6.1.2. 当实参和形参都是对象,进行实参与形参的结合时,会调用拷贝构造函数。
6.1.3. 当函数的返回值是对象,函数调用完成返回时,会调用拷贝构造函数。(优化选项-fno-elide-constructors)
注意:拷贝构造函数的参数形式可以改变吗?(也就是引用符号可以去掉吗?const关键字可以去掉吗?)

7.隐含的this指针

8.赋值运算符函数

9.特殊数据成员的初始化

9.1常量数据成员
9.2引用数据成员
9.3类对象成员
9.4静态数据成员
10.特殊的成员函数
10.1静态成员函数
10.2const成员函数
11.对象的组织
11.1const对象
11.2指向对象的指针
11.3对象数组
11.3.1对象数组的声明:
11.3.2对象数组的初始化:可以在声明时进行初始化。
11.4堆对象
12.单例模式

上一篇:Java面试/线程生命周期包括哪几个阶段
下一篇:C++学习笔记3--程序内存分配方式

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年04月01日 01时56分48秒