目录
<一>const成员
<二> 取地址及const取地址操作符重载
<三>再谈构造函数(初始化列表)
1.构造函数体赋值
2.初始化列表
<四>explicit关键字
<五>static成员
概念
<六>友元函数
<七>友元类
<八>内部类
<九>匿名对象
<一>const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
我们来看看代码:
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 24)//默认构造
{
_year = year;
_month = month;
_day = day;
}
//void Print1(Date* this)
void print1()
{
cout << _year << '/' << _month << '/' << _day << endl;
}//这里不加const 只能事非const对象可以调用
//void Print2(const Date* this)
void print2() const//const加在函数的后面 修饰this指针。
{
cout << _year << '/' << _month << '/' << _day << endl;
}//这里加 const不仅const对象可以调用 非const对象也能调用。
//上面两个又构成函数的重载。
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 24);
d1.print1();
d1.print2();
const Date d2(2022, 10, 24);
//d2.print1();//报错权限提升
d2.print2();
return 0;
}
注意:
- 成员函数加 const,变成 const 成员函数是有好处的,这样 const 对象可以调用,非 const 对象也可以调用。
- 不是说所有的成员函数都要加 const ,具体要看成员函数的功能,如果成员函数是修改型 (operrato+=、Push),那就不能加;如果是只读型 (Print、operator+),那就最好加。
- const 对象不可以调用非 const 成员函数 (权限放大);非 const 对象可以调用 const 成员函数 (权限缩小)。(权限只能缩小不能放大)
- const 成员函数内不可以调用其它非 const 成员函数;非 const 成员函数内可以调用其它 const 成员函数。
<二> 取地址及const取地址操作符重载
- 这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date { public: //取地址重载 Date* operator&() { return this; } //const取地址操作符重载 //注意这里的返回值喝 const函数的使用。 const Date* operator&()const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1; Date d2; cout << &d1 << endl; cout<< &d2 << endl; return 0; }
注意:这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
<三>再谈构造函数(初始化列表)
1.构造函数体赋值
在创建对象时,编译器会自动调用构造函数,给对象的每一个成员变量一个合适的初始值。
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 24)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
注意:
- 虽然在调用构造函数前已经有一个初始值,但是不能将其视为对对象进行初始化,构造函数的函数体中的语句只能将其称为赋值,而不能将其称为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。正真的初始化是在初始化列表中初始化。
2.初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 24)
:_year(year)
,_month(month)
,_day(day)
{ }
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
但是当成员变量中又自定义类型,然而这个自定义类型没有默认构造函数的时候就会出现问题这个时候就必须用到,初始化列表来实现初始化。
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)" << endl;
}
A& operator=(const A& aa)//不写也行,因为这里只有内置类型,默认生成的就可以完成
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;
};
class B
{
public:
B(int a, int b)
{
//_aa._a = a;//err:无法访问private成员
//无法访问就只能再去定义一个A 类的对象。
//然后赋值重载给_aa;
//A aa(a);//构造函数
//_aa = aa;//赋值重载函数
_aa = A(a);//简化版,同上面一个意思
_b = b;
}
private:
int _b = 1;
A _aa;
};
【注意】
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
1.引用成员变量
2.const成员变量
3 .自定义类型成员(且该类没有默认构造函数时)
class A
{
public:
A(int a)//普通构造函数
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)//引用只能在定义的时候初始化,所以必须放在这里
,_n(10)//const类型的也是一样必须在定义的时候初始化。
{}
private:
//这里只是声明
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
<四>explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用
class Date
{
public:
// //1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// //explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
//explicit Date(int year)
// :_year(year)
//{}
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换
Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2022);
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2023;
// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
Date d2 = 2022;//这里因为存在隐式类型转换,会有一个临时变量,就会先构造函数+拷贝构造
//现在有编译器的优化 就会直接构造函数解决
}
上述代码可读性不是很好,用explicit修饰构造函数,将会禁止构造函数的隐式转换。
<五>static成员
概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
如果想要计算一个类被创建过多少次:
int countC = 0;
int countCC = 0;
class A
{
public:
A()
{
++countC;
}
A(const A& a)
{
++countCC;
}
};
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << countC << endl;
cout << countCC << endl;
return 0;
}
//这种方式也可以但是,不好countc 和 countCC 可以随意改动 不好,在c++中还是尽量少使用全局变量。
class A
{
public:
A()
{
++_count;
}
A(const A& a)
{
++_count;
}
int GetCount()
{
return _count;
}
static int GetCount()
{
return _count;
}
private:
int _a;
static int _count;
};
//定义初始化
int A::_count = 0;
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << sizeof(A) << endl;
//这里就体现了static成员属于整个类,也属于每个定义出来的对象共享,但限制于公有
/*cout << A::_count << endl;
cout << a1._count << endl;
cout << a2._count << endl;*/
/*A ret;
cout << ret.GetCount() - 1 << endl;*/
/*cout << A().GetCount() - 1 << endl;*/
cout << A::GetCount() << endl;
return 0;
}
特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。放在类外面定义的目的是,只初始化一次,这样避免每个对象在定义初始化的时候,都要初始化static成员变量。
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
- 静态的成员变量和静态成员函数一样,不属于任何一个对象,存在静态区。可以通过类名和对象去调用。
问题
- 静态成员函数可以调用非静态成员吗?
答:不能,因为静态成员函数没有 this 指针。 - 非静态成员函数可以调用类的静态成员函数吗?
答:可以,因为非静态成员函数有 this 指针。
<六>友元函数
问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
class Date
{
public:
Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void print()
{
cout << _year <<'/' << _month <<'/' << _day << endl;
}
friend ostream& operator<<(const ostream& out, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(const ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
}
int main()
{
return 0;
}
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
说明:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
<七>友元类
友元类和友元函数差不多,友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承,在继承位置再给大家详细介绍。
class Time
{
public:
Time(int hour,int minute ,int second )
:_hour(hour)
,_minute(minute)
,_second(second)
{}
friend class Date; //这个就是声明一下 Date 是我的友元类。
private:
int _hour;
int _minute;
int _second;
};
class Date//这个类中的所有成员函数 都可以直接访问Time类的私有成员。
{
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
, t(3,3,3)
{
}
Date& operator=(const Date& d)//拷贝构造。
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void print()
{
cout << _year << '/' << _month << '/' << _day << endl;
cout << t._hour << '/' << t._minute << '/' << t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time t;
};
int main()
{
Date d1;
d1.print();
return 0;
}
注意:
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。比如上述 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接访问 Time 类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。
- 友元关系不能传递,如果 C 是 B 的友元, B 是 A 的友元,则不能说明 C 是 A 的友元。
<八>内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
- 1. 内部类可以定义在外部类的public、protected、private都是可以的。
- 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- 3. sizeof(外部类)=外部类,和内部类没有任何关系。
- 相当于两个独立的类 ,只是内部的类 ,受外部类域的限制。
class A//会生成一个默认构造函数。
{
public:
class B//B类天生就是A的友元
{
public:
void foo(const A& a)
{
cout << a.k << endl;//ok
cout << a.h << endl;//ok
}
private:
int _b;
};
private:
static int k;
int h;
};
int A::k = 0;
int main()
{
cout << sizeof(A) << endl;//4//这里的A的大小和B无关,和静态变量也无关。
cout << sizeof(A::B) << endl;//4//注意这里不知道A域编译器根本不认识B。
A::B b;//要用B去定义,必须得指定域,必须指定A域
b.foo(A());//通过实例化对象可以突破A域。
//A()是匿名对象,生命周期只有一行。
return 0;
}
<九>匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
//
//
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}