友元:
friend关键字:
1.友元函数:
语法: friend在类中修饰一个函数。 那么这个函数定义到全局时仍可以访问该类的私有成员的。 (定义时不需要用friend,而是需要在全局定义前,在要使用的成员的类里进行声明,声明时用friend修饰该函数。 在类中声明友元时,友元是public还是private都可以,都不影响友元的声明)。 注意friend修饰的函数并不是类中的函数,而是一个定义在类外的普通函数。 友元函数不能被被const修饰,指不能 friend int (Date& d) const;
Eg:
而流插入是在全局中定义的。
Eg:
此处的d._year以及d._month , d._day不会报错。但是如果没有声明友元,那么此处访问私有成员是不可以的。
- 友元类: 作用: 使可以在一个类里面使用另一个类的成员。
语法: friend class 类名;
Eg:
注意:在Time类中声明了 Date是Time的友元。 那么可以在Time类中可以使用Date类中的成员。 但这时并不能在 Date 类里面使用 Time类的成员。 只有在Date 类中声明了 Time是 Date的友元,那么才能在Date中使用Time的成员。(即 友元类的声明是单向的, 这一个是另一个的友元 并不一定 代表 另一个就是这一个的友元)
友元不能传递。 即 a 是 b 的友元 , b 是 c的友元。 但并不代表 a 就是 c的友元
内部类:
内部类和所在的类 都是 独立的类,内部类仅仅是放到所在的类的类域里面而已。 唯一与其定义到全局域不同的 是 这个类定义在一个类域中。 即内部类的特点就是在 类 域中 , 且其 内部类 天生 就是 该所在类 的友元。
Eg: 注意: sizeof(A) 的大小是 4。 即没有算内部类B的大小, 这是因为B和A是两个独立的类,唯一的关系就是 B 放到了A的类域中 且 B类 天生就是 A类的 友元。
Const成员函数:
原本的 this指针的类型是 类 类型* const。 即 this 指针本身不能++或者--,即this指针本身不能改变。
当 const Date d1(2024,4,14); 后续如果传入 d1.Print(); 那么传入的实参类型是 const Date* 的。 而this指针的类型是 Date* const this; 这就导致权限放大。(因为const Date*的类型, 只能读,可以进行解引用,但不能通过解引用修改其值,即不能进行*this += 1; 这种操作。) (而本身this指针是 Date* const类型,这是可以 *this += 1;的,但不能修改this指针本身,即不能进行this += 1这种操作。)(因此实参类型为const Date* ,而形参类型为Date* const。这就是权限放大了,会报错)
Eg1:
Eg2:
在成员函数后 加入const , 可以使this指针的类型 修改为 const Date* const 类型。而从const Date* 传入到 const Date* const类型,这是权限的缩小,是被允许的
取地址操作符的重载:
这两个运算符的重载一般不需要显式写,默认生成的即可使用
Eg:
初始化列表:构造函数的函数名和函数体。 初始化列表初始化的是存储在对象中的成员
记该类的成员有 3个不同类型的自定义类型 _s1 , _s2 , _s3 和一个内置类型 _a
语法: : _s1(_s1的构造函数的对应参数) , _s2(_s2的构造函数的对应参数) , (_s3的构造函数的对应参数) , _a(要初始化的值)
Eg:
注意:对于用初始化列表初始化类型 是 相当于 在定义成员变量的时候进行初始化的, 因此const修饰的成员只能在初始化列表中进行初始化。
只能在初始化列表中进行初始化的成员类型:
- 引用类成员变量
- 没有默认构造函数的成员(该成员必须显式调用其构造函数的时候,必须在初始化列表中进行初始化)
- Const修饰的成员
如果没有显式写出初始化列表(或者没有写全。即写了 : _a(),_b(a) 那么后续也会默认生成),那么会自动自动生成一个初始化列表,但是默认生成的初始化列表对于内置类型 只会去调用默认构造。(即生成的为 : _a() , _b() , _c() , _m) ,因此如果内置类型没有默认构造函数,那么会编译报错 。
内置类型的缺省值实质上是给初始化列表使用的。 即如果成员_m在声明的时候给予缺省值 即int _m = 10; 那么如果没有显式写初始化列表,在编译器自动生成或者补齐的时候,内置类型会处理为 _m(10) 即 _m(缺省值)
构造函数是先走初始化列表再走函数体。
在实践中,能用初始化列表进行初始化尽量有初始化列表,不方便的话再在函数体中进行初始化。
初始化列表中对成员的初始化顺序是和成员声明的先后顺序保持一致的,与初始化列表中成员出现的先后顺序没有任何关系。
Eg: 此处输出的是 1 和 随机值 。 原因:先用_a1初始化_a2,此时的_a1为随机值。然后用 1 初始化 _a1。
因此推荐 初始化列表 中 成员出现的先后顺序 和 成员声明的先后顺序保持一致。 这样可增强可读性
声明时给缺省值的几种情况: 此处Stack 能这样给的前提是 Stack 类里有单参的构造函数。_a1,_a2也是同理。 因为实质上缺省值是 给初始化列表使用的。 走的其实是初始化列表.即_b1(1) , _ptr((int*)malloc(40)) , _pushst(10) , _a1(1) , _a2( { 1 , 2 } ) , _a3(_a2)
此处是声明给缺省值, 而不是初始化
单参数构造函数:
单参数的构造函数支持隐式类型转换。
此处A类中的构造函数是
A(int a)
{
_a = a
}
此处的 A aa3 = 3; 的是把 3 隐式转换,先构造出一个 类为 A 的临时对象(调用的该类的构造函数,从而构造出的临时对象)。 然后这个临时对象再拷贝构造 到 aa3 中。
如果要引用类型的话,应该是 const A& , 因为此处引用的实质上是 3 隐式转换 构造出的一个构造出的 类 为A 的临时对象, 而临时对象具有常性,因此要用const 修饰 A& 。 否则是权限的放大,出现编译错误。
编译器在遇到 连续的构造 + 拷贝构造 à 会优化为 直接构造。即 A aa3 = 3; 这里会被编译器优化为 直接构造。 但是它实质上的过程 就是上述的 先构造临时对象,再拷贝构造到 aa3 中。
推荐 要引用时 尽量用 const A& , 这样可以使得在st.Push(2); 时也可以传入,原因是2隐式转换 构造出一个A类的 临时对象, 而临时对象具有常量,因此要用const A& ,而不能用 A。 而已用const A& 也保证了传入的 实参不会被修改
取消单参数构造函数允许隐式转换的方法:
Explicit关键字:
Eg:
Explicit A(int a)
{
_a = a;
}
此后 A a1 = 2; 是不被允许的。 因为 不允许 2 隐式转换构造 临时对象了。所以会编译错误。
静态成员: 静态成员变量不存储对象中,而是存储到静态区。
Static修饰的成员。
静态成员不能给缺省值。 因为缺省值是给初始化列表使用的。而初始化列表初始化的是存储到对象中的成员。 而静态成员存储到静态区,因此静态成员不能给缺省值。
静态成员属于所有对象。 因此可以在使用的时候可以 A : : _scount 。
即可以 cout << A : : _scount << endl; 也可以 cout << a._scount << endl;(不过如果_scount是private的 ,那么此处仍是不能访问该成员的)
而且静态成员存储到静态区。所以对象使用的 静态成员为同一个静态成员。
即 A a1 ; A a2; A a3: 并且 int A : : _scount = 0 ; 那么 a1._scount , a2._scount , a3._scount 的_scount 是同一个,同一份空间。
不过在类中仍可以通过函数对其进行操作(但是静态成员只能在全局域中定义,即进行初始化)。
静态成员函数: 静态成员函数是没有this指针的, 因此静态成员函数是不能访问 非静态成员变量的。 而静态成员变量定义在静态区,因此仍可以使用静态成员变量。
那么调用时 可以 类名: : 函数名 来调用(当然,前提是该函数是公用的即public的)。那么就可以通过这个函数来返回静态成员变量的值,从而拿到静态成员变量的值(用于当静态成员为private的时候)
对于元素为自定义类型的数组:
Eg:
A arr[10]; 此处是调用了10此默认构造函数的,每个元素在创建时都调用了其默认构造函数。 因此 对于 1 + 2 + 3 +……+n 的值,可以这样写。