再谈构造函数
代码语言:javascript复制class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
代码语言:javascript复制class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化: 引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
下面这段代码:
尽管初始化列表尝试首先初始化_a1
然后初始化_a2
,但实际的初始化顺序是由成员变量在类定义中的声明顺序决定的。因此,_a2
会首先被初始化(使用未初始化的_a1
),然后_a1
才会被初始化为传入的参数a
。
在_a2
初始化时,_a1
是未定义的(即处于未初始化状态),但C 标准并不要求在这种情况下编译器产生错误或警告。实际上,许多编译器会允许这种用法,但_a2
会使用_a1
的当前(未定义)值进行初始化,这通常是一个未定义行为(Undefined Behavior, UB),但在实践中,它可能会表现为使用某个随机值。
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
题目:
代码语言:javascript复制class A {
public:
A() { _scount; } // 构造函数,每当创建一个A类的新对象时,都会增加_scount的值
A(const A& t) { _scount; } // 拷贝构造函数,当通过拷贝另一个A类对象来创建新对象时,也会增加_scount的值
~A() { --_scount; } // 析构函数,每当一个A类的对象被销毁时,都会减少_scount的值
static int GetACount() { return _scount; } // 静态成员函数,返回当前A类对象的数量
private:
static int _scount; // 静态私有成员,用于跟踪A类对象的数量(声明)
};
int A::_scount = 0; // 在类定义外部初始化静态成员_scount,初始值为0
void TestA() {
cout << A::GetACount() << endl; // 输出当前A类对象的数量,此时应该为0,因为还没有创建任何A类的对象
A a1, a2; // 创建两个A类的对象a1和a2,此时A类对象的数量应该增加到2
A a3(a1); // 通过拷贝a1来创建一个新的A类对象a3,此时A类对象的数量应该增加到3
cout << A::GetACount() << endl; // 再次输出当前A类对象的数量,此时应该为3
}
1.静态成员函数可以调用非静态成员函数吗?
不能。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,由于静态成员函数没有this指针,因此它无法访问类的非静态成员,因为这些非静态成员需要this指针来访问。
2. 非静态成员函数可以调用类的静态成员函数吗?
可以。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,所以它们可以在没有任何对象实例的情况下被调用。非静态成员函数可以通过类名直接调用静态成员函数,也可以通过this
指针调用,尽管通过this
指针调用并不是必要的,因为静态成员函数不属于任何特定对象实例。
友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。 友元分为:友元函数和友元类
友元函数
现在尝试去重载operator,然后发现没办法将operator重载成成员函数。
因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置,this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator重载成 全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。
代码语言:javascript复制friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C是A的友元。
友元关系不能继承。
内部类
概念:如果一个类定义在另一个类的内部,这个类就叫做内部类。
内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。
特性: 1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。
匿名对象
代码语言:javascript复制int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}
拷贝对象时的一些编译器优化
在传参和传返回值的过程中,编译器会做一些优化,减少一些对象的拷贝。
代码语言:javascript复制int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 传值返回
f2();
cout << endl;
// 隐式类型,连续构造 拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造 拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
// 一个表达式中,连续拷贝构造 拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造 赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
再次理解类和对象
类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。