C++初阶 —— 类与对象(二)

目录

一,构造函数

二,析构函数

三,拷贝构造函数

四,赋值运算符重载

五,日期类的实现

六,const成员

七,取地址及const取地址操作符重载


类的六个默认成员函数!

任何一个类,在不写的情况下,都会生成6个默认成员函数;

class Date{};

一,构造函数

  • 构造函数是一种特殊的成员函数,名字和类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次;
  • 构造函数主要任务并不是开空间创建对象,是初始化对象;

特性

  • 函数名与类名相同;
  • 无返回值;
  • 对象实例化时,编译器自动调用;
  • 可以重载;
  • 如类没有显示定义构造函数,C++编译器会自动生成一个无参的默认构造函数,用户显示定义则编译器不在生成;
  • 无参构造函数和全缺省构造函数,都称为默认构造函数,且默认构造函数只能有一个;即不传参数的构造函数,都是默认函数;推荐使用全缺省的构造函数;
  • 编译器自动生成的默认构造函数偏心处理,内置类型不初始化、自定义类型调用其自身的构造函数;

注:成员变量,建议加前缀或后缀标识,与形参加以区分;

//无参和有参构成函数构成重载
class Date
{
public:
	//无参构造函数,默认构造函数
	Date() 
	{}
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1; //实例化时调用无参构造函数
	d1.Display();
	Date d2(2021, 10, 23); //实例化时调用带参构造函数
	d2.Display();
	return 0;
}
//默认构造函数只能有一个
//调用默认构造函数时会造成歧义,不调用没关系
class Date
{
public:
	//无参默认构造函数
	Date()
	{
		_year = 1921;
		_month = 7;
		_day = 1;
	}
	//全缺省默认构造函数
	Date(int year = 2021, int month = 10, int day = 23)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

注:C++把类型分为内置类型和自定义类型

  • 内置类型/基本类型,即语法已经定义好的类型,如int/char等;
  • 自定义类型,即自己定义的类型,如struct/class/union等;
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	//内置类型(基本类型)
	int _year;
	int _month;
	int _day;
	//自定义类型(类)
	Time _t;
};

int main()
{
	Date d; //会对自定义类型_t调用默认无参函数
	return 0;
}
class Date
{
public:
	Date()
	{
		_year = 2020;
	}
private:
    //默认构造函数的缺省值 C++11,即实例化时调用构造函数未初始化此变量,赋此值
	int _year = 0; //2020
	int _month = 1; //1
	int _day = 1; //1
};

局部优先原则

//局部优先原则
class Date
{
public:
    Date(int year)
    {
        year = year;
    }
private:
	int year;
};

int main()
{
	Date d(1); //成员变量year还是随机值,可this->year指定
	return 0;
}

二,析构函数

  • 与构造函数相反,析构函数不是完成对象的销毁,局部对象的销毁工作是由编译器完成的;
  • 对象在销毁时,会自动调用析构函数,完成类的一些资源清理工作;

特性:

  • 析构函数名是在类名前加字符 
  • 无参数,无返回值;
  • 一个类有且只有一个析构函数,若未显示定义系统会自动生成默认的析构函数;
  • 对象生命周期结束时,C++编译器会自动调用;
  • 编译器自动生成的默认析构函数偏心处理,内置类型不处理、自定义类型调用其自身的析构函数;
class SeqList
{
public:
	//构造函数
	SeqList(int capacity = 0)
	{
		_pData = (int*)malloc(capacity * sizeof(int));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	//析构函数
	~SeqList()
	{
		if (_pData)
		{
			free(_pData);
			_pData = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _pData;
	size_t _size;
	size_t _capacity;
};
class String
{
public:
	//构造函数
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
        assert(_str);
		strcpy(_str, str);
		cout << _str << endl;
	}
	//析构函数
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};

class Person
{
private:
	String _name;
	int _age;
};

int main()
{
	Person P;
	return 0;
}

三,拷贝构造函数

  •  拷贝构造函数,也称复制构造函数,会拷贝复制一个同类对象;
  • 只有单个形参,该形参是对本类类型对象的引用(一般使用const修饰,但并不限),在用已存在的类类型对象创建新对象时由编译器自动调用; 

特征:

  • 拷贝构造函数是对构造函数的一个重载;
  • 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用;
  • 若未显式定义,系统会默认生成拷贝构造函数;
  • 默认的拷贝构造函数对象按内存存储字节序完成拷贝(叫做浅拷贝或值拷贝),不区分基本类型和自定义类型都会处理;
    • 内置类型,类似memcpy函数;
    • 自定义类型,调用其自身的拷贝构造函数;

注,如成员变量是指针类型时,在完成拷贝(默认浅拷贝)构造后,程序销毁时会崩溃(对指针清理两次),应使用深拷贝构造;

以下形式会调用拷贝构造

  • 传对象值传参,按值传递时会创建参数副本;
  • 以对象值返回,按值返回时会创建对象副本;
  • 以对象作为参数初始化对象;
class Date
{
public:
	//构造函数
	Date(int year = 2021, int month = 10, int day = 23)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1); //书写形式也可为:Date d2 = d1;
	return 0;
}
class Date
{
public:
	//构造函数
	Date(int year = 2021, int month = 10, int day = 23)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//不是要拷贝构造函数,使用指针也可以,但不方便
	Date(Date* p)
	{
		_year = p->_year;
		_month = p->_month;
		_day = p->_day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(&d1);
	return 0;
}
//无拷贝构造函数,默认生成
class Date
{
public:
	//构造函数
	Date(int year = 2021, int month = 10, int day = 23)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1); //浅拷贝或值拷贝
	return 0;

}
//无拷贝构造函数,默认生成
class String
{
public:
	//构造函数
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		assert(_str);
		strcpy(_str, str);
	}
	//析构函数
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};

