类和对象
概述
类和结构体
为了兼容C语言,我们可以用struct来定义类,但是结构体里面是不能出现函数的,类却可以,我们可以理解为,结构体是一种数据的类型,而类是对象的类型,类能完成结构体的所有功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct Student
{
void SetStudentInfo(const char* name, const char* gender, int age)
{
strcpy( _name, name);
strcpy( _gender, gender);
_age = age;
}
void PrintStudentInfo()
{
cout<<_name<<" "<<_gender<<" "<<_age<<endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main()
{
Student s;
s.SetStudentInfo("Peter", "男", 18);
return 0;
}类和对象
类可以当成是对象的抽象,而对象是类的具体化实现。举个例子,我们可以说人类是一个类,而我们每个人就是一个对象,是人类这个概念的具体化。
类不占用内存,但是对象占用。- 类的函数
类的函数可以在类里直接实现,也可以在类里写声明,在类外定义。如果一个函数代码量少并且经常用到建议直接放在类里实现,因为在类里实现的函数,编译器可能会将其当成内联函数处理,会在一些地方提高运行效率。
在类外实现的函数必须带上类域限定符来表示该函数属于哪个类。
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。(静态成员函数除外) - 类的访问限定符
类有三个访问限定符:public private protected
一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。(struct定义的类默认是public)
public修饰的成员可以在类外直接访问,另外两个不行。 - 类与成员变量
尽量避免成员函数的参数与成员变量同名,因为成员变量在类中具有全局作用域属性,会造成一些地方可读性差的问题。 - 类对象的大小
基本与结构体计算大小一致,但是成员函数不占用对象大小,并且用空类或者只含函数的类实例化的对象的大小不为零而唯一(为了能够唯一标识该对象)。 - 类的this指针
C++编译器给每个“成员函数“增加了一个隐藏的指针参数,让该指 针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访 问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。 - this指针的特性
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值操作符重载
- 取地址操作符重载
- const修饰的取地址操作符重载
构造函数
对于一个类,可以通过成员函数等公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数是特殊的成员函数,其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象构造(对象实例化)时编译器自动调用对应的构造函数。
- 构造函数可以重载
1 | class Date |
- 构造函数可以在类中定义,也可以在类外定义。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定 义编译器将不再生成
1 | class Date |
- 无参的构造函数和全缺省的构造函数都称为缺省构造函数,并且缺省构造函数只能有一个
1 | // 缺省构造函数 |
- 对于含有其他类的类执行默认构造函数时,会对其他类的默认构造函数进行调用。
析构函数
析构函数:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。(而不是删除对象,析构函数是在对象销毁前自动调用的)
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
拷贝构造函数
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用(形参也会调用拷贝构造函数引发无穷递归)。
- 若未显示定义,系统会默认生成默认的拷贝构造函数。 默认的拷贝构造函数会按照成员的声明顺序依次 拷贝类成员进行初始化
赋值操作符重载
类似于拷贝构造函数,为了使代码可读性变强,可以方便的对对象用另一个对象赋值。
深拷贝与浅拷贝
对于系统默认的拷贝构造与赋值函数,都只是将变量值一一拷贝的浅拷贝,对于动态开辟的空间,必须使用深拷贝,否则在对象销毁时会使程序崩溃。
取地址与const修饰的取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Date
{ public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如想让别人获取到指定的内容.
初始化列表
构造函数调用之后,对象中已经有了一个初始值,但是我们不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内 可以多次赋值。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个”成员变量”后面跟一个放在括 号中的初始值或表达式。
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
- 尽量避免使用成员初始化成员
- 类中包含以下成员,一定要放在初始化列表位置进行初始化:
1.引用成员变量
2.const成员变量
3.类类型成员(该类有非缺省的构造函数)
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。(用关键字explicit可以禁止这样的隐式转换)
友元
友元分为:友元函数和友元类
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。
说明:
- 友元函数可访问类的私有成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
友元的优缺点
优点:提高了程序运行效率
缺点:破坏了类的封装性和隐藏性
注意:
友元关系是单向的,不具有交换性。
友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。特殊友元类:内部类
直接定义在其他类里面的类是该类的内部类 - 内部类是外部类的友元类,反之不然.
2 .内部类可以直接访问外部类的静态成员,枚举成员并且不需要外部类的名字。 - sizeof(外部类) = 外部类大小,与内部类无关。
类的静态成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;
用static修饰的 成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化 - 静态成员为所有类对象所共享,不属于某个具体的实例
- 静态成员变量必须在类外定义,定义时不添加static关键字
- 类静态成员即可用类名::静态成员或者对象.静态成员来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值, const修饰符等参数