C++语法(5)——类和对象的补充

目录

1.类中的函数重载——流插入,流提取

1.类中实现

2. 把限制解掉

3.友元函数实现

2.const使用到成员函数

3.默认六大函数的两个取地址函数

4.构造函数补充,初始化列表 

1.初始化的选择顺序

2.const成员变量

3.引用成员变量

4.没有默认构造的自定义类型

5.类型转换

1.单参数类型转换

2.多参数类型转换(C++11出现的操作)

6.静态成员

1.静态成员变量

2.静态成员函数

3.静态成员的妙用

7.友元

8.内部类

9.匿名对象

10.编译器优化构造


1.类中的函数重载——流插入,流提取

在前面对于C的补充中我们知道了cout<<,这样的指令能自动识别并且打印所有内置类型,其根本原因是cout的函数本身就是函数重载。那么我们就好奇,如果要类也实现cout的指令会怎么样呢。

1.类中实现

那么我们在类中使用函数重载,将<<写成可以判断Date类的函数。

我们想要实现的是d1流到cout去,然后通过调用打印出日期。但是写进类中的函数重载传入的第一个值作为类函数的this指针,观察上面错误,我们发现,好像cout作为了this指针,这显然不合理,this指针是这个类中的自定义类型啊。所以要把d1跟cout调换一下才能通过。

 但是这样写不觉得很奇怪吗,跟C++自己编写的cout完全相反了。这个时候应该怎么改呢?我们目前能知道的是,不能放在类中,所以试着把函数写到类外面,写成全局函数。

不过写在类的外面要注意这个类的成员变量的权限,所以此刻的问题是成员变量的权限怎么办?

2. 把限制解掉

把限制解掉,但是这样子做十分危险,所以不太可行。不过这里撇开限制安全与否不谈,也存在错误,出现了重定义。这里错误的原因是:全局函数写在头文见中都会出现这样的错误,原因是函数不仅仅在.h中展开,在.c中也展开。所以重定义了。怎么解决呢?1).声明定义分离  2).变为静态的也可以解决,其原因是不会进其他文件,不生成符号表.  3).内联也可以解决(直接在.h中内联)

链式输出

在cout函数中,经常看到连续输出的链式形式。但是很显然上面的函数不能实现,因为没有返回任何值。解释一下cout的链式实现,它的实现是从左向右实现的,并且返回值为out。

3.友元函数实现

在类的任意位置写出友元声明,功能使得该函数允许访问限制的成员变量。

2.const使用到成员函数

我们想把只读的对象调用成员函数时,出现了权限放大的问题。为什么会出现这种情况?因为我们传入的对象是const的,但是要知道类的成员函数实现时隐藏的this指针没有const限制,说明对于函数视角看,传入的变量既可以读又可以写。 

 所以怎么改呢?只需要对成员函数后加const即可,即const修饰this指针使得指针地址指向的变量不可变属性带有const。(定义和声明都要加)不改变传入的对象时,建议都加const

3.默认六大函数的两个取地址函数

这两个函数一个用来获取普通的对象,一个获取const的对象。不过默认生成的取地址函数返回的跟写出来的没什么区别。所以不太使用,除非不只得到地址或不想取到正确地址,进行其他的操作可以自己写一个进行诠释。

4.构造函数补充,初始化列表 

在C++(4)中自动生成的构造函数中提到过初始化列表。类的初始化分为两层:1.构造函数体内的初始化 2.初始化列表的初始化。

可是,如果只是功能一致的初始化,那设计就没什么意义了。所以使用初始化列表一定有它迫不得已需要的地方。初始化列表起到什么作用?首先要清楚,类中的成员变量是声明,除非使用了缺省的形式不然达不到定义。此为初始化(在没有自己写初始化列表的类中)过程中,当函数走到初始化列表的位置,变量就已经创建了,并且被赋予定义了;初始化列表后的初始化可以理解为二次定义,当然在人的视角看来其实是一步完成的,但是它确实是分两步生成的。

必须使用初始化列表的情况:

1.const成员变量

2.引用成员变量

3.自定义类型成员(且该类没有默认构造函数时)

1.初始化的选择顺序

如果初始化列表中写了对成员变量的初始化,那么在类声明的中对成员变量做缺省操作的就不会再使用。

1.无论什么变量的类型,有初始化列表不看缺省

2.内置类型中,有缺省则不会生成随机

3.自定义类型,调用默认构造,没有就报错

2.const成员变量

 报错原因:其实就是const不能二次被定义。原因是在初始化列表变量就已经创造了,不过这个const变量不可控了。如此我们必须在初始化列表中定义。下图这样写才能使得const生成成功

3.引用成员变量

 报错原因:引用只能赋值一次,不能重新定义,如果没有初始化列表初始化,那么引用无法被改变。

4.没有默认构造的自定义类型

