一、构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。构造函数主要任务并不是开空间创建对象,而是初始化对象。
特征:
1. 函数名与类名相同。
2. 无返回值。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6. C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(注:无参构造函数、全缺省构造函数、没有显式写出编译器默认生成的构造函数,都可以认为是默认构造函数。)
代码测试:
#include <iostream>
using namespace std;
class Custom {
public:
Custom(int ca = 0, int cb = 0) : _ca(ca), _cb(cb) {}
private:
int _ca;
int _cb;
};
class Test {
public:
// 1.无参构造函数,若没有显式定义,则编译器自动生成
// 内置类型仍为随机值,但自定义类型会调用对应的默认构造函数
test() {};
// 2.带参构造函数
// 带参无缺省函数体内初始化,
// 无缺省 -- 调用时需逐一输入对应形参值,
// 函数体内初始化 -- 变量经过定义后在函数体内重新赋值,因而不可赋值初始化常量
Test(int a, int b, Custom& c) {
_a = a;
_b = b;
_c = c;
}
// 带参全缺省函数体内初始化,
// 全缺省 -- 若无输入形参值,则默认使用缺省值,输入的变量值按对应顺序排列
Test(int a = 0, int b = 0, Custom c = {0, 0}) {
_a = a;
_b = b;
_c = c;
}
// 带参全缺省初始化列表初始化
// 初始化列表初始化 -- 在定义变量时即定义值,可初始化定义常量
Test(int a = 0, int b = 0, int ca = 0, int cb = 0) {
_a = a;
_b = b;
_c = { ca, cb };
_c = Custom(ca, cb); // 经编译器优化后,与上式等价
//_c(ca, cb); // 错误写法,不能编译通过
}
// 带参非缺省函数体内初始化
// 前几个形参可以不必缺省,但定义类对象时必须补齐
Test(int a, int b = 0, int ca = 0, int cb = 0, const int d = 0)
: _a(a)
, _b(b)
, _c(ca, cb)
, _d(d)
{}
// 带参全缺省初始化列表初始化
// 用另一种方式输入缺省的类
Test(int a = 0, int b = 0, Custom c = {0, 0}, const int d = 0)
: _a(a)
, _b(b)
, _c(c)
, _d(d)
{}
private:
int _a;
int _b;
Custom _c;
const int _d = 0;
};
int main() {
//Test test_1; // 调用无参或全缺省构造函数
//Test test_2(0, 0, 0, 0, 0); // 调用带参的构造函数(非全缺省)
//Test test_error(); // 这是一个错误的写法
//Test test_4(0, 0, 0, 0, 0);
//Test test_5(0, 0, { 1, 1 }, 0);
return 0;
}
二、析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特征:
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
代码测试:
#include <iostream>
using namespace std;
class Test {
public:
Test(int a, int b, int c) {
_a = a;
_b = b;
_c = (int*)malloc(c * sizeof(int));
if (_c == nullptr) {
perror("error");
exit(-1);
}
}
~test() {
free(_c);
_c = nullptr;
}
private:
int _a;
int _b;
int* _c;
};
int main() {
Test test(0, 0, 1);
return 0;
}
三、拷贝构造函数
拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(注:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。)
代码测试:
#include <iostream>
#include <assert.h>
using namespace std;
class Test {
public:
Test(int a, int b) {
assert(b > 0);
_a = a;
_b = b;
_c = (int*)malloc(b * sizeof(int));
if (_c == nullptr) {
perror("error");
exit(-1);
}
}
~test() {
free(_c);
_c = nullptr;
}
Test(const Test& t) {
_a = t._a;
_b = t._b;
_c = (int*)malloc(t._b * sizeof(int));
if (_c == nullptr) {
perror("error");
exit(-1);
}
}
private:
int _a;
int _b;
int* _c;
};
int main() {
Test test(0, 1);
return 0;
}
四、赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注:
1. 不能通过连接其他符号来创建新的操作符:比如operator@
2. 重载操作符必须有一个类类型参数
3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5. .* :: sizeof ? : . 注意以上5个运算符不能重载。
C++的默认成员函数中存在赋值运算符重载。赋值运算符只能重载成类的成员函数而不能重载成全局。因为赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
用户没有显式实现赋值运算符重载时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。但是内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。因此如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
对于 前置++和 后置++这两种单目运算符编译器运用函数重载实现区分。
代码测试:
#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;
class Test {
public:
Test(int a, int b) {
assert(b > 0);
_a = a;
_b = b;
_c = (int*)calloc(b, sizeof(int));
if (_c == nullptr) {
perror("error");
exit(-1);
}
}
~test() {
free(_c);
_c = nullptr;
}
Test(const Test& t) {
_a = t._a;
_b = t._b;
_c = (int*)calloc(t._b, sizeof(int));
if (_c == nullptr) {
perror("error");
exit(-1);
}
}
Test& operator=(const Test& t) {
if (this != &t) {
_a = t._a;
_b = t._b;
memcpy(_c, t._c, sizeof(*t._c));
}
return *this;
}
// 前置++
Test& operator++() {
_a++;
return *this;
}
// 后置++
Test operator++(int) {
Test ret(*this);
_a++;
return ret;
}
private:
int _a;
int _b;
int* _c;
};
int main() {
//Test test_1(0, 1);
//Test test_2(test_1);
//test_1++;
//++test_2;
return 0;
}
五、取地址操作符和 const取地址操作符
顾名思义,就是对类对象进行取地址操作,属于操作符重载。这两个默认成员函数一般不用重新定义,编译器默认会生成。
#include <iostream>
using namespace std;
class Test {
public:
Test* operator&() {
return this;
}
const Test* operator&() const {
return this;
}
private:
int _a;
};
int main() {
Test test_1;
const Test test_2;
Test* p_test = &test_1;
const Test* c_p_test = &test_1;
c_p_test = &test_2;
return 0;
}
原文地址:https://www.jb51.cc/wenti/3282957.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。