C++入门超详解(3)

在这里插入图片描述
😇 😇 在上期的内容中,留下了一些面试题,小伙伴们有认真思考吗?今天在下来和大家一起细细讨论,共同学习。同时今天的主题是C++类和对象(中篇),也是入门的重中之重,希望小伙伴们能够从中有所收获。

一、思考题

🍔1、 如何让结构体按照指定的对齐参数进行对齐?

✈️ ✈️ 使用#pragma pack()设置默认对齐数,括号里面设置的是对齐数

代码⬇️ ⬇️ :

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
#pragma pack(1)
class A
{
	char a;
	int b;
};
int main() {
	//结果为5
	cout << sizeof(A) << endl;
	return 0;
}

当我们不设置默认对齐数时,结果⬇️ ⬇️ :

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
//#pragma pack(1)
class A
{
	char a;
	int b;
};
int main() {
	//结果为8
	cout << sizeof(A) << endl;
	return 0;
}

🍔2、 如何知道结构体中某个成员相对于结构体起始位置的偏移量

✈️ ✈️ 使用offsetof(struct name,member name),可以直接计算出偏移量
头文件为<stddef.h>。
⬇️ ⬇️

#include<iostream>
#include<stddef.h>
using std::cin;
using std::cout;
using std::endl;
//#pragma pack(1)

class A
{
public:
	char a;
	int b;
};

int main() {
	//结果为8
	cout << sizeof(A) << endl;
	//结果为4
	cout << offsetof(class A,b) << endl;

	return 0;
}

🍔3、 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景?

大小端字节序的来历(摘自《深入理解计算机系统 第三版》)
🌈 术语“little endian(小端)”和“big endian(大端)”出自Jonathan Swift的《格列佛游记》(Gulliver’s Trabels)一书,其中交战的两个派别无法就应该从哪一端(小端还是大端)打开一个半熟的鸡蛋打成一致。就像鸡蛋的问题一样,选择何种字节顺序没有技术上的理由,因此争论沦为关于社会政治论题的争论。

🌈 一下是Jonathan Swift在1726年关于大小端之争历史的描述:

🌈 “…下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋是碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极为反感。历史告诉我们,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻救避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派的任何人不得做官。”(此段译文摘自网上蒋剑锋译的 《格列佛游记》第一卷第4章。)

🌈 在他那个时代,Swift是在讽刺英国(Lilliput)和法国(Blefuscu)之间的持续的冲突。Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序,后来这个术语被广泛接纳了。

大小端模式
大端模式:即数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中。
小端模式:即数据的低位保存在内存的低地址中,数据的高位保存在内存的高地址中

🌻 那我们如何写一个程序来判断某台机器是大端还是小端呢?
思路:存入一个多字节数据,通过将其转换成char后,判断输出.
🌴 方法1:直接判断法
🌴 方法2:联合判断法

代码⬇️ ⬇️ :

#include<iostream>
using std::cin;
using std::cout;
using std::endl;

union A
{
	int x;
	int y;
};

//直接判断法
int check_sys1()
{
	int i = 1;
	return *(char*)&i;
}

//联合判断法
int check_sys2()
{
	union A a;
	//int
	a.x = 1;
	return a.y;
}

int main() 
{
	if (check_sys1())
		cout << "小端" << endl;
	else
		cout << "小端" << endl;

	if (check_sys2())
		cout << "小端" << endl;
	else
		cout << "小端" << endl;
	return 0;
}

用途:在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。


🍔4、this指针存在哪里?

✈️ ✈️ 其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。
类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。


🍔5、this指针可以为空吗?

✈️ ✈️ 可以为空。调用函数的时候,如果函数内部不需要使用this指针(即解引用之类的操作),那么this指针可以为空。


上期的思考题到这结束了,现在我们开始今天的正题 😎 😎 :

在这里插入图片描述

二、类的六个默认成员函数

🌻 在C++中引入了六个默认成员函数,引入的目的是什么呢?

当我们用C去写数据结构里面的顺序表、链表的时候,在打印插入等一系列操作前,我们都要对它们进行初始化,同时在结束的时候要销毁它们。
但是,你在使用栈和队列的时候是不是经常忘记初始化以及要销毁呢?

