今天我们学习C++类和对象的最后一些知识,主要是为了更加深入地理解和使用类和对象。
我们之前将到过,类会默认一个构造函数。然后在实例化对象时,编译器就会调用这个构造函数,给成员变量赋值。
class Date
{
public:
Date(int year = 1 , int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
//声明
int _year;
int _month;
int _day;
};
int main()
{
//对象
Date d1(2025, 8, 23);
Date d2;//未定义无参的默认构造 写成全缺省就ok
return 0;
}如果没有定义无参的构造函数,我们建议将构造函数写成全缺省的,因为这样不管是有参对象还是无参对象都能够使用这个构造函数。
我们来看一下如果不是全缺省的情况:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2025,8,23);
//对于下面这个无参的对象,无法调用构造函数
//因为我们只有含参的构造函数
Date d2;
}
上面是通过构造函数进行初始化,除此之外,C++还提供了一种通过初始化列表的方式。
初始化列表:以一个冒号开始 ,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
我们试着使用初始化列表的形式,初始化我们的Date日期类:
class Date
{
public:
//初始化列表
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
//声明
int _year;
int _month;
int _day;
};
int main()
{
//对象
Date d1(2025, 8, 23);
Date d2;
return 0;
}从上面的代码来看,好像和使用构造函数体赋值并没有优化什么,那么为什么还要使用初始化列表呢?
这里我们就要回忆起一个知识点,有些成员变量是需要在声明时就进行初始化的!!!
因为如果需要在变量声明时就初始化的话,构造函数体赋值的方式,明显是会报错的。因为在没有调用构造函数之前,都无法进行初始化。而初始化列表就能完美解决这个问题!
Q:思考,那些成员是必须在定义的地方就进行初始化呢?
A:(a) 引用成员变量 (b)const成员变量 (c)自定义类型(无默认构造函数)
PS:这里补充说明一下为什么自定义类型(无默认构造函数)必须在定义时初始化。先要注意的是,这里并不是说所有的自定义类型,而是特指那些没有默认构造函数的自定义类型,而默认的构造函数是无参构造函数。所以在没有无参构造函数的情况下,如果不提供初始化的参数(或者说是一个无参的对象),是肯定会报错的。
class A
{
public:
A(int a)
:_a(a)
{
}
private:
int _a;
};
class Date
{
public:
//只能在初始化列表初始化
Date(int year, int n, int ref,int a)
:_n(n)
,_ref(ref)
//这里的_aa并没有别的意思,只是已经有了_a,避免重名
,_aa(a)
{
_year = year;
}
private:
//可以在定义时初始化;也可以定义时不初始化,后面再赋值修改
int _year;
//只能在定义的时候初始化
const int _n;
int& _ref;
A _aa;
};总结:
class OrderMatters {
private:
int a;
int b;
public:
// 虽然写的是 : b(1), a(2)
// 但实际初始化顺序是:先 a(2),再 b(1)(按声明顺序)
OrderMatters() : b(1), a(2) {}
};我们先来看一个奇妙的事情:

我定义的d2很明显是一个Date日期类,但是2025很明显是一个int类型,但是我却成功运行了这段程序,并且发现2025被初始化成了_year。这是怎么回事?

