1、构造函数
-
构造函数赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造 函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内 可以多次赋值。
-
初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变 量"后面跟一个放在括 号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month( month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
}
上面两种方法来定义构造函数,一种是在构造函数体内对成员变量进行初始化,一种是使用初始化列表来初始化成员变量,都可以达到初始化成员变量的效果,那两者有什么区别吗?在对于内置类型来看,我们并不能看出有什么区别,那对于自定义类型呢?现在看下面的代码:
class Time
{
public:
Time(int hour = 0,int minute = 0,int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
cout << "Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1990, int month = 1, int day = 1,int hour = 1,int minute = 1,int second = 1)
{
_year = year;
_month = month;
_day = day;
_t = Time(hour, minute, second);
//创建一个Time类匿名对象来对_t进行初始化
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d;
system("pause");
return 0;
}
先来看看程序输出的结果:
可以看到输出结果为两次 “Time()”,说明程序调用了两次 Time 的构造函数,为什么会出现两次?下面对程序进行调试:
当我经过调试的时候发现,当程序运行到 970 行时,再按 F11 发现程序运行到第 955 行,进入 Time 的构造函数,当程序到975行 _t = Time(hour, minute, second); 时又调用一次构造函数,所以一共会输出两次,但是为什么会在 970 行的下一步调用Time 的构造函数呢?
然后我把 Date 的构造函数用初始化列表进行初始化:
发现用初始化列表进行初始化后只调用了一次Time的构造函数。
得出结论:对于自定义类型,一定会在初始化列表处调用一次它的构造函数,即使我们没有自己写初始化列表,编译器也会自己调用一次初始化,所以说我们尽量使用初始化列表进行初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化。另外,为什么要用初始化列表进行初始化:① 进行初始化对于自定义类型来说就少了一次调用它的构造函数,提高了效率 。②:有的情况必须使用初始化列表来初始化。
使用初始化列表注意几点:
-
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
-
初始化列表是成员变量定义的地方,有些变量必须在定义的时候初始化,比如下面几种情况:
①:常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
②:引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里。
class A
{
public:
B(int a, int ref)
: _ref(ref)
, _n(10)
{}
private:
int& _ref; // 引用
const int _n; // const
};
③:没有默认构造函数的自定义类型。
当我把 Date 类的构造函数改为用赋值进行初始化、把 Time 的默认构造函数改为带参的构造函数(不是默认的构造函数),编译时发现编译器报错----“2 IntelliSense: 类 “Time” 不存在默认构造函数”如图:
当我把 Date 类的构造函数用初始化列表进行初始化时, Time 的默认构造函数继续为带参的构造函数(不是默认的构造函数)发现程序正常运行:
所以没有默认构造函数的自定义类型必须使用初始化列表进行初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
class A
{
public:
A(int x)
:_b(x)
, _a(_b)
{}
private:
int _a;
int _b;
};
int main()
{
A s(10);
system("pause");
return 0;
}
_a 被初始化为随机值
_b 被初始化为10
-
explicit 关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用,来看一段代码:
class Date
{
public:
Date(int year = 1990)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018);//构造函数
Date d2 = 2019;//构造函数+拷贝构造+优化 = 构造函数
system("pause");
return 0;
}
在我看来,代码中构建 d2 这个对象是不可能实现的,但是实际上编译器是可以完成的,用一个整形变量给一个日期类型对象赋值,实际编译器背后会用 2019 构造一个临时无名对象,最后用无名对象给d1对象进行赋值。其中经过了 “构造函数+拷贝构造”,只不过编译器优化了整个过程。上述代码可读性不是很好,如果不想要这样的情况发生,用 explicit 修饰构造函数,将会禁止单参构造函数的隐式转换。
class Date
{
public:
explicit Date(int year = 1990)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018);
Date d2 = 2019;
加上explicit后运行整段代码是,编译器就会报错:不存在从 "int" 转换到 "Date" 的适当构造函数
system("pause");
return 0;
}
在这里举一个例子:
int main()
{
double a = 1.23;
int b = a;
}
在这里,a 会创建一个临时 int 类型变量,然后再把临时变量赋给 b ,会有一个隐式类型转换。
另外还有一种情况,如果这样子的话:
int main()
{
double a = 1.23;
const int& b = a;
}
用引用一定要记得在前面加上 const ,因为临时变量具有常性,现在 b 是临时变量的别名,不能被修改。
2、static 成员
-
什么是 static 成员
声明为 static 的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变 量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
-
static 成员的特性
①:静态成员为所有类对象所共享,不属于某个具体的对象,而属于整个类。
②:静态成员变量必须在类外定义,定义时不添加static关键字 。
class A
{
public:
A()
{}
private:
static int _count;
};
int A::_count = 0;//定义初始化
int main()
{
A d1;
system("pause");
return 0;
}
③:类静态成员即可用类名 :: 静态成员或者对象 . 静态成员来访问。
class A
{
public:
A()
{
++_count;
}
static int Getcount()
{
return _count;
}
private:
static int _count;
int _a;
};
int A::_count = 0;//定义初始化
int main()
{
cout << A::Getcount() << endl;//输出 0
//这样子只能调静态成员函数,不能调用非静态成员函数,
//因为非静态成员函数含有隐含的 this 指针,需要传实参,这样子没有隐含的对象的地址
A d1;
cout << d1.Getcount() << endl;//输出 1
system("pause");
return 0;
}
④: 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员。
⑤:静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值, const修饰符等参
⑥:静态成员函数不可以调用非静态成员函数、非静态成员函数可以调用类的静态成员函数
三、C++ 11 的一些新特性
- C++11支持非静态成员变量在声明时,直接初始化。
class B
{
public:
B(int b = 0)
:_b(b)
{}
int _b;
};
class A
{
public:
void Print()
{
cout << a << endl;
cout << b._b << endl;
cout << p << endl;
}
private:
// 非静态成员变量,可以在成员声明时,直接初始化。
int a = 10;
B b = 20;
int* p = (int*)malloc(4);
static int n;
};
int A::n = 10;
int main()
{
A a;
a.Print();
return 0;
}
四、友元
-
友元函数
引入友元函数的原因:类具有封装的特性,只有类的成员函数才能访问类的私有成员变量,非成员函数只能访问类的公用成员变量。为了使类的非成员函数访问类的成员,唯一的做法就是讲成员变量定义为 public ,但是这样子做话破坏类的封装特性。基于以上原因,所以引入友元函数解决。举一个例子:现在我写一个成员函数重载 operator<< ,代码如下:
class Date
{
public:
Date(int year = 1990, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2019,10,3);
//cout << d1;
d1 << cout;
system("pause");
return 0;
}
通过编译我发现,该函数重载不能写成 “cout << d1”,因为 << 是双目操作符,cout 的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。所以只能写成 “d1 << cout” 这个样子,但是这种代码失去了它的可读性,相比来说,我们更喜欢 “cout << d1” 这样的代码,为了实现这种,可以考虑不用成员函数,我将operator<<重载成全局函数。但是这 样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。
class Date
{
friend void operator<<(ostream& out, const Date& d1);
public:
Date(int year = 1990, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out, const Date& d1)
{
out << d1._year << "-" << d1._month << "-" << d1._day << endl;
}
int main()
{
Date d1(2019,10,3);
cout << d1;
system("pause");
return 0;
}
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。另外,使用友元函数还要注意下面几点:
①:友元函数可访问类的私有成员,但不是类的成员函数
②: 友元函数不能用const修饰
③: 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
④: 一个函数可以是多个类的友元函数
⑤: 友元函数的调用与普通函数的调用和原理相同
- 友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员,使用类应该注意下面几点:
①:友元关系是单向的,不具有交换性。
比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
②:友元关系不能传递。
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
class Date; // 前置声明
class Time
{
friend class Date;
// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
}
/*Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}*/
private:
int _hour;
int _minute;
int _second;
};
class Date
{
friend void operator<<(ostream& out, const Date& d1);
public:
Date(int year = 1990, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void SetTime(int hour, int minute, int second)
{ // 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
五、内部类
-
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的 类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
-
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。
-
特性:
①: 内部类可以定义在外部类的public、protected、private都是可以的。
②: 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
③: sizeof(外部类) = 外部类,和内部类没有任何关系。
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void func(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.func(A());
return 0;
}