前言:
前言:
上一篇博客我们介绍了类与对象中的几类默认成员函数,这篇让我们继续来学习类与对象吧!
个人主页:Pianeers
文章专栏:C++
如果有问题,欢迎评论区讨论!
希望能帮到大家,求点赞,关注加收藏!
目录
一、初始化列表
我们之前的默认构造函数是用来初始化的,可以在函数内给成员变量初始化,但如果有一些特殊的成员呢?如:const成员变量,没有默认构造的类类型变量。
这时就要从之前说起,成员变量在private中只是声明并没有被定义,当创建一个对象时,成员变量需要被定义,这时要介绍初始化列表。
之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅式,就是初始化列表,所以初始化列表不仅可以定义还可以初始化。
所以成员变量都要初始化列表中定义,初始化没有太多限制,但是有一些必须要在初始化列表中初始化。
①const成员变量
② 没有默认构造的类类型变量
③引⽤成员变量
它们都是被要求在定义的同时就要进行初始化的。
初始化列表的特点:
①初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
②每个成员变量在初始化列表中只能出现⼀次,因为语法理解上初始化列表认为是每个成员变量定义初始化的地⽅。
③成员变量的初始化与在类中的声明顺序有关,与在初始化列表中的顺序无关。
以下还有几个需要注意的点:
①C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
其中该缺省值的作用是:如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化。
class Time { public: Time(int hour) :_hour(hour) { cout << "Time()" << endl; } private: int _hour; }; class Date { public: void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: // 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的 // 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化 int _year=2024; int _month=7; int _day=16; Time _t=1; const int a=1; int* tmp = (int*)malloc(12); }; int main() { int i = 0; Date d1; d1.Print(); return 0; }
我个人建议能使用初始化列表初始化就用初始化列表,因为每个成员变量都要在初始化列表中定义 。
初始化列表中情况分析:
1.在初始化列表中初始化的成员。(显示写出来的)
2.没在初始化列表中初始化的成员。(没有写出的)
→→①在声明时有缺省值时,用缺省值进行初始化。
→→②没有缺省值。
{
→→→①内置类型,大概率随机值,看编译器。
→→→②自定义类型,调用默认构造,没有则报错。
}
二、类型转换
C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
如下:
当1这个类型直接拷贝构造给a时,1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造a。
当你不希望这种隐式类型转换发生,可以构造函数前⾯加explicit就不再⽀持隐式类型转换
class A
{
public:
A(int a)
:_a1(a)
{
}
void print()
{
cout << _a1 << _a2 << endl;
}
private:
int _a1= 1;
int _a2 = 2;
};
int main()
{
A a = 1;
a.print();
return 0;
}
三、static
在之前的C语言的学习中,我们了解了static关键字的作用是在程序中标记变量、方法或代码块为静态,同样的它在类中同样可以标记成员变量为静态。
static的特点:
①⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。
②⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针,所以静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
③⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
④静态成员也是类的成员,受public、protected、private 访问限定符的限制。可以通过类名::静态成员 或者 对象.静态成员访问任意的静态成员变量和静态成员函数。
⑤静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区;静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
class B
{
public:
B()
{
++a;
}
B(const B& c)
{
++a;
cout << "kaobe" << endl;
}
int rmid()
{
//⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数
mid();
return a;
}
static int mid()
{
//只能访问静态的
return a;
}
private:
//类里面声明
static int a;
int g = 0;
};
//类外面初始化
//突破类域就可以访问静态成员
int B::a=0;
int main()
{
B b;
cout << b.mid() << endl;
B d(b);
cout << d.mid() << endl;
}
四、友元
当我们创建一个函数不在类以内时,因为成员变量被private修饰,所以成员变量不可以在函数内被访问,这时我们可以通过友元的方式,友元提供了⼀种突破类访问限定符封装的⽅式。
友元是一种很有效的方式,但是并不推荐使用,因为它会破坏封装
友元的特点:
①友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
②外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
③友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
④⼀个函数可以是多个类的友元函数。
⑤友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
⑥友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
class B;
class A
{
friend void func(const A& aaa,const B& bbb);
private:
int a1;
int a2;
};
class B
{
friend void func(const A& aaa, const B& bbb);
private:
int b1;
int b2;
};
void func(const A&aaa,const B&bbb)
{
cout << aaa.a1 << aaa.a2 << endl;
cout << bbb.b1 << bbb.b2 << endl;
}
int main()
{
A aa;
B bb;
func(aa, bb);
}
五、内部类
如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。
内部类的特点:
①内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类,sizeof(外部类)=外部类。②内部类默认是外部类的友元类。但内部类可以直接访问外部类中的static成员
③内部类可以定义在外部类的public、protected、private都是可以的
class A
{
private:
static int _k;
int _h = 1;
public:
class B // B默认就是A的友元
{
public:
void foo(const A & a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}
六、匿名对象
⽤ 类型(实参) 定义出来的对象叫做匿名对象,相⽐之前我们定义的 类型 对象名(实参) 定义出来的 叫有名对象;匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。
class A
{
public:
A(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//无类型名
A(2024, 7, 18);
}
七、拷贝对象时优化
现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传值 过程中可以省略的拷⻉。
class A
{
public:
A(int a=100)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A & aa)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a=20;
};
A func1()
{
A c;
return c;
}
int main()
{
//A a;
//A b(a);
func1();
}
本来函数调用:先构造c、返回值时创建一个c的临时对象。应该是两次构造,2次析构。
但优化后只有一次构造和一次析构