int main()
{
	String s1;
	String s2(s1); 
    //无拷贝构造函数,调用默认生成的拷贝构造函数,内置类型浅拷贝;
    //生命周期结束时,调用析构函数,s2释放空间后,s1又接着释放,会造成重复释放,系统会报错;
    //需将浅拷贝,改为深拷贝;
	return 0;
}

四,赋值运算符重载

  • 运算符默认是给内置类型使用的,自定义类型需运算符重载,即需自己定义实现运算符行为;
  • 为了增强代码的可读性,C++引入了运算符重载,其是具有特殊函数名的函数,有返回值类型,函数名,及参数列表,其返回值类型与参数列表与普通函数类似;
  • 函数原型:返回值类型 + operator + 运算符 (参数列表) { 重载操作 }
  • 函数名:operator + 运算符

注:

  • 不能通过连接其他符号来创建新的操作符,如operator@;
  • 重载操作符必须有一个类类型或枚举类型的操作数;
  • 用于内置类型的操作符,其含义不能改变,即符号和内容含义一致;
  • 作为类成员的重载函数时,其形参看起来比操作数数目少一个成员函数的操作符,有一个默认的形参this,限定为第一个形参;
  • .*::sizeof?:以上5个运算符不能重载;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
//引用成员变量,不可为private
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;
	Date d2;
    //正常调用方式为,operator==(d1,d2),但可读性很差,还不如写一个EqualDate函数
	cout << (d1 == d2) << endl; 
	return 0;
}
class Date
{
public:
    //构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //运算符重载
    //bool operator == (Date* this, const Date& d2)
    bool operator == (const Date& d2)
    {
	    return _year == d2._year
		    && _month == d2._month
		    && _day == d2._day;
    }
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
    //类似d1.operator==(d2),即编译器翻译为d1.operator==(&d1,d2)
	cout << (d1 == d2) << endl;
	return 0;
}

赋值运算符重载

  • 参数类型,const 类&;
  • 返回值,类&;
  • 检查是否自己给自己赋值;
  • 返回*this;
  • 一个类如没有显示定义赋值运算符重载,编译器会自动生成,完成对象按字节序的值拷贝,类似拷贝构造函数;
    • 内置类型,值拷贝;
    • 自定义类型,调用其自身的赋值运算重载函数;
class Date
{
public:
	//构造函数,无则默认会自动生成
	Date(int year = 1900, 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;
	}
	//赋值运算符重载,无则默认会自动生成
	Date& operator = (const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2020, 2, 2);
	Date d3(2021, 3, 3);
	d1 = d2 = d3;
	return 0;
}
//无赋值运算符重载,默认生成
class String
{
public:
	//构造函数
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		assert(_str);
		strcpy(_str, str);
	}
	//析构函数
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};

int main()
{
	String s1("hello");
	String s2("world");
	s1 = s2;
    //无赋值运算符重载,调用默认赋值运算符重载函数,内置类型浅拷贝;
    //生命周期结束时,调用析构函数,s2释放空间后,s1又接着释放,会造成重复释放,系统会报错;
    //需将浅拷贝,改为深拷贝;
	return 0;
}

五,日期类的实现

各种运算符重载的实现https://gitee.com/napoleoncode/start-code/tree/master/C++/Date