一旦发生,可能会造成难以挽回的损失(数组越界、内存泄漏等)。所以C++为了规避这种问题,提高程序员们的效率,就引入了六个默认成员函数。

🍕 1、构造函数

当我们创建一个对象时,如果每次对象都要经过先创建在赋值,未免显得太过麻烦,C++引入的构造函数就可以很好的在创建对象的同时给对象初始化。

🍒 构造函数:主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。在一个类可以有多个构造函数(构造函数重载) ,可根据其参数个数的不同或参数类型的不同来区分它们。

代码⬇️ ⬇️ :

Date(int year = 0,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
		//判断构造函数是否调用
		cout << "Date(int year = 0,int month=1,int day=1)" << endl;
	}

🍉 🍉 几点说明
🌵 当类中没有显示构造函数时,编译器会自动生成无参默认构造函数
🌵 默认构造函数指的是创建对象时不传递参数就可以调用的构造函数。包括了三种类型:
(1)全缺省的构造函数
(2)无参的构造函数
(3)系统默认生成的构造函数

🌵 构造函数的函数名和类名相同。
🌵 构造函数无返回值。
🌵 当类比较复杂时,一个构造函数通常无法满足初始化的要求,所以用多个构造函数构成重载。
🌵 构造函数可以显示调用,且只能用于初始化时。
🌵 系统默认生成的构造函数对内置类型不做处理(但是会调用),只会对自定义类型进行处理。


🍕 2、析构函数

🍒 析构函数:析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。当对象结束其生命周期,如果对象所在的函数已调用完毕时,系统自动执行析构函数。

🍉 🍉 几点说明
🌵 当类中没有显示析构函数时,C++编译器会自动生成无参默认构造函数
🌵 析构函数的函数名和类名相同,在函数名前面要加上位取反符“~”
🌵 析构函数无返回值。
🌵 析构函数不能够重载。(析构函数只有一个,而且没有参数)
🌵 系统默认生成的析构函数对内置类型不做处理(但是会调用),只会对自定义类型处理


🍕 3、拷贝(复制)构造函数

🍒 拷贝构造函数:只有单个形参,形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

代码⬇️ ⬇️ :

	//拷贝构造函数(构造函数的重载)
	Date (const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		//判断拷贝构造函数是否调用
		cout <<"Date (const Date& d)" << endl;
	}

🍉 🍉 几点说明
🌵 当类中没有定义拷贝构造函数时,C++编译器会默认生成拷贝构造函数。
🌵 拷贝构造函数是构造函数的重载
🌵 拷贝构造函数有且只有一个形参,形参是对本类类型对象的引用(用const修饰),拷贝构造函数无返回值
🌵 拷贝构造函数调用的四种情况:
1、用已创建的对象拷贝生成新对象。
2、对象以值传递的方式传给函数的参数。
3、对象以值传递的方式进行返回。
4、编译器生成一个临时对象时。

🌵 编译器默认生成的拷贝构造函数只能完成浅拷贝(值拷贝),若要完成深拷贝就要自定义拷贝构造函数

注意❗️ ❗️

🌻 为什么拷贝构造函数的参数要传引用?
这里就要谈到调用拷贝构造的四种调用形式了。如果定义值传递的拷贝构造函数(当然编译器不能通过,这里假设可以通过),进行值传递的过程中,会创建一个临时变量,实参将值赋给临时变量后,再通过临时变量把值传递给形参,但是临时变量的创建也是通过拷贝构造函数进行的,这样就会陷入一个无穷递归错误,而传引用就不会创建临时变量了,直接规避了递归错误。所以拷贝构造函数的参数只能传引用。
对象以值传递的方式都会创建临时变量,从而调用拷贝构造函数。

🌻 浅拷贝与深拷贝有什么区别?
浅拷贝就是复制原有对象的属性,但是如果是指针的类型,光复制指针是没用的,复制后两个指针指向同一片空间,这是不符合我们的需求的,我们需要重新开辟空间,这时候就只能通过深拷贝来完成了。深拷贝会创建一个一模一样的对象,新对象和旧对象不会共享内存,相互影响。

