一、基础知识
1. 概念
类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。
与结构体一样,类只是一种复杂数据类型的声明,不占用内存空间。而对象是类这种数据类型的一个变量,或者说是通过类这种数据类型创建出来的一份的数据,所以占用内存空间。
类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++ 语法本身并不提供现成的类的名称、结构和内容。
2. 创建
1)创建类
C++ 中使用关键字 class 来定义类, 其基本形式如下:
#include <iostream>
using namespace std;
class Student
{
public:
// 成员函数
void Display()
{
cout << _name << ":" << _age << ":" << _sex << ":" << endl;
}
private:
// 成员变量
char* _name;
int _age;
char* _sex;
};
class 是 C++ 中新增的关键字,专门用来定义类。Student 是类的名称;类名的首字母一般大写,以和其他的标识符区分开。{ } 内部是类所包含的成员变量和成员函数,它 们统称为类的成员(Member);由 { } 包围起来的部分有时也称为类体,和函数体的概念类似。
访问限定符 public 也是 C++ 的新增关键字,它只能用在类的定义中,表示类的成员变量或成员函数具有“公开”的访问权限。 与之对应的是 private 关键字,有“保护”权限。
注意在类定义的最后有一个分号 ; ,它是类定义的一部分,表示类定义结束,不能省略。
整体上讲,上面的代码创建了一个 Student 类,它包含了 3 个成员变量和 1 个成员函数。
类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,才可以赋值。
2)创建对象
有了 Student 类后,就可以通过它来创建对象了,例如:
Student zll; //创建对象
Student 是类名,zll 是对象名。这和使用基本类型定义变量的形式类似:
在创建对象时,class 关键字可要可不要,但是出于习惯我们通常会省略掉 class 关键字,例如:
class Student zll; //正确
Student zll; //正确
除了创建单个对象,还可以创建对象数组:
Student Stu[50];
3)访问类的成员
创建对象以后,可以使用点号 . 来访问成员变量和成员函数,这和通过结构体变量来访问它的成员类似,如下所示:
#include<iostream>
using namespace std;
//类通常定义在函数外面
class Student{
public:
//成员变量
char *name;
int age;
float score;
public:
//成员函数
void Display(){
cout << name << "年龄是" << age << ",成绩是" << score << endl;
}
};
int main(){
//创建对象
Student stu;
stu.name = "兰兰";
stu.age = 21;
stu.score = 95.5f;
stu.Display();
system("pause");
return 0;
}
运行结果如下:
stu 是一个对象,占用内存空间,可以对它的成员变量赋值,也可以读取它的成员变量。
3. 访问限定符
公有成员 public:在程序的任何地方都可以被访问。
私有成员 private:只能被成员函数和类的友元访问,实行信息隐藏的类把其数据成员声明为 private。
保护成员 protected:对派生类 derived class 就像 public 成员一样,对其他程序则表现得像 private。
如果紧跟在类名称后面声明私有成员,则关键字 private 可以省略,例如:
class person
{
int age,id; //age和id是私有成员,即为private类型
};
4. 面现对象封装性
面向对象有三大特性:封装、继承、多态。
(1)封装:将方法和数据封装在类里面,可以根据访问限定符的使用保证数据的安全性,隐藏了方法的实现细节,也方便使用。
(2)继承:对已有类增加属性和功能或进行部分修改来建立新的类,是实现代码的复用的重要手段,继承是类型之间的关系建模。
(3)多态:在面向对象的程序里面,同一个消息被不同的对象接受后可以导致不同的行为,是接口的多种不同的实现方式。
封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。封装存在的理由就是重用,重用就是写的一个代码库可以在很多地方重用,而且易于扩充。封装——Encapsulation,隐藏对象的属性和实现细节,仅对外提供公共访问方式。
1)优点:
(1)将变化隔离;
(2)便于使用;
(3)提高复用性;
(4)提高安全性;
2)原则:
(1)将不需要对外提供的内容都隐藏起来;
(2)把属性都隐藏,提供公共方法对其访问。
5. 对象大小的计算
C++ 中类的成员函数,静态成员是不占类的大小的。类的大小等于基类的大小 + 子类个非静态成员变量的大小 + 非虚基类大小。
对象大小的计算依旧遵守内存对齐的原则 。
结构体内存对其规则:
1)第一个成员在与结构体变量偏移量为0的地址处。
2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
//对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。 VS 中默认值为 8 ,gcc 中默认值为 4
3)结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
4)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
空类对象(无成员变量的类)的大小也不一样,VC 中为 1。
实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以大小为1。 在不同的编译器上得到的结果可能不同,但是不管类是否为空类,是否有成员变量,这个类在创建对象的时候都是需要分配空间的。
二、四个默认成员函数
1. 构造函数
成员变量为私有的,要对它们进行初始化,必须用一个公有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数(constructor)。
构造函数是特殊的成员函数,其特征如下:
1)函数名与类名相同。
2)无返回值。
3)对象构造(对象实例化)时系统自动调用对应的构造函数。
4)构造函数可以重载。
5)构造函数可以在类中定义,也可以在类外定义。
6)如果类定义中没有给出构造函数,则 C++ 编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。
7)无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。
举例:
1)无参构造函数 & 带参的构造函数
class Date
{
public :
// 1.无参构造函数
Date ()
{}
// 2.带参构造函数
Date (int year, int month , int day )
{
_year = year ;
_month = month ;
_day = day ;
}
private :
int _year ;
int _month ;
int _day ;
};
void TestDate1 ()
{
Date d1 ; // 调用无参构造函数
Date d2 (2015, 1, 1); // 调用带参的构造函数
Date d3 (); // 注意这里没有调用 d3 的构造函数定义出 d3
}
2)带缺省参数的构造函数
class Date
{
public :
// 3.缺省参数的构造函数
Date (int year = 2000, int month = 1, int day = 1)
{
_year = year ;
_month = month ;
_day = day ;
}
// 4.半缺省参数的构造函数(不常用)
Date (int year, int month = 1)
{
_year = year ;
_month = month ;
_day = 1;
}
private :
int _year ;
int _month ;
int _day ;
};
void Test()
{
Date d1 ; // 调用缺省构造函数
Date d2 (2015, 1, 9); // 调用缺省构造函数
}
2. 拷贝构造函数
创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数(Copy Constructor)。
拷贝构造函数是特殊的构造函数,特征如下:
1)拷贝构造函数其实是一个构造函数的重载。
2)拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。
3)若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
举例:
class Date
{
public :
Date()
{}
// 拷贝构造函数
Date (const Date& d)
{
_year = d ._year;
_month = d ._month;
_day = d ._day;
}
private :
int _year ;
int _month ;
int _day ;
};
void TestDate1 ()
{
Date d1 ;
// 下面两种用法都是调用拷贝构造函数,是等价的。
Date d2 (d1); // 调用拷贝构造函数
Date d3 = d1; // 调用拷贝构造函数
}
3. 析构函数
当一个对象的生命周期结束时,C++ 编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数(destructor) 。
构造函数是特殊的成员函数,其特征如下:
1)析构函数在类名加上字符~。
2)析构函数无参数无返回值。
3)一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
4)对象生命周期结束时,C++ 编译系统系统自动调用析构函数。
5)注意析构函数体内并不是删除对象,而是做一些清理工作。
举例:
class D ate
{
public :
// 析构函数
~Date()
{}
private :
int _year ;
int _month ;
int _day ;
};
4. 运算符重载
为了增强程序的可读性,C++ 支持运算符重载。
运算符重载特征:
1)operator + 合法的运算符 构成函数名(重载<运算符的函数名:operator<)。
2)重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
5个C++不能重载的运算符: .* / :: / sizeof / ?: / .
5. 赋值运算符重载
拷贝构造函数是创建的对象使用一个已有对象来初始化这个准备创建的对象;赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
举例:
class Date
{
public :
Date()
{}
// 拷贝构造函数
Date (const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{}
// 赋值操作符的重载
Date& operator = (const Date& d)
{
if (this != &d)
{
this->_year = d. _year;
this->_month = d. _month;
this->_day = d. _day;
}
return *this ;
}
private:
int _year ;
int _month ;
int _day ;
};
void Test ()
{
Date d1 ;
Date d2 = d1; // 调用拷贝构造函数
Date d3 ;
d3 = d1 ; // 调用赋值运算符的重载
}
三、隐含的 this 指针
1. 每个成员函数都有一个指针形参,它的名字是固定的,称为 this 指针,this 指针是隐式的。(构造函数比较特殊,没有这个隐含 this 形参)
2. 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参 this 指针。
3. this 指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加 this 指针的参数定义,也不能在调用时显示传递对象的地址给 this指针。
举例:
void Display (Date* this)
{
//cout <<_year<< "-" <<_month << "-"<< _day <<endl;
cout <<this->_year<< "-" <<this->_month << "-"<<this->_day <<endl;
}