一、细说构造函数
1.1初始化列表的引入
我们都知道构造函数的作用是在创建对象的时候给对象的成员属性赋值。
那么下面的场景可以用构造函数进行赋值吗?
#include<iostream>
using namespace std;
class example1
{
public:
//可以这么写吗? 编译器能通过吗?
example1(int i, int j)
{
x = i;
j = y;
}
private:
const int x;
int& y;
};
int main()
{
return 0;
}
答案是不可以的。编译器也不支持这么胡搞。
首先要知道为什么不可以这么写。
为了解决上面不能使用构造函数来初始化的场景,所以就引入了初始化列表这个概念。可以通过初始化列表来完成初始化。比如:
1.2初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
注:
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量、 const成员变量、自定义类型成员(且该类没有默认构造函数时)
怎么理解上面被加粗的这段话呢?
之前在C++类和对象(上)说过默认构造函数会对内置类型不做处理对自定义类型会调用其默认构造函数。这句话准确的来讲是在初始化列表会调用自定义类型的默认构造函数。如图:
那如果example2没有默认构造函数呢?我的意思是你重载了一个有参构造函数、或者说你重载了两个有参构造函数,此时默认构造函数就失效了,而编译器又不知道你调用的有参构造函数的参数是多少,所以编译器不能帮你调用有参构造函数,所以就会报错。
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。这个比较坑,稍不留神都不知道自己错在哪里。比如:
初始化列表顺序是从_arr开始,而_arr需要使用_capacity来初始化,慢慢的开始离谱了。
1.2关键字explicit
这个是4/6级词汇。顺带复习一下。
explicit:adj 清楚的、明确的、易于理解的。
explicitness:n 直言不讳、明确性
explicitly:adv 清楚明确地
在了解explicit这个关键字的作用之前先看看这个代码:
这么写为什么不会报错呢?这个代码不符合我们对类的常规认识呀。
其实这里发生了隐式类型转换。具体如下:
这里再补充一个奇葩的写法。如下:
说明如下:
那如果你不想发生隐式类型转换怎么办呢?就需要用到关键字explicit 这个英文含义是直接了当的,不需要隐式类型转换这个含糊不清的东西。比如:
二、static成员
2.1static成员的特性
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
特点:
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
2.2题目:实现一个类,计算程序中创建出了多少个类对象
代码:
#include<iostream>
using namespace std;
class example
{
public:
example()
{
num++;
}
static int GetNum()
{
return num;
}
private:
static int num;
};
int example::num = 0;
void test()
{
for (int j = 0; j < 10; j++)
{
example ex;
}
}
int main()
{
for (int i = 0; i < 10; i++)
{
example ex;
}
test();
cout << example::GetNum() << endl;
return 0;
}
**补充:**非静态成员函数可以调用静态成员函数,而静态成员函数不可以调用非静态成员函数。
为什么呢?解释如下:
刚刚上面说过了静态成员函数不能访问非静态成员变量。如果一个非静态成员函数A进行了对非静态成员函数的访问,那么此时你用静态成员函数B再去访问非静态成员函数A不就相当于完成了对非静态成员的访问了?那么就矛盾了。
2.3题目:设计一个类 只能再栈上或者堆上创建
#include<iostream>
using namespace std;
class example
{
public:
static example GetObjectInStack()
{
example ex;
return ex;
}
static example* GetObjectInHeap()
{
return new example;
}
private:
example()
{}
};
int main()
{
example ex1 = example::GetObjectInStack();
example* ex2 = example::GetObjectInHeap();
return 0;
}
三、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。(关于这个耦合度我自己也不怎么清楚,这里先给自己埋下一个坑,等以后自己的知识储备更加完善后来填坑,特此标注来提醒自己。)
友元分为:友元函数和友元类
3.1友元函数
友元函数简单来讲呢就是:类外的函数想要访问类的属性需要成为该类的友元函数。
怎么成为该类的友元函数呢?
语法:在类内的任意位置中加上friend 函数返回值 函数名(函数参数);
比如:
#include<iostream>
using namespace std;
class Date
{
friend void Printf();
public:
Date(int year, int month, int day)
{
Year = year;
Month = month;
Day = day;
}
private:
int Year;
int Month;
int Day;
};
void Printf()
{
Date d1(2023, 5, 16);
cout << d1.Year << "-" << d1.Month << "-" << d1.Day << endl;
}
int main()
{
Printf();
Date d2(2022, 12, 12);
return 0;
}
说明:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
3.2友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元类的特性:
- 友元关系是单向的,不具有交换性。
如果A类是B的友元类不能说明B类是A类的友元类。 - 友元关系不能传递
- 友元关系不能继承
使用如下:
#include<iostream>
using namespace std;
class Time
{
friend class Date;
public:
Time(int hour, int minute, int second)
{
Hour = hour;
Minute = minute;
Second = second;
}
private:
int Hour;
int Minute;
int Second;
};
class Date
{
friend void Printf(const Date& d1);
public:
Date(int year, int month, int day)
{
Year = year;
Month = month;
Day = day;
}
void PrintAllDate(const Time& t)
{
cout << Year << "-" << Month << "-" << Day << "_" << t.Hour << ":" << t.Minute << ":" << t.Second << endl;
}
private:
int Year;
int Month;
int Day;
};
void Printf(const Date& d1)
{
cout << d1.Year << "-" << d1.Month << "-" << d1.Day << endl;
}
int main()
{
Date d1(2023, 5, 16);
Printf(d1);
Time t1(19, 44, 55);
d1.PrintAllDate(t1);
return 0;
}
四、内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
比如:
#include<iostream>
using namespace std;
class External
{
public:
class Internal
{
public:
void fun1(const External& e1)
{
cout << "val1=:" << e1.val1 << "val2=:" << e1.val2 << endl;
cout << x << endl;
}
};
private:
static int x;
int val1;
int val2;
};
int External::x = 888;
int main()
{
cout << sizeof(External) << endl;
return 0;
}
五、匿名对象
匿名对象与有名对象的定义如下:文章来源:https://www.toymoban.com/news/detail-438121.html
#include<iostream>
using namespace std;
class Example
{
public:
Example(int x, int y)
{
cout << "构造函数调用" << endl;
val1 = x;
val2 = y;
}
~Example()
{
cout << "析构函数的调用" << endl;
}
void func01()
{
cout << "func01成员函数调用" << endl;
}
private:
int val1;
int val2;
};
int main()
{
Example ex1(666, 888);//有名对象
Example(888, 666);//匿名对象
return 0;
}
他们从定义的形式来看差别在于匿名对象没有名字。
在生命周期来看,匿名对象的生命周期在当前行结束之前,出了当前行后就被销毁。如:
匿名对象也是可以调用成员函数的。比如:
匿名对象与临时变量一样具有常性。所以对象在引用一个临时变量前必须加上const。
加上const后事情开始变得有趣了。匿名对象的生命周期变长了,生命周期变得在当前函数的作用域内。因为不这样的话就变成野引用了。比如:
文章来源地址https://www.toymoban.com/news/detail-438121.html
到了这里,关于【C++的类与对象(下)】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!