C++面向对象(一)

0.前言:

C++中结构体不可以有成员函数。
【结构体大小计算回忆】
请添加图片描述
在64位下,指针大小为8字节。
a1:4,
a2:4
a3:4
指针:8
sum:20 => 24,因为需要成8的倍数。【对齐数】

1. 构造函数

  • 由来:C语言中struct经常忘记初始化:像栈一样给top位置和栈size
    所以C++加了构造函数,会自动初始化。
  • 特殊点:如果自己不手动写,编译器自己实现。对象实例化时自动调用。
  • 特点:
    最好写成public:类型。
    此外,C++成员变量命名风格喜欢在变量前加_

1.1带缺省的构造函数

  如下图,缺省和无参不可以同时存在,语法上无参和全缺省可以同时存在,因为构成了函数重载。因为如果创建Date d1;这样有二异性。它不知道调用缺省构造函数还是无参构造函数。
请添加图片描述

  • 改进:
    请添加图片描述
      也可以只传一两个【推荐全缺省或半缺省】

1.2 默认构造函数

  • 默认构造函数:特指编译器自动生成的无参构造函数,虽然编译器创建了,但是查看值,并没有被初始化。
    请添加图片描述
       1. 那么默认生成的是不是什么事情都没干?
       答:C++里面把类型分为两类:
    内置类型(基本类型如int、char、double、内置类型数组等),自定义类型【结构体、对象】。
    对于自定义类型的成员变量,它编译器会自己去调用该类型的构造函数。而内置类型,就不管。所以如下year、month、day,仍然是随机值。请添加图片描述
  • 注意点:
       如果你写了一个类的有参构造函数,那么系统将不提供无参构造函数,但是你最好写出来,不然如果你创建对象不给参数,就会报错。因为上面说了,系统默认给无参构造时是你自己啥构造函数都不写。为了避免错误,最好自己手动添加。**最建议你直接写一个全缺省的构造函数。**此外,如果是自定义类型有你手写的构造函数,它会自己去调用,如果你自己没写,它还会又深入去如上的A类中创建无参构造函数,但是A类中的a因为是int类型属于内置类型,又不会被初始化值。
    暴露了【C++缺点】:没有对内置类型和自定义类型统一处理。

此外注意:创建对象不给参数时,直接用对象名,不加括号。

2. 析构函数:资源释放

   不是完成对象的销毁,为了完成对象中的一些资源清理工作。比如:对指针在堆上申请的空间做释放。此外,一个类只有一个析构函数。
~Date()
{
//释放指针类资源,比如栈中a指针,会申请一片连续空间。;
free(a);
// 变量清0、该置0的置0
}

  • 编译器生成的默认析构函数和默认构造一样,自定义的类型会去调用它本身的析构函数【就是你类中的类属性,默认析构调用它的析构】。而内置类型就不会自动删除。

  • 虽然C++有缺点,但是也方便。方便就方便在类中的类属性成员变量,会自己直接调用类属性它的类的默认构造参数,直接就做完赋值了,比较方便。
    总结:没有资源需要清理,不用自己实现析构函数,其中对于自定义类型会调用该自定义类型的析构,内置类型不做处理。

3. this指针

  • 特点:
  1. this指针的类型: 类类型* const
  2. 只能在成员函数内部使用
  3. 对象中不存储this指针,this指针只是个“成员函数”的形参。当对象调用成员函数时,编译器自动把对象地址作为实参给了this形参,this形参实际是第一个成员函数的参数,它是隐藏起来的。所以对象中不存储this指针,且没必要存。
  4. 【注意:】可以为空,当你不输出或不使用对象成员变量时,显然可以不穿地址,所以this没必要接收。

4. 构造函数和析构函数的调用顺序:

请添加图片描述

  • 最先构造的,最晚析构,结合栈特点。
    所以以上选B。
  • 再看一个:
    请添加图片描述
    解析:
  1. 全局的C和static D,都存在静态内存区,在这个静态区内,也得符合栈顺序。
  2. A是在堆上申请开,和局部静态变量是同级的,A语句早于C语句,所以A先掉构造。然后是C,再D。且A、C都在数据段,这两个析构顺序符合栈特点
    最后析构顺序按照构造顺序相反。

请添加图片描述
请添加图片描述