报错原因:A没有默认构造函数。默认构造函数是什么?1.我们写的无参的构造 2.我们写的全缺省的构造 3.系统生成的构造函数 。所以对于有自定义类型的,会调用默认构造,没有就报错。上图的A类确实没有默认构造函数。此时我们也可以对A做一个默认构造函数可以解决,当然用初始化列表来初始化解决问题也可以。

初始化列表初始化的顺序为声明的顺序!!!

5.类型转换

1.单参数类型转换

在下列代码中,我将类中的成员变量_year单独作为参数写出构造函数。这样子,我们就可以通过构造+拷贝构造,使得先把2022调用构造函数拷贝变为临时对象,然后把该临时对象构造为d2。该形式可理解为,一种由int强制类型转换成Date的隐式类型转换

 若不想出现隐式类型转换发生需要在构造前加上explicit。演示如下。

2.多参数类型转换(C++11出现的操作)

6.静态成员

1.静态成员变量

C++中尽量避免全局变量,因为没有被封装,不安全。所有在类中想要有一个生命周期为全局的话,我们需要引入静态成员。而在类中设置静态变量意味着,生命周期是全局的,但是会受到类域的限制,也就是作用域在类中进行。

 如上,相同类的对象中,static不算做对象内的内存。因为_a存在栈帧中,而N存在静态区中。它属于类,并且被类的每个对象共享。

那么如何把静态变量初始化呢?

定义在类的外面,需要用到域限定符;在类中可以直接访问静态变量。

int A::N = 0;//初始化

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
	static int N;
};

2.静态成员函数

目前想要访问static变量要么在对象里调用,要么将变量权限解除通过类域访问。但是还有一个问题,我想不需要把类解除的情况下将并且不用对象调用静态变量怎么办?此时想要静态成员函数。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
	static int GetN() //静态成员函数
	{
		return N;
	}
private:
	int _a;
	static int N;
};

int main()
{
	cout << A::GetN() << endl;  //访问静态成员函数
	return 0;
}

静态成员函数在类中没有隐藏的this指针,也就意味着不需要通过对象进行调用函数,所有在外面访问函数想要经过类域的访问限定符得到调用函数的功能。

3.静态成员的妙用

//求1+2+3+...+n
//要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)
class Sum
{
public:
    Sum()
    {
        _ret+=_i;
        ++_i;
    }
    static int GatSum()
    {
        return _ret;
    }
private:
    static int _i;
    static int _ret;
};


int Sum::_ret=0;
int Sum:: _i=1;


class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n];
        return Sum::GatSum();
    }
};

7.友元

友元:友元函数和友元类

友元函数特征:

1.访问类的私有和保护变量

2.不能用const修饰

3. 没有this指针,它只是可以访问类中的变量;想要类做参数还得重新传入

4.不受访问限定符的限制,可以是多个类的友元

友元类特征:

友元类的单项的;友元类可以访问类的私有成员,但是类无法访问友元类的私有成员

友元关系不能链式传递;A是B友元,B是C友元,A不是C友元

友元破坏封装,所以C++一般不使用。

8.内部类

内部类:即在类中的定义的类

class A
{
private:
	int _a;
public:
	class B
	{
		int _b;
	};
};

求A类的大小得出的结果是4,说明B是A的内部类但是二者类是独立的,只是B的类的访问受到A类域的限制,且B天生是A的友元

内部类C++也不太使用。

9.匿名对象

匿名对象构造为直接在类后面加括号而不定义具体对象名的操作。

 这里发现:匿名对象的生命周期只生效于那行。当该行结束后,系统会调用析构函数销毁对象。

生命周期过短,匿名对象的使用有什么用?是这样的,如果我们创造一个对象仅仅对该对象进行一次函数调用就不再使用,对于我们而言要有两步操作:1.创造对象,还得给对象定义名字,这个对象还要在整体的域结束后才得到内存释放 2.用得到的类调用函数 。其实这样子很麻烦,匿名对象则刚好解决,一行内构造匿名对象同时调用函数即可。

10.编译器优化构造

编译器会在调用构造函数时,自己判断优化使得尽可能高效的构造对象。

优化1(构造+拷贝构造 > 构造)

优化2,传参优化(构造+拷贝构造 > 构造)

优化3(拷贝构造+拷贝构造 > 拷贝构造)

优化4(构造+拷贝构造+拷贝构造 > 构造)

总结:编译器会对构造产生优化,优化的条件为“1.在同一行中有多次的构造或者拷贝构造实现需要实现 2.构造和拷贝构造,拷贝构造和拷贝构造都可以进行优化。”所以对自定义的类进行构造时,多考虑编译的优化条件以达到提高构造效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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
发出的红包

打赏作者

哈里沃克

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

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

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

打赏作者

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

抵扣说明:

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

余额充值