class Date
{
public:
	//获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
			return 29;
		return monthDays[month];

	}
	//构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		//判断合法性
		if (_year < 0 || (_month <= 0 || _month >= 13) || (_day <= 0 || _day > GetMonthDay(_year, _month)))
		{
			cout << _year << "/" << _month << "/" << _day << "->";
			cout << "非法日期" << endl;
		}
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	//运算符重载函数
	//日期 += 天数
	Date& operator += (int day);
	//日期 -= 天数
	Date& operator -= (int day);
	//日期 + 天数
	Date operator + (int day);
	//日期 - 天数
	Date operator - (int day);

	//前置++
	Date& operator ++ ();
	//后置++
	Date operator ++ (int);
	//前置--
	Date& operator -- ();
	//后置--
	Date operator -- (int);

	//日期 - 日期 (返回天数)
	int operator - (const Date& d);

	// > 运算符重载
	bool operator > (const Date& d);
	// >= 运算符重载
	bool operator >= (const Date& d);
	// < 运算符重载
	bool operator < (const Date& d);
	// <= 运算符重载
	bool operator <= (const Date& d);
	// == 运算符重载
	bool operator == (const Date& d);
	// != 运算符重载
	bool operator != (const Date& d);

private:
	int _year;
	int _month;
	int _day;
};

六,const成员

const 修饰类的成员函数

  • 将const修饰的类成员函数称为const成员函数;
  • 其实际修饰的是该成员函数隐含的this指针;
  • 表明在该成员函数中不能对类的成员进行修改;

注:

  • const成员函数,const对象可调用,非const对象也可调用;
  • 只针对只读型的成员函数,对修改型的成员函数不可加,如operator += ()等;
class Date
{
public:
	//构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Display(Date* this)
	void Display()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d(2020, 1, 1);
    //d.Display(&d)
	d.Display(); //报错,因为权限放大,void Display()const函数加const即可
}
//日期类,const修饰的成员函数
class Date
{
public:
	void Display()const
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

//编译器翻译为
class Date
{
public:
	void Display(const Date* this)
	{
		cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

七,取地址及const取地址操作符重载

  • 这两个默认成员函数,一般不用重新定义,编译器会自动生成;
  • 只有特殊情况,才需要重载,如想获取到指定内容;
class Date
{
public:
	//取地址操作符重载
	Date* operator&()
	{
		return this;
	}
	//const取地址操作符重载
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

  • 传值传参传值做返回值的过程中,在一个表达式调用连续步骤中,构造、拷贝构造会被编译器优化合并;
class Date
{
public:
	//构造函数
	Date()
	{
		cout << "Date()" << endl;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		cout << "Date(Date& d)" << endl;
	}
    //赋值运算符重载
	Date& operator=(const Date& d)
	{
		cout << "operator=" << endl;
		return *this;
	}
};
Date f()
{
	Date d;
	return d;
}
int main()
{
	//d拷贝构造给临时变量,临时变量在拷贝构造给ret1,两次拷贝构造
	//但编译器会优化,直接将d拷贝给ret1,一次拷贝构造
	// d -> tmp -> ret1 => d -> ret1
	Date ret1 = f();

	Date ret2;
	//d拷贝构造给临时变量,临时变量在赋值重载给ret2,1次拷贝构造
	// d -> tmp = ret2 
	ret2 = f();
	return 0;
}
Date f(Date d)
{
	return d;
}
int main()
{
	Date ret;
	//两个拷贝构造,Date d = ret,Date tmp = d;
	f(ret);
	cout << endl;

	//Date()是匿名对象,生命周期只在这一行
	//Date()先构造,在拷贝构造给d,但编译器优化为只有一个构造
	f(Date());
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在面向对象的编程中,C语言并不直接支持和抽象的概念。引用中提到,final关键字用来修饰方法,表示该方法不能在子中被覆盖。而abstract关键字用来修饰抽象方法,表示该方法必须在子中被实现。然而,在C语言中,没有对应的关键字来实现和抽象的概念。 相反,C语言通过结构体来模拟的概念。结构体是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。通过结构体,我们可以将相关的数据和功能组合在一起。然而,C语言中的结构体不支持继承和多态等面向对象的特性。 在C语言中,我们可以使用函数指针来模拟抽象和接口的概念。函数指针可以指向不同的函数,通过使用函数指针,我们可以实现多态性,即在运行时根据函数指针指向的具体函数来执行不同的操作。 综上所述,C语言并不直接支持面向对象中的和抽象的概念,但可以使用结构体和函数指针来实现似的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [面向对象——对象](https://blog.csdn.net/shouyeren_st/article/details/126210622)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [面向对象编程原则(06)——依赖倒转原则](https://blog.csdn.net/lfdfhl/article/details/126673771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值