5. 拷贝构造函数:复制对象

  • 特点:
  1. 拷贝构造函数与构造函数构成重载。
  2. 使用同类型的对象值去拷贝,加const防改变且用别名&防无限递归调用。简言之,参数类型MyClass必须加&
      解释2: 对象类型的参数都是临时变量,临时拷贝出来的,编译器自动调用拷贝构造函数。所以如果拷贝构造的参数是类,会无限递归。,所以一定记住:参数类型MyClass必须加&,也就是类的引用。
  • 使用场景:
    自定义类型用同类型对象做初始化,就是拷贝构造
  • 注意:
      1. 因为参数用了const&,起来别名,防止赋值写反导致先前对象被误改,所以习惯最前面加const。
       2. 如果没有显示定义,系统会生成默认的拷贝构造函数。
  1. 内置类型成员:也会完成按字节序的拷贝。:即按一个个字节去拷贝。按字节依次去拷贝。
  • 思考:
      那么要不要写拷贝构造,因为发现默认生成的也很够用。是不是都不需要写?
       答:不可以, 比如栈就不行,因为会做浅拷贝,栈2用栈1去拷贝复制,结果栈2析构,会把自己指向的空间释放,但是栈1析构已经释放了。所以浅拷贝导致它们指向的空间被析构两次,同一片空间,不能同时释放两次。且s2删、增,也会影响s2。所以需要深拷贝,而深拷贝需要我们自己写。
    但是对于日期Date这样的类可以用。但是栈不可以用。
    拷贝构造对内置类型,都只是做值拷贝或浅拷贝。有指向资源的【或说自定义类型的成员】,都不可以用系统默认生成的拷贝构造,且系统遇到了自定义类型的属性,会调用这个自定义成员属性它自己【自定义类型】的拷贝构造函数。(且被调用的类的拷贝构造函数一定是我们人为实现的,因为需要深拷贝的拷贝构造函数)。

【总结】:系统会自动生成默认拷贝构造函数,系统会对内置类型和自定义类型都会拷贝处理。但是对两者处理细节不一样,且和构造、析构都不一样。
特点

  1. 构造函数,它就是为了把当前对象的值做成和参数一样的对象。所以没有返回值
  2. 只有一个参数。是对象的引用
  3. 每个类都存在 拷贝构造函数,分浅拷贝和深拷贝。默认的拷贝构造函数是浅拷贝,会拷贝参数对象中同样的地址,所以有时候可能会影响一些操作,一个对象的改变影响到这个浅拷贝的对象,比如你改变值,或析构另外一个对象。深拷贝要自己写,且有时候必须要用深拷贝。

拷贝构造使用的代码举例

// 法1
class A1(2,1);
class A2(A1);
// 法2:
class A3 = A1;
// 调用
A3 = A2; 这是在

而最下面那一行,已经有了对象了,那个涉及的是赋值,而不是拷贝构造,且对象类型的右值,编译器会自动做=运算符重载。

  1. 函数返回值是对象时会返回临时变量,做一次拷贝构造。
  2. 形参是类时。

【回忆】:
** 插入:【size_t】类型是什么**
size_t:数组下标,无符号整数类型。
无符号不能保存负数,最低存到-1。

const修饰成员函数

  • 特点:
  1. 常函数:成员函数后面加const后称这个函数为常函数。如下:
    void Person::show() const
  2. 常函数内不可以修改成员属性。即不可以出现类似如下:
    void fun(){ this->a = val}; 这种改变成员变量值的赋值语句绝不可以。
  3. 成员变量声明加mutable,在常函数中依然可以修改。
  4. 声明和实现时,都需要加const。

【构造函数的初始化列表】

在调用构造函数之前,先会默认做初始化列表操作,这会才是在做定义。即开辟空间。而后的自动调用的构造函数,是在做初始化。

  • 使用场景:
  1. const 成员变量,因为常量必须在定义时初始化,之后再也不能改变。
  2. 引用成员变量
  3. 自定义类型的成员变量:不使用初始化列表会调用多次构造函数。
  • 注意:
    类中变量声明次序就是初始化列表中的初始化顺序。

  • 例题:类成员声明顺序就是构造化顺序。
    请添加图片描述
    因为a2声明先于a1,而a2构造列表中需要a1值,但是a1还没有,所以a2是随机值。a1是1。

  • 例题:
    请添加图片描述
    静态变量不是常量,静态变量必须在类外初始化,而const常量才是在初始化列表,所以

【回忆】变量的声明、定义、初始化

  1. 声明:声明变量a,不是定义,不给a分配内存空间。如下两种算声明。一般是带着extern的都叫声明。
    还有场景:函数在.h文件中做声明,而在.c文件中做定义和初始化。
extern int a; // 声明
在类中,写出类的成员变量和有哪些函数【不去实现】,也是声明。
  1. 定义:特指指给分配了空间。如下代码算声明a,也给a定义了。
int a; 
  1. 初始化:给初始值。

const修饰成员变量

特点

  1. 必须使用构造函数初始化列表方式做初始化,因为初始化列表处是在做定义的地方,即:在开辟空间了,而构造函数内部:在做初始化,即空间之前已经开辟好了。【要知道创建类在调用构造函数之前,先会默认做初始化列表操作,这会才是在做定义。即开辟空间】
  2. const成员变量需要在定义时就初始化。所以const成员变量必须在初始化列表时赋值。

