概述
???? ???? 在上期的内容中,留下了一些面试题,小伙伴们有认真思考吗?今天在下来和大家一起细细讨论,共同学习。同时今天的主题是C++类和对象(中篇),也是入门的重中之重,希望小伙伴们能够从中有所收获。
文章目录
- 一、思考题
- ????1、 如何让结构体按照指定的对齐参数进行对齐?
- ????2、 如何知道结构体中某个成员相对于结构体起始位置的偏移量?
- ????3、 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景?
- ????4、this指针存在哪里?
- ????5、this指针可以为空吗?
- 二、类的六个默认成员函数
- ???? 1、构造函数
- ???? 2、析构函数
- ???? 3、拷贝(复制)构造函数
- ???? 4、赋值操作符重载
- ???? 5、取地址操作符重载
- ???? 6、const修饰的取地址操作符重载
- const修饰成员函数
一、思考题
????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修饰。
如果小伙伴们觉得有所收获的话,不妨收藏一波,谢谢老铁们的支持!!
最后
以上就是开心网络为你收集整理的C++入门超详解(3)一、思考题二、类的六个默认成员函数的全部内容,希望文章能够帮你解决C++入门超详解(3)一、思考题二、类的六个默认成员函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复