判断是否调用三种成员函数(构造、析构、拷贝构造)⬇️ ⬇️ :

#include<iostream>
using std::endl;
using std::cin;
using std::cout;

class A
{
public:
	A(int x = 0)
	{
		_x = x;
		//判断构造函数是否调用
		cout << "A(int x = 0)" << endl;
	}

	~A()
	{
		//判断析构函数是否调用
		cout << "~A()" << endl;
	}

	A(const A& a)
	{
		_x = a._x;
		//判断拷贝构造函数是否调用
		cout << "A(const A& a)" << endl;
	}

	void Print()
	{
		cout << _x << endl;
	}

private:
	int _x;
};

int main()
{
	A a;
	A aa(a);
	a.Print();
	return 0;
}

在这里插入图片描述


🍕 4、赋值操作符重载

🍒 为了增强代码的可读性,C++中可以对操作符实现重载,使操作符不仅仅可以对内置类型使用,同时也可以对自定义类型使用。

代码⬇️ ⬇️ :

	//日期加减天数,返回日期
	Date& operator+=(int day);
	Date& operator-=(int day);
	Date operator-(int day);
	Date operator+(int day);
	//日期-日期,返回天数
	int operator-(const Date& d);

	//日期自增(自减),返回日期
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
	
	//运算符重载
	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);

🍉 🍉 几点说明
🌵 操作符重载关键字为”operator“,函数名为operator加上操作符。
🌵 不能通过连接其他符号来创建新的操作符:比如operator@ 。
🌵 重载操作符必须有一个类类型或者枚举类型的操作数
🌵 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义。
🌵 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参。
🌵 .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。


🍕 5、取地址操作符重载

🍒 (不做详细介绍,作用不大)

作用:让别人取到指定的地址。⬇️ ⬇️

	Date* operator&()
	{
		return this;
	}

🍕 6、const修饰的取地址操作符重载

🍒 (不做详细介绍,作用不大)⬇️ ⬇️

	const Date* operator&() const
	{
		return this;
	}

这里我们谈谈const。

const修饰成员函数

const修饰的类成员函数称为const成员函数。const成员函数中,实际上const修饰的是成员函数隐含的this指针,保证了在成员函数中对象的成员不被修改。(const 放在成员函数的后面表示修饰的是对象的this指针)

思考
🌻 1、const对象可以调用非const成员函数吗?
🌻 2、非const对象可以调用const成员函数吗?
🌻 3、const成员函数内可以调用其它的非const成员函数吗?
🌻 4、非const成员函数内可以调用其它的const成员函数吗?

🍎 1、const对象可以调用非const成员函数吗? 不可以⬇️ ⬇️

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;

class Date
{
public:
	void Print()
	{
		cout << _year  << "   "
			 << _month << "   " 
			 << _day   << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	const Date d1;
	//编不过去
	d1.Print();
	return 0;
}

🍎 2、非const对象可以调用const成员函数吗? 可以⬇️ ⬇️

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;

class Date
{
public:
	void Print()const
	{
		cout << _year  << "   "
			 << _month << "   " 
			 << _day   << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	Date d1;
	//可以编过去
	d1.Print();
	return 0;
}

🍎 3、const成员函数内可以调用其它的非const成员函数吗? 不可以⬇️ ⬇️

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;

class Date
{
public:
	void Print()
	{
		cout << _year  << "   "
			 << _month << "   " 
			 << _day   << endl;
	}
	//编不过去
	void Test()const
	{
		Print();
	}

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

int main() {
	return 0;
}

🍎 4、非const成员函数内可以调用其它的const成员函数吗? 可以⬇️ ⬇️

#include<iostream>
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;

class Date
{
public:
	void Print()const
	{
		cout << _year  << "   "
			 << _month << "   " 
			 << _day   << endl;
	}
	//可以编过去
	void Test()
	{
		Print();
	}

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

int main() {
	return 0;
}

🎂 🎂 总结:(非)const成员函数调用其它的成员函数,其本质上还是关于this指针是否被const修饰。


如果小伙伴们觉得有所收获的话,不妨收藏一波,谢谢老铁们的支持!!在这里插入图片描述

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值