const修饰对象

  1. const对象不能调用普通类的成员方法。
    请添加图片描述
      如上,对象调用成员函数,会传地址给隐藏的第一个形参this,而this的类型是:Date* const this,这代表着this指向的对象不能改变。在d1.Print()时,传&d1给this,Date传给 Date const this,没有问题,这只是保护起来了。
      但是d2.Print()传&d2,它的类型是: const Date*,在const后面,即指向的内容的成员变量也不能改变。即d2对象的成员变量只读,但是传给this后,this可以改变对象成员,是权限放大了,所以不可以。**造成d2不可以调用Print(),所以这里得使用const修饰成员函数。即之前的const成员变量。但是this是隐含的,不能显示加,所以我们直接加给()的后面,这样就不能改变*this所指向的对象的成员变量。**

这里注意,在后面的const,或const一行没有,不涉及权限缩小。比如上面const Date this就是,且所有this指针,前面的const都是保护this不变,没有权限缩小。如下也是。*

const int x = a;
  • 例题:
    请添加图片描述
  • 解析:
    这是一个const成员函数,对象值不能改变,所以函数调用后,对象值无变化。

运算符重载的应用: 实现Date:

class Date

{

public:

// 获取某年某月的天数

int GetMonthDay(int year, int month);



  // 全缺省的构造函数

Date(int year = 1900, int month = 1, int day = 1);



  // 拷贝构造函数

// d2(d1)

Date(const Date& d);

   

  // 赋值运算符重载

// d2 = d3 -> d2.operator=(&d2, d3)

Date& operator=(const Date& d);



  // 析构函数

~Date();



  // 日期+=天数

Date& operator+=(int day);



  // 日期+天数

Date operator+(int day);



  // 日期-天数

Date operator-(int day);



   // 日期-=天数

Date& operator-=(int day);



  // 前置++

Date& operator++();



  // 后置++

Date operator++(int);



  // 后置--

Date operator--(int);



  // 前置--

Date& operator--();



  // >运算符重载

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);



  // 日期-日期 返回天数

int operator-(const Date& d);

private:

int _year;

int _month;

int _day;

};
  1. 重载">“等比较运算符,这个建议从”=="开始,然后互相使用,减少代码的重复率。利用已有的重载运算符,去简写其它的。
  2. 重载"+“和”+=" 、 “-“和”-=”。要注意区分"+=“和”+",+和-不需要变化自己,而+=和-=需要。
    +=:日期加天数:天数超这个月就一直加。月,然后月超12就加年。
    且最后返回的是当前值,所以函数返回值是引用类型、返回*this。
    +:本身不能加day,所以用临时变量。而**用临时变量,就别引用。**用了引用就出错了,因为那个date不存在了。
Date& Date::operator+=(int day)
{
	_day += day;
	int tmp = (*this).GetMonthDay(_year, _month);
	while (_day > tmp)
	{
		_day -= tmp;
		_month++;
		if (_month > 12)
		{
			_month = 1;
			_year++;
		}
		tmp = (*this).GetMonthDay(_year, _month);
	}
	return *this; 
}


Date Date::operator+(int day)
{
	Date d1(*this);
	d1._day += day;
	return d1;
}
  1. 重载++:分前置和后置:写法上为了区分:后置加了默认参数int,写为:operator++(int)。创始人主要是为了加个参数做区分。编译的时编译器会多传一个0。
    其中:前置++,要返回自己,所以用引用类型传即可。
    而后置++,应该返回自己的原来,所以用拷贝的临时变量,所以返回普通类类型。
    分析:前置不使用拷贝构造函数。而后置++用两次拷贝构造,所以推荐用前置++。
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

Date& Date::operator++(int)
{
	Date d = *this;
	*this += 1;
	return d;
}

  1. 重载"-“、”-=":
    -、-=
    这里调用GetMonthDay时写_year、_month就好,加*this也行,但是难看。

  2. 两个日期天数之差
    有以下几种思路:
    a. 求和公元0年第1天,再作差。
    b. 让小日期加到大日期

运算符重载

  • 为了增强代码可读性,让自定义类型对象支持运算符。这也是STL的关键利器,此外: “::”、“.*”、“?:”、"."不可以重载。
    都是需要自己重载。
  • 应用:
    比如:日期对象之间做加减。
  • 注意:
  1. 返回值类型需要自己考虑,三操作数不允许重载,一般是单、双。
  2. 有几个操作数,就有几个参数。(建议传引用)因为用拷贝构造,对象类型参数必须用引用。否则无限递归且加const使得更安全,如果下面写错了会报错。
  3. 运算符重载上要使用成员变量,而成员变量总是私有那么如何提供?
    1. private变public
    2. 造get方法
    3. 友元:比public好,但会稍破坏封装。
    4. 成员函数法:把opeartor放到类中,参数减少一个,使opeartor变为成员函数。
      变成成员函数后,函数多了个 隐藏参数:this指针。
  • 使用:
    cout时候要注意:
    cout<< d1 > d2 << endl;
    这样要小心流顺序,<<优先级高于>,所以为了他俩能计算,把d1和d2括起来。
  • d1>d2,编译器会自动转为:operator>(d1, d2) ,但是为什么不支持直接自写:opeartor(>d1,d2)?
    答:开头说了:为了可读性。显然,直接写符号更易懂看着舒服。

