【C++】类与对象(上)

这篇具有很好参考价值的文章主要介绍了【C++】类与对象(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章

之前的文章中讲解了,什么是类、类的实例化,以及封装的意义,若仍有不理解的部分可以移步上一篇文章  【C++】类与对象(引入)


目录

系列文章

1.默认成员函数

2.构造函数

2.1定义

2.2特性

2.2.1重载构造函数

2.2.2与缺省参数混合使用

2.2.3默认构造函数

3.析构函数

3.1定义

3.2特性

4.拷贝构造

5.赋值运算符重载

5.1运算符重载

5.2赋值运算符重载

5.3区分调用时的赋值运算符重载与拷贝构造

6.const成员

7.取地址操作符重载

8.总结


1.默认成员函数

🧀如果一个类中一个成员都没有的话,就称这个类为空类

🧀但空类并不是什么都没有。若用以下的代码查看对象 的大小,你会发现输出的结果是 而不是 。虽然这是个空类,但是系统为了表现他至少存在,便为其分配了 字节的空间(占位符)以表示其存在

class A     //声明一个空类
{

};

int main()
{
	A a;
	cout << sizeof(a);  //打印对象a的大小
	return 0;
}

不仅如此, 类中还带有六个天选之子,也就是我们说的默认成员函数。只要用户没有显示实现,编译器就会自动生成,下面就开始一个个学习吧。

【C++】类与对象(上)

2.构造函数

2.1定义

🧀在以前,我们使用C语言写数据结构的时候,都会写一个初始化的函数,之后还要手动调用它。显得非常麻烦,而构造函数就相当于实现在类中的初始化函数,不用我们自己手动操作,实例化对象的时候便会自动调用,可谓是十分方便。

🧀百闻不如一见,一起来看看构造函数是怎么实现的。

class A           //定义一个类A
{
public:
	A(int a)      //实现A的构造函数
	{
		_a = a;   //用传入的参数初始化成员a
	}
private:
	int _a;       //定义成员a
};

int main()
{
	A a(5);       //实例化对象a
	return 0;
}

2.2特性

🧀构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。总结为以下特性:

  • 函数名与类名称相同。
  • 没有返回值(不是void,而是直接不写)。
  • 对象实例化时自动调用对应的构造函数。
  • 可以重载。

2.2.1重载构造函数

🧀正是因为构造函数支持重载,这才使其有多种的初始化方式除了赋初值的方式进行构造,还能够使用无参的构造函数,适用于各个场景。

class A           //定义一个类A
{
public:
	A(int a)      //实现A的构造函数
	{
		_a = a;   //用传入的参数初始化成员a
	}
	A()           //无参的构造函数
	{}
private:
	int _a;       //定义成员a
};

int main()
{
	A a1(5);       //内部a被初始化成5
	A a2;          //内部a未被初始化,是随机值
	return 0;
}

【C++】类与对象(上)

2.2.2与缺省参数混合使用

🧀但像上面那样写成两个构造函数未免过于麻烦了,现在我们想要一个函数中,如果我们传参就使用传过去的参数进行初始化,若没有传参的话也希望有一个初始值对其进行初始化而不是系统的随机值。

🧀这时候我们突然想起来,之前学过的缺省参数正好符合我们的目标要求。便可以在定义带参数的构造函数中加上缺省参数,实现目标效果。

class A           //定义一个类A
{
public:
	A(int a = 0)      //实现A的构造函数缺省值为0
	{
		_a = a;   //用传入的参数初始化成员a
	}
private:
	int _a;       //定义成员a
};

int main()
{
	A a1(5);       //内部a被初始化成5
	A a2;          //内部a被初始化成0
	return 0;
}

2.2.3默认构造函数

🧀我们知道当我们未显式定义时,编译器会自动生成一个默认成员函数,用于对类进行初始化。但这个默认构造函数对内置类型不做处理,对于自定义类型则会去调用其默认构造函数进行构造。

🧀其中所说的内置类型就是:int char ...以及指针,这种语言提供给我们的数据类型。

🧀同样,自定义类型就是我们自己定义出来的类、结构体、union这类的类型。

 🧀C++11 中针对内置类型成员不初始化的缺陷,打了补丁:内置类型成员变量在类中声明时可以给默认值

 🧀于是我们就可以像下方代码这样,给特定的成员变量默认值。

class A           //定义一个类A
{
public:
	A(int a)      //实现A的构造函数
	{
		_a = a;    //用传入的参数初始化成员a
	}
	A()            //无参的构造函数
	{}
private:
	int _a = 0;       //定义成员a给定初始值为0
};

int main()
{
	A a1(5);       //内部a被初始化成5
	A a2;          //内部a被初始化成0
	return 0;
}

[注意]

🧀无参的构造函数全缺省的构造函数都称为默认构造函数无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认构造函数。并且默认构造函数只能有一个,若全缺省和无参数的构造函数同时出现,则在无参实例化对象的时候出现歧义。因此二者不能同时出现。

【C++】类与对象(上)

3.析构函数

3.1定义

🧀析构函数与构造函数的功能正相反,析构函数不是完成对对象本身的销毁,而是对象在销毁时会自动调用析构函数,就像以前写的 destory 函数,完成对象中资源的清理工作。

3.2特性

  • 析构函数名就是 ~+类名
  • 没有参数,也没有返回值
  • 一个类只能有一个析构函数,且不能重载
  • 在对象生命周期结束时,C++编译系统自动调用。

🧀我们用如下代码验证析构函数在对象销毁时会自动调用:

class A
{
public:
	A()
	{
		cout << "调用构造函数:A()" << endl;   //调用构造函数的话就输出
	}
	~A()
	{
		cout << "调用析构函数:~A()" << endl;  //调用析构函数的话就输出
	}
};

int main()
{
	A a;
	return 0;
}

🧀可以直接看到程序结束时, 编译器自动地调用了目标类的析构函数:

【C++】类与对象(上)

 🧀不仅如此,若该类中若有自定义类型的成员变量,则会调用该自定义类型成员变量的析构函数。

【C++】类与对象(上)

class B
{
public:
	~B()
	{
		cout << "调用B的析构函数:~B()" << endl;  //调用B的析构函数的话就输出
	}
};

class A
{
public:
	A()
	{
		cout << "调用A的构造函数:~A()" << endl;    //调用构造函数的话就输出
	}
	~A()
	{
		cout << "调用A的析构函数:~A()" << endl;   //调用A的析构函数的话就输出
	}
private:
	B b;     //有个自定义的成员变量
};

int main()
{
	A a;
	return 0;
}

🧀小结:创建哪个类的对象则调用那个类的构造函数,销毁那个类的对象也调用该类的析构函数

4.拷贝构造

拷贝构造其实是构造函数的一个重载形式,用于创建一个与当前已存在对象一模一样的对象。在用已存在的类类型对象创建新对象时由编译器自动调用。

🧀拷贝构造函数的参数只有一个且必须是类类型对象的引用,由于是拷贝因此不修改原对象的值所以一般还使用 const 修饰形参。

class A
{
public:
	A(int a = 5)     //A的构造函数
	{
		_a = a;
	}
	A(const A& a)    //A的拷贝构造函数
	{
		_a = a._a;
	}
private:
	int _a;         //A的成员变量
};

int main()
{
	A a;            //实例化a
	A a1(a);        //用a拷贝构造a1
	//A a1 = a;     //也可以这样写
	return 0;
}

🧀若使用传值方式传递拷贝构造函数的参数,编译器会直接报错,因为会引发无限递归调用

🧀我们都知道,在函数之中形参是实参的一份临时拷贝,当我们通过传值传参给拷贝构造函数时,系统会调用拷贝构造函数生成形参。因为又调用了拷贝构造函数所以又要生成形参,为了生成形参又要调用拷贝构造函数。如此反复就会产生无限递归的情况。因此,使用引用作为拷贝构造函数的参数就能够解决当前问题。因为引用就是原来对象的别名,因此不会再次调用拷贝构造函数。

【C++】类与对象(上)

🧀若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝。

 🧀就像这样,我们并没有直接写 的拷贝构造函数,但是系统的默认生成的拷贝构造函数还是能够实现对对象的拷贝。其中对于内置类型则直接拷贝,而自定义类型则会调用对应的拷贝构造函数

【C++】类与对象(上)

 🧀但是否编译器生成的默认拷贝构造函数就能够满足我们的需求呢?答案是否定的,当类涉及资源申请的时候,默认拷贝构造函数就无法完成任务。

class A
{
public:
	A()
	{
		_a = (int*)malloc(sizeof(int));  //动态开辟空间
	}
	~A()
	{
		free(_a);    //释放_a
	}
private:
	int* _a;         //A的成员变量
};

int main()
{
	A a;            //实例化a
	A a1(a);        //用a拷贝构造a1
	return 0;
}

若运行上文的代码,系统便会崩溃报错, 这是为什么呢?通过调试我们能够观察到:两个类中的指针是一样的。这意味着在拷贝构造的时候,默认的拷贝构造只是将数值给了新构造类中的变量,并没有再次动态开辟内存,因此最后调用析构函数时,由于只开辟了一块空间却释放了两次,因此在free处报错。

【C++】类与对象(上)

 🧀若我们自己实现一个拷贝构造函数为变量开辟空间,程序便不会崩溃报错了。

class A
{
public:
	A()
	{
		_a = (int*)malloc(sizeof(int));  //动态开辟空间
	}
	A(const A& a)
	{
		_a = (int*)malloc(sizeof(int));  //动态开辟
		*_a = *a._a;                     //赋值拷贝
	}
	~A()
	{
		free(_a);    //释放_a
	}
private:
	int* _a;         //A的成员变量
};

int main()
{
	A a;            //实例化a
	A a1(a);        //用a拷贝构造a1
	return 0;
}

拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

🧀每次使用传值传参都要调用一次拷贝构造,其代价是随着代码复杂度而上升的,为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时则根据实际场景,能用引用尽量使用引用。

5.赋值运算符重载

5.1运算符重载

🧀 C++ 为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,使得自定义对象可以使用运算符

🧀函数原型:返回值类型 + operator + 重载的操作符 + (参数列表)

class A
{
public:
	A(int a = 5)
	{
		_a = a;
	}
	int operator+(const A& a)  //重载+号
	{
		return _a + a._a;
	}
private:
	int _a;         //A的成员变量
};

int main()
{
	A a(3);            //实例化a
	A a1(a);           //用a拷贝构造a1
	cout << a + a1;    
	return 0;
}

🧀凡事都有例外,需注意以下五点:

  • 不能通过连接其他符号来创建新的操作符。
  • 重载操作符必须有一个类类型参数。
  • 用于内置类型的运算符,不能改变其含义,例如:内置的整型+,不能改变其含义。
  • 作为类成员函数重载时,其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this
  • .*    ::   sizeof    ? :     .   以上5个运算符不能重载。

5.2赋值运算符重载

🧀赋值运算符重载就是对 ‘=’ 的重载,需要注意以下细节:

  • 为了能够多次连续赋值,应将自身作为返回值
  • 检测是否给自己赋值
  • 只能重载成类的函数而不能重载成全局函数
class A
{
public:
	A(int a = 5)
	{
		_a = a;
	}
	A& operator=(const A& a)  //赋值重载
	{
        if(&a == this) return *this   //检测是否给自己赋值
		_a = a._a;                    //赋值
		return *this;                 //返回自己
	}
	void print()
	{
		cout << _a << endl;
	}
private:
	int _a;         //A的成员变量
};

int main()
{
	A a(3);            //实例化a
	A a1(5);           //实例化a1
	a.print();
	a1.print();
	a = a1;            //用a1赋值a
	a.print();
	return 0;
}

🧀如此便完成了赋值运算符重载的实现。 

🧀用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

🧀与拷贝构造函数相同,当类中涉及动态开辟时,默认的运算符重载的缺点就暴露出来了。如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现

5.3区分调用时的赋值运算符重载与拷贝构造

🧀我们知道,拷贝构造还有以下这种写法,不禁让人想对比其与运算符重载之间的区别。

A a1 = a;

🧀拷贝的双方:赋值运算符重载的两个对象都是已经实例化的对象,而拷贝构造中是以一个已经实例化的对象为基础来实例化一个新的对象

区分的小细节:只要看当前行是否存在对象的声明,即找当前行是否出现了类名,若出现则是拷贝构造,否则为赋值运算符重载。

A a1(a);     //调用拷贝构造
a = a1;      //调用赋值重载
A a1 = a;    //调用拷贝构造

6.const成员

🧀将const修饰的成员函数称之为 const 成员函数,const 修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。

🧀例如我们此时有一个 const A 类型的对象,若我们直接调用我们的一个打印函数,编译器会报错,因为会出现权限放大

【C++】类与对象(上)

🧀放作平时,我们可以直接给参数列表里的参数加上一个 const ,这样既能避免权限放大,同时普通的对象也能够调用这个函数

🧀但类中的这个函数没有参数,若想修饰其中的 this 指针就需要使用 const 来修饰函数,进而避免权限放大。

🧀为了函数的泛用性,内部不改变成员变量的成员函数,最好用 const 来修饰

7.取地址操作符重载

🧀取地址操作符重载包括了 取地址及 const 取地址操作符重载,一般不用重新定义 ,编译器默认会生成。可以这么写但是意义不大。

class A
{
public:
	A(int a = 5)
	{
		_a = a;
	}
	A* operator&()                //取地址操作符重载
	{
		return this;
	}
	const A* operator&() const    //const取地址操作符重载
	{
		return this;
	}
private:
	int _a;         //A的成员变量
};

int main()
{
	const A a(3);    //实例化a
	A a1(5);
	printf("%p\n%p\n", &a, &a1);
	return 0;
}

🧀若想让别人获得特定的内容,就可以使用这个重载。 无论怎么取该对象的地址返回的都是空指针。

class A
{
public:
	A(int a = 5)
	{
		_a = a;
	}
	A* operator&()                //取地址操作符重载
	{
		return nullptr;           //无论如何返回空指针
	}
	const A* operator&() const    //const取地址操作符重载
	{
		return nullptr;
	}
private:
	int _a;         //A的成员变量
};

int main()
{
	const A a(3);    //实例化a
	A a1(5);
	printf("%p\n%p\n", &a, &a1);
	return 0;
}

8.总结

今天简单地讲了类中 6 个默认成员函数、运算符重载以及 const 成员函数。需要具体区分的是拷贝构造函数赋值运算符重载,前面四个函数都要了解清楚其性质以及如何实现,并落实到代码上。而最后两个倒没有那么重要作为了解就可以。学会利用一些代码的技巧去减少程序的负担,就比如上文讲到的,情况允许的话使用引用传参和返回

好了,今天类与对象上半部分讲解到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。文章来源地址https://www.toymoban.com/news/detail-414057.html

到了这里,关于【C++】类与对象(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • C++类与对象(下)

    C++类与对象(下)

    创建对象时,编译器会调用构造函数给对象中的成员变量一个合适的初始值。 虽然上述构造函数调用时,每个成员变量都有一个初始值了,但是这并不能称为类对象成员的初始化, 构造函数体中的语句只能称为赋初值 ,而不能称为初始化,因为 初始化只能初始一次,而构造

    2024年02月11日
    浏览(8)
  • C++ 类与对象(上)

    C++ 类与对象(上)

    目录 本节目标  1.面向过程和面向对象初步认识  2.类的引入  3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化  7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式猜测 7.3 结构体内存对齐规则  8.this指针 8.1 this指针的引出

    2024年02月19日
    浏览(4)
  • C++ 类与对象(下)

    C++ 类与对象(下)

    目录 1. 再谈构造函数 1.1 构造函数体赋值  1.2 初始化列表  1.3 explicit  2. static成员 2.1 概念 2.2 特性  3.友元 3.1友元函数   3.2 友元类 4. 内部类 5.匿名对象  6.拷贝对象时的一些编译器优化  7. 再次理解类和对象 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5.匿名对象

    2024年02月19日
    浏览(6)
  • 【C++】类与对象(中)

    【C++】类与对象(中)

    目录 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5. 赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置++和后置++重载 6. const成员 7. 取地址及const取地址操作符重载 如果一个类中什么成员都没有,

    2024年02月05日
    浏览(6)
  • C++类与对象(下)

    您好这里是limou3434的博文,感兴趣可以看看我的其他博文系列。 这一部分是对C++类的一些补充:初始化列表、友元、内部类。 在给对象进行初始化的时候,如果选择函数体内进行初始化是一件很麻烦的事情。 但是这样写是没有办法过的,要成功运行的话只能写一个默认构造

    2024年02月11日
    浏览(8)
  • 【C++】——类与对象(上)

    【C++】——类与对象(上)

    今天我们来学习C++初期最重要的知识点,类与对象,因为类与对象涉及的知识太多,所以我将分为上中下三个部分来为大家讲解类与对象。 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++是基于面向对象的,关注的是对象,将一

    2024年02月02日
    浏览(8)
  • [C++]类与对象下篇

    [C++]类与对象下篇

    目录 类与对象下篇::                             1.再谈构造函数                             2.static成员                             3.友元                             4.内部类                             5.匿名对象                             6.拷贝

    2023年04月25日
    浏览(8)
  • c++类与对象详解

    c++类与对象详解

    在C++中,对象是类的实例。定义对象的语法为: 其中,class_name 是定义类时指定的类名,object_name 是对象的名称。 例如,在以下示例中,我们定义了一个名为 Rectangle 的类和两个对象 rect1 和 rect2: 这里我们创建了两个 Rectangle 类的对象:rect1 和 rect2。 在C++中,类可以通过以

    2024年02月13日
    浏览(10)
  • 【C++】——类与对象(下)

    本文是C++类与对象部分最后一篇文章,类与对象的重点是类与对象(中),本文主要是补充类与对象的剩余知识点及收尾。 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。

    2024年02月06日
    浏览(7)
  • [C++] 类与对象(上)

    [C++] 类与对象(上)

      目录 1、前言 2、类的引入 3、类的定义 3.1 类的两种定义方式 4、类的访问限定符 5、类的作用域 6、类的实例化 7、类对象模型 7.1 内存对齐规则 7.1 类对象的存储方式 8、this指针 8.1 this指针的特性 8.2 this指针是否可以为空 C语言是 面向过程 的, 关注 的是 过程 ,分析出求解

    2024年02月15日
    浏览(6)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包