实际上,单参数构造函数(Single-argument constructor)可以用于隐式类型转换。这是C++中一个强大但需要小心使用的特性。
但是这样的话可能会出现一些不合法数据被错误的传入了构造函数,而explicit关键字就是用来限制这种类型转换的:
class Date
{
public:
explicit Date(int year=1, int month=1, int day=1)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
此时这个错误就会被检测出来,所以说explicit关键字就是用来禁止单参构造函数的隐式转换
我们通过一个问题来进入static的探讨,Q:统计类型A被创造了多少次?
//全局变量 -- 不提倡
int num = 0;
class A
{
};
A Func(A a)
{
A copy(a);
return copy;
}
int main()
{
A a1;
A a2 = Func(a1);
cout << num << endl;
}如果我们直接判断的话会比较不方便,找起来也比较麻烦。
方法一:全局变量
这里可以直接将构造函数和拷贝构造函数调用的记录打印不就好了,这里我们分别使用不同的两个全局变量来记录:
int num1 = 0;
int num2 = 0;
class A
{
public:
A()
{
++num1;
}
A(const A& aa)
{
++num2;
}
};
A Func(A a)
{
A copy(a);
return copy;
}
int main()
{
A a1;
A a2 = Func(a1);
cout << num1 << endl;
cout << num2 << endl;
}
这样确实可以得到正确的答案,但是会出现一个问题:在项目中,如果将头文件中定义全局变量的话可能发生链接错误;并且全局变量也破坏了封装性。
方法二:静态成员变量
#include <iostream>
using namespace std;
class A
{
public:
A()
{
++_count1;
}
A(const A& aa)
{
++_count2;
}
//静态成员变量属于整个类
static int _count1;//声明
static int _count2;
int _a;
};
A Func(A a)
{
A copy(a);
return copy;
}
//定义
int A::_count1 = 0;
int A::_count2 = 0;
int main()
{
A a1;
A a2 = Func(a1);
cout << sizeof(a1) << endl;
cout << a1._count1 << endl;
cout << a2._count1 << endl;
cout << a1._count2 << endl;
cout << a2._count2 << endl;
cout << A::_count1 << endl;
cout << A::_count1 << endl;
return 0;
}
PS:这里可能在不同的计算机上会有不同的答案,可能是4113311(因为编译器的优化程度不同)
这里的结果中的 4 比较好理解,因为整个类中有一个int型的成员变量,_count1、_count2都是静态成员变量(被储存在静态区)。
这里还要注意一个点:就是静态成员变量在类中声明,不能在类中直接定义!!!定义时不添加static
如果静态成员变量是private私有的,需要编写访问接口:
class A
{
public:
A()
{
++_count1;
}
A(const A& aa)
{
++_count2;
}
//访问接口
int GetCount1()
{
return _count1;
}
int GetCount2()
{
return _count2;
}
private:
//静态成员变量属于整个类
static int _count1;//声明
static int _count2;
int _a;
};
A Func(A a)
{
A copy(a);
return copy;
}
//定义
int A::_count1 = 0;
int A::_count2 = 0;
int main()
{
A a1;
A a2 = Func(a1);
cout << a1.GetCount1() << endl;
cout << a2.GetCount1() << endl;
cout << a1.GetCount2() << endl;
cout << a2.GetCount2() << endl;
return 0;
}
如果我并没有实例化对象,但是我仍想访问这个静态成员变量:

正常情况下是会报错的,但是我们可以将这个访问接口也写成静态成员函数:
class A
{
public:
A()
{
++_count1;
}
A(const A& aa)
{
++_count2;
}
//静态访问接口
static int GetCount1()
{
return _count1;
}
static int GetCount2()
{
return _count2;
}
private:
//静态成员变量属于整个类
static int _count1;//声明
static int _count2;
int _a;
};
A Func(A a)
{
A copy(a);
return copy;
}
//定义
int A::_count1 = 0;
int A::_count2 = 0;
int main()
{
A a1;
A a2 = Func(a1);
cout << a1.GetCount1() << endl;
cout << a2.GetCount2() << endl;
cout << A::GetCount1();
return 0;
}
C++11支持非静态成员变量在类中生命时,直接进行初始化赋值,注意这里的初始化赋值本质上是一种缺省值的形式。
class B
{
public:
B(int b = 0)
:_b(b)
{
}
private:
int _b;
};
class A
{
public:
private:
//非静态成员变量,可以在成员声明时给缺省值
int a = 10;
B b = 20;//隐士类型转换
int* p = (int*)malloc(4);
};友元包括友元函数和友元类。 友元突破封装性性,确实在一定程度上有灵活性。但是,友元同样增加了代码耦合性,所以友元的使用要有限制。
友元函数可以访问类中私有、受保护的成员变量,它是定义在类之外的普通函数,并不属于某一个类,只是需要在类中声明,声明时需要加上friend关键字:
class Date
{
//友元函数
friend ostream& operator<<(ostream& out, const Date& d);
public:
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day << endl;
return out;
}总结:
友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类的非公有成员。
//友元
class Date;//Date类的声明
class Time
{
//友元类
friend class Date;
public:
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
友元函数
friend ostream& operator<<(ostream& out, const Date& d);
public:
要访问Time类内的成员,使用友元类
void SetDateTime(int hour,int minute,int second)
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day << endl;
return out;
}这段代码中我们可以发现,想要在Date类中访问Time类,就需要使用友元类。
注意:
内部类的概念其实很简单,当一个类定义在另一个类的内部,那么这个内部的类就叫做内部类:
class A
{
private:
static int k;
int h;
public:
//内部类
class B//B天生是A的有友元
{
public:
void foo(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
private:
int _b;
};
};然后我们来探讨一下这个内部类能干嘛?
我们先来看看这个内部类占用外部类的内存情况:
A a;
cout << sizeof(a) << endl;
结果令人有些吃惊,居然只有四个字节!!!
而这个内存正是外部类int h的,换句话说,内部类似乎与外部类并没有任何关系。那么它存在的意义是什么呢?
C++中这样规定,所有内部类天生是外部类的友元(也就是说,内部类可以访问外部类的成员,但是外部类并不是内部类的友元):
class A
{
private:
static int k;
int h;
public:
//内部类
class B//B天生是A的有友元
{
public:
void foo(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
private:
int _b;
};
void Print(const B& b)
{
cout << b._b;
}
};
int main()
{
A a;
}
总结:
(本篇完)