:: 、sizeof、? . 这四个不要重载。 .*也不能重载。其中点用来做对象访问成员的。
C++必须写返回值类型。

全局和类上都写,会不会重定义:即非要在外面写operator以">"为例,写两个参数,不会编译报错,因为构成重载。一般建议写成员中的。类中的函数,不是属于全局,属于类域。且大部分、习惯上, 都把运算符重载放在类中,所以优先去类中找这也寻找本身更有优势。
最后发现调用成员,优先去成员中找。

赋值运算符重载:

使用场景:比如日期类Date,想让Date d1和d3相等。实现opeartor赋值。
不需要有返回值。

  • 为什么使用引用类型返回就会减少拷贝构造函数的调用?
    比如: d1=d2=d3,如果此时使用如下操作符重载,会调用两次拷贝构造函数。
    前情概要:对象变量做返回值,会调用拷贝构造函数,不会返回某个对象它本身,除非用别名&引用,才返回这个东西,且不会调用拷贝构造函数
    前情概要:对象变量做返回值,会调用拷贝构造函数,不会返回某个对象它本身
    前情概要:对象变量做返回值,会调用拷贝构造函数,不会返回某个对象它本身
    Date operator=(const Date & d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    
    		return *this;
    	}
    

请添加图片描述
因为返回了一个Date类型对象,是临时的,它本质通过了拷贝构造。所以调用拷贝构造在返回时。

而使用引用做返回,则一次都不使用。
Date& operator=(const Date & d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

请添加图片描述
当重载等号以引用返回,返回的是已经存在的对象,一直用的是等号重载。

此外,极端情况自己给自己赋值,就可以不用处理了,直接判断后跳过。
且用地址比较,不要去比较对象。
this指针是地址,&d也是地址。如果y用:*this!=d,还要对!=做重载,因为两边是两个对象, 不能直接比。

此外,不写赋值运算符重载,编译器会自己生成一个。
此外,不写赋值运算符重载,编译器会自己生成一个。
此外,不写赋值运算符重载,编译器会自己生成一个。
编译器默认生成的赋值重载,根拷贝构造做的事情类似。

  1. 内置类型成员,会完成字节序值拷贝——浅拷贝。
  2. 自定义类型成员,会调用自定义类型的=重载。

如上,如果是队列用两个栈实现,那么这个队列不需要自己实现赋值重载,因为内含Stack拥有,且它自己没有一些连续内存的地址空间。但是如果栈本身,就需要自己实现赋值重载,因为这个自定义类型中使用了指针,需要手动释放。
总结,默认生成的四个默认成员函数,构造和析构处理机制是类似的。
拷贝构造和赋值重载处理机制是基本类似的。

  • 注意点:

Date d5 = d1;是拷贝构造,不是赋值重载。

匿名对象
比如:创建一个对象只是为了传参。

一般对象声明周期在当前函数。
匿名对象生命周期只在当前这一行。

如果直接传匿名对象,拷贝构造会少调用一次。

【取地址&运算符重载】

默认成员函数:不写,也可以对这是取到地址,所以说它是默认重载的。

赋值运算符重载

  1. 默认运算符“=”只是实现了"浅层复制"功能。
  2. 重载赋值运算符
  3. 赋值=运算符重载只能在类内重载

下列关于赋值运算符“=”重载的叙述中,正确的是( )
A.赋值运算符只能作为类的成员函数重载
B.默认的赋值运算符实现了“深层复制”功能
C.重载的赋值运算符函数有两个本类对象作为形参
D.如果己经定义了复制拷贝构造函数,就不能重载赋值运算符
根据上面引用,知道A对,BC错,而D:可以有拷贝构造,也可以重载赋值=运算符。

重载运算符:流插入<<和提取>>【友元中】

流重载是:双操作数的操作符重载
本质是:对象对 流对象的操作。
所以有两个操作数,必须第一个是操作数,第二个参数是右操作数。

所以不能重载成重载函数。
如果还想cout到左边,不能实现成员函数,所以就做一个全局,不是成员函数,就不区分左右了,因为成员函数,第一个必须是操作数,默认是类类型的,也就是那个对象。
【改进】:写全局运算符重载。不写类运算符重载。

  • 【面试题】:
    哈希冲突如何解决?
    散列法、双重哈希等等。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值