c++类和对象中

1,类的6个默认成员函数

如果一个类中什么成员都没有,简称空类。

默认成员函数:用户自己没有显示实现,编译器会自动生成的成员函数叫做默认成员函数

 重点是构造,析构,拷贝构造和赋值重载这四个函数。

2,构造函数

构造函数是特殊的成员函数,名字虽然叫做构造,但他不是用来开空间的,而是来完成初始化工作的。当实例化对象完成后,就会调用构造函数来对成员变量进行初始化。

构造函数的特点:

(1)函数名与类名相同

(2)无返回值(返回值什么都不写,void也不写)

(3)对象实例化时系统会自动调用构造函数

(4)构造函数可以重载

class Date
{
public:
    //构造函数可以重载
    Date()//无参构造函数
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
    Date(int year,int month,int day)//带参构造函数,全缺省
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void print()
    {
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;//调用无参的构造函数
    d1.print();

    Date d2(2024, 7, 26);//调用带参的构造函数
    d2.print();

    return 0;
}

(5)如果类中没有显示定义构造函数,那么c++编译器会自动生成一个无参的默认构造函数。

如果类中定义了,编译器将不会再生成。

class Date
{
public://调用编译器自动生成的构造函数
    
    void print()
    {
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;//调用无参的构造函数
    d1.print();



    return 0;
}

(6)无参构造函数,全缺省构造函数,和我们不写时编译器自动生成构造函数,都叫做默认的构造函数。但这三个函数不能同时存在,不然调用的时候会存在歧义。

说明: c++中,把类型分为内置类型自定义类型内置类型就是语言提供的原生数据类型,如int,double等,自定义类型就是我们使用class/struct等关键字自己定义的类型。

(7)我们不写时,编译器自动生成的构造函数,对内置类型成员变量没有要求,是否初始化取决于编译器。而对于自定义类型成员变量,要求调用这个成员变量的默认构造函数,若是没有默认构造函数,编译器就会报错

3,析构函数

析构函数与构造函数的功能相反,它完成的不是对对象本身的销毁,不如局部对象是存在栈帧的,函数结束栈帧销毁,它就释放了,不需要我们管。c++规定,在对象销毁时,会调用析构函数,完成对象中资源清理释放工作

析构函数的特点:

(1)析构函数名是在类名前加上~

(2)无参数,无返回值

(3)一个类只能有一个析构函数,若是未显示定义,系统会自动生成默认的析构函数。

(4)对象生命周期结束时,系统会自动调用析构函数。

下面是栈的部分代码:

typedef int DataType;
class stack
{
public:
	//构造函数
	stack(size_t capacity=3)
	{
		//需要开空间
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (_array == nullptr)
		{
			perror("maloc fail");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	~stack()//析构函数
	{
		//释放资源
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}
	void push(DataType x)
	{
		_array[_size] = x;
		_size++;
	}
private:
	DataType* _array;
	int _size;
	int _capacity;
};
int main()
{
	stack st;//自动调用构造

	st.push(1);
	st.push(2);
	return 0;//结束调用析构
}

(5)和构造函数类似,我们不写时,编译器自动生成的析构函数对内置类型不做处理,对自定义类型会调用它的默认析构函数。

(6)还有一点,我们显示写了析构函数,对于自定义类型,也会调用它本身的析构,也就是说无论写不写析构函数,自定义类型都会调用它本身的默认析构函数。

对于下面的代码,需要析构自定义类型。就要调用它自己的析构函数。

class Time
{
public:
	~Time()
	{
		cout << "析构Time" << endl;
	}

private:
	int _hour;
	int _minute;
	int _second;
	
};
class Date
{
private:
	int _year;
	int _month;
	int _day;

	Time _t;//自定义类型
};
int main()
{
	Date d1;
	return 0;
}

 

注:先调用的后析构,比如上面的Date类,对象d1,d2,先析构d1,再析构d2.

4,拷贝构造函数 

如果一个构造函数的第一个参数是自身类型的引用,且任何额外的参数都有默认值,那么这个构造函数就叫做拷贝构造函数。也就是说,拷贝构造函数是特殊的构造函数。

拷贝构造函数的特点:

(1)拷贝构造函数是构造函数的重载

(2)拷贝构造函数的第一个参数必须是类类型对象的引用,必须是引用的方式,传值的方式编译器会报错,因为语法逻辑上会引发无穷递归。(理由在后面)

class Date
{
public:
    Date(int year=1,int month=1,int day=1)//构造函数
    {
        _year = year;
        _month = month;
        _day = day;
    }
    Date(const Date& d)//拷贝构造
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void print()
    {
        cout << _year << "年" << _month << "月" <<_day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2024, 7, 15);//实例化
    d1.print();

    Date d2(d1);//用d1拷贝构造出d2,也可以写成 Date d2=d1
    d2.print();
}

(3)若未生成拷贝构造函数,编译器会自动生成默认拷贝构造函数。自动生成的拷贝构造函数对内置类型变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型会调用它的默认拷贝构造函数。

(4)为什么传参的时候必须是引用传参,传值传参不行,这就要提一下,c++规定:所有传值传参需调用拷贝构造函数。

class Date
{
public:
    Date(int year=1,int month=1,int day=1)//构造函数
    {
        _year = year;
        _month = month;
        _day = day;
    }
    Date(const Date& d)//拷贝构造
    {
        cout << "拷贝构造" << endl;
    }
    void print()
    {
        cout << _year << "年" << _month << "月" <<_day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
void func(Date d)
{

}
int main()
{
    Date d1;
    func(d1);//传值传参
    return 0;
}

这样就能看出,在传值传参时都会调用拷贝构造函数。

拷贝构造函数不能传值传参的原因:下图中,可以看出会无限递归下取。 

(5)像Date这样的类,没有指向什么空间资源,编译器生成的拷贝构造就够用了,也就是浅拷贝。而对stack栈这样的类,涉及到开辟空间,就需要我们自己实现,也就是深拷贝

栈的拷贝构造函数:

typedef int DataType;
class stack
{
public:
	//构造函数
	stack(size_t capacity=3)
	{
		//需要开空间
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (_array == nullptr)
		{
			perror("maloc fail");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	~stack()//析构函数
	{
		//释放资源
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}
	stack(const stack& st)//拷贝构造函数
	{
		//需要对_array所指向的资源,创建同样大的资源再拷贝
		_array = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (_array == nullptr)
		{
			perror("maloc fail");
			return;
		}
		memcpy(_array, st._array, sizeof(DataType) * _size);
		_size = st._size;
		_capacity = st._capacity;
	}
	void push(DataType x)
	{
		_array[_size] = x;
		_size++;
	}
private:
	DataType* _array;
	int _size;
	int _capacity;
};
int main()
{
	stack st1;//自动调用构造

	stack st2(st1);
	return 0;//结束调用析构
}

可以看出st1,st2拷贝成功, 并且是两个独立的空间。

若是使用编译器生成的默认拷贝构造函数,就是浅拷贝,就会使st1,st2指向同一块空间。

 5,赋值运算符重载

5.1运算符重载

(1)当运算符被用于类类型对象时,c++允许我们通过运算符重载指定新的含义。c++规定,类类型对象使用运算符时,必须转化成调用对应的运算符重载,若没有对应的运算符重载,编译器就会报错。

(2)运算符重载是具有特殊名字的函数,它的名字有operator和后面要定义的运算符构成,例如operator+,operator++等。

(3)如果一个重载运算符函数是成员函数时,则它的第一个运算对象默认传给隐士的this指针

因此运算符重载作为成员函数时,参数比运算对象少一个

(4)5个不能重载的运算符:.*      ::      sizeof      ?:     .

class Date
{
public:
    Date(int year=1,int month=1,int day=1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void print()
    {
        cout << _year << "年" << _month << "月" <<_day << "日" << endl;
    }
//private:
    int _year;
    int _month;
    int _day;
};
//放在全局
bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
int main()
{
    Date d1(2024, 7, 15);
    Date d2(2024, 7, 15);

    d1 == d2;//调用运算符重载,也可以写operator==(d1,d2)
    return 0;
}

可以看作就是一个普通的函数调用,但是这样写,有一个问题,我们的成员变量都必须写成共有的才行,不建议这样写,所以我们可以把他重载为成员函数, 代码如下:

class Date
{
public:
    Date(int year=1,int month=1,int day=1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    bool operator==(const Date& d2)//判断两个日期是否相等
    {
        return _year == d2._year && _month == d2._month && _day == d2._day;
    }
    void print()
    {
        cout << _year << "年" << _month << "月" <<_day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024, 7, 15);
    Date d2(2024, 7, 15);

    d1 == d2;//调用运算符重载,也可以写operator==(d1,d2)或者d1 .operator==(d2);
    return 0;
}

同时,这样写在传参的时候,会有一个隐士的this指针,可以少传一个参数。

注:如果全局和成员函数中有相同的运算符重载函数,优先调用成员函数中的。

5.2赋值运算符重载
class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //d1=d2
    Date& operator=(const Date& d2)
    {
        _year = d2._year;
        _month = d2._month;
        _day = d2._day;

        return *this;
    }
    //d1==d2
    bool operator==(const Date& d2)
     {
            return _year == d2._year && ._month == d2._month && _day == d2._day;
     }
    void print()
    {
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024, 7, 15);
    Date d2(2024, 7, 15);

    d1 = d2;//调用赋值重载拷贝

    //Date d3=d2;//调用拷贝构造
    //Date d3(d2);//调用拷贝构造
    return 0;
}

和拷贝构造函数不同,赋值重载函数是对两个已经存在的对象的赋值。而拷贝构造是用一个对象去初始化另一个对象。

注:

1,参数类型:const T&,使用引用可以提高效率。

2,返回值类型T&,同样提高效率,有返回值目的是为了支持来连续赋值

3,赋值运算符必须重载为成员函数,不能重载为全局函数。

4,用户没有实现时,编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝

注意,内置类型成员变量时直接赋值的,而自定义类型时需要调用自己的赋值运算符重载的。

6,取地址运算符重载

6.1const成员函数

(1)将const修饰的成员函数叫做const成员函数,const修饰成员函数,将const放在成员列表的后面。

class Date
{
public:
	Date(int year=1,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print() const
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//这里d1是非const,也可以调用const成员函数,是一种权限的缩小
	Date d1(2024, 7, 20);
	d1.print();

	const Date d2(2024, 7, 15);
	d2.print();
	return 0;
}

所以如果希望对象不被修改, 建议在函数后都加上const。

6.2取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。

class Date
{
public:
	Date(int year=1,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print() const
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	const Date* operator&()const//取地址运算符重载,const Date d2;&d2调用该重载
	{
		return this;
		//return nullptr;//当不想被取到地址时,可以随意返回一个地址
	}
	Date* operator&()//取地址运算符重载
	{
		return this;
		//return nullptr;//当不想被取到地址时,可以随意返回一个地址
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 7, 20);
	const Date d2(2024, 7, 15);

	cout << &d1 << &d2<<endl;
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值