const成员函数
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
void Show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 2);
d1.Show();
const Date d2;
d2.Show();//error C2662: “void Date::Show(void)”:
//不能将“this”指针从“const Date”转换为“Date &”
return 0;
}
以上代码定义了一个日期类,并实例化了2个对象d1和d2,不同的是d2是const类型。在调用类成员函数打印时,d1可以正常调用,d2发生错误,原因在于:
权限的放大会使得用const定义的常量d2,通过*this可以被改变了,在C++中,这种权限的放大被禁止,那常量d2怎样访问它的成员函数呢?
C++通过引入const成员函数来解决这个问题,语法也很简单,函数参数列表后面跟关键字const即可。const成员函数参数列表中隐含的this指针类型为 const Date* const,相当于双const修饰,this指针不能改变指向,*this的值也不能发生改变,这样const变量就可以访问了。
成员函数变const,那非const对象,比如d1还能访问吗?答案是可以的。d1访问时,参数类型从Date*变成const Date*,这是权限缩小,权限缩小是被允许的。
class Test
{
public:
void NonConstFunc()
{
ConstFunc();//OK
}
void ConstFunc() const
{
NonConstFunc();//err
}
};
上面的代码如果不考虑运行时的无限调用,从语法上来说,非const成员函数可以调用const成员函数,但是const成员函数不能反过来调用非成员函数。其中的原因也是因为this指针的权限问题。
总结:
const对象不能调用非const成员函数。
const成员函数不能调用非const成员函数。
除此之外都可以调用。
&运算符和const &运算符的重载
这两个运算符重载并没有多大意义,因为即便我们不写,编译器也会自动生成,而且生成的也够用。
class Test
{
};
int main()
{
Test a;
const Test b;
cout << &a << endl;
cout << &b << endl;
return 0;
}
流插入<<和流提取>>运算符的重载
流插入<<和流提取>>运算符的重载可以实现自定义类型对象的直接输入输出。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
ostream& operator<<(ostream& out) const
{
out << _year << "-" << _month << "-" << _day << endl;
return out;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 2);
const Date d2;
//cout << d1 << endl;//err
d1 << cout;
d2 << cout;
return 0;
}
由于隐含的this指针占用了第一个参数位置,所以cout对象只能传给第二个参数,这就导致了与平时内置类型cout参数顺序不同,所以我们还得想别的办法。
要想第一个参数不是this,而是Date对象,就不能定义在类中,所以只能实现为全局函数。而且,为了在类外访问私有成员变量,需要把重载函数声明为Date类的友元函数。
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" <<d._month << "-" << d._day;
return out;
}
int main()
{
Date d1(2022, 10, 2);
const Date d2;
cout << d1 << endl;
cout << d2 << endl;
return 0;
}
对于cin,可以用类似的做法:
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
int main()
{
Date d1(2022, 10, 2);
Date d2;
cin >> d1 >> d2;
cout << d1 << endl;
cout << d2 << endl;
return 0;
}
初始化列表
在构造函数函数体内赋值并不是严格意义上的初始化,因为初始化只能一次,而函数体内可以多次赋值。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式,只有构造函数可以使用。
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
【注意】
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量(引用在定义的时候必须赋值初始化)
const成员变量(常量必须在定义的时候初始化)
自定义类型成员(且该类没有默认构造函数时)
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
//_N = 10;error C2789: “Date::_N”: 必须初始化常量限定类型的对象
//此时_N已经被定义出来了,但没有完成初始化,所以报错。
}
private:
int _year;
int _month;
int _day;
//const int _N;
};
int main()
{
return 0;
}
class Test//TEST没有默认构造函数
{
public:
Test(int a)
{
}
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_N(10)
,_ref(year)
,t(1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
const int _N;
int& _ref;
Test t;
};
int main()
{
return 0;
}
总结:
1. 初始化列表是成员变量定义的地方。
2. const成员变量,引用成员变量和没有默认构造的自定义成员变量必须在初始化列表里初始化。因为它们都必须在定义的时候初始化。
3. 其他成员变量,也可以在其他地方初始化。
4. 建议在初始化 列表的位置初始化,特别是自定义类型,更高效。
5. 即便没有写初始化列表,也可以认为初始化列表也是存在的,变量的定义就在这里完成,包括对象中的自定义类型。
6. 初始化按照声明的顺序来,与初始化 列表顺序无关。
explicit关键字
class Date
{
public:
Date(int year = 1)
:_year(year)
{
}
private:
int _year;
};
int main()
{
Date d = 2022;//隐式类型转换
//编译器会在这里用2022构造一个临时对象
//再用这个临时对象拷贝构造d
//但编译器这里会优化,最后只剩一个构造
return 0;
}
从int到Date发生了隐式类型转换。
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
class Date
{
public:
explicit Date(int year = 1)
:_year(year)
{
}
private:
int _year;
};
int main()
{
Date d = 2022;// error C2440: “初始化”: 无法从“int”转换为“Date”
return 0;
}
Static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。
静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间。
静态成员变量一定要在类外进行初始化,类外初始化时不带static,要指定类域。
class Test
{
public:
Test(int a = 0)
:_a(a)
{
_cnt++;
}
Test(const Test& b)
:_a(b._a)
{
_cnt++;
}
static int GetCnt()//没有this指针,只能访问static变量或函数
{
return _cnt;
}
void Foo()
{
_cnt++;
GetCnt();//普通成员函数可以访问静态函数和静态变量
}
private:
int _a;
static int _cnt;//静态成员变量类内声明
};
int Test::_cnt = 0;//静态成员变量类外定义
Test Func(Test t)
{
return t;
}
int main()
{
Test u(100);
Test v = u;
Func(v);
cout << u.GetCnt() << endl;//外部访问静态成员函数可以用对象访问
cout << Test::GetCnt() << endl;//也可以用指定类域直接访问
Test::Foo();//err 但是调用非静态成员函数需要一个对象
//return 0;//可省略?
}
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员,即只能访问静态成员变量和静态成员函数。
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
友元
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。见前面<<流插入运算符重载。
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递, 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承
内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。
4. 内部类和在全局定义是一样,只是受到外部类域的限制。