类:class(数据+函数)
类是一种复杂的数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。这有点像C语言中的结构,唯一不同的就是结构没有定义所说的“数据相关的操作”,“数据相关的操作”就是我们平常经常看到的“方法”,因此,类具有更高的抽象性,类中的数据具有隐藏性,类还具有封装性。
1. public成员可从类外部直接访问,private(只能在类的里面进行访问)/protected成员不能从类外部直接访问。
2. 每个限定符在类体中可使用多次,它的作用域是从该限定符出现开始到下一个限定符之前或类体结束前。
3. 类体中如果没有定义限定符,则默认为私有的。
4. 类的访问限定符体现了面向对象的封装性。
类的一般定义格式如下:
class <类名>
{
public: //三种访问限定符
private:
protected:
};
<各个成员函数的实现>
类的作用域:
1. 每个类都定义了自己的作用域,类的成员(成员函数/成员变量)都在类的这个作用域内,成员函数内可任意访问成员变量和其它成员 函数。
2. 对象可以通过 . 直接访问公有成员,指向对象的指针通过 -> 也可以直接访问对象的公有成员。
3. 在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
类内声明定义和类声明类外定义:(二者取一即可)
class Student
{
public:
char* _name;
char* _sex;
int _tell;
//void Display(); //类内声明
void Display() //类内声明定义
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
private:
/*char* _name; //私有域内的对象在类外是直接访问不到的,这体现了c++的封装性(可通过其他方式访问)
char* _sex;
int _tell;*/
protected:
};
/*void Student:: Display() //类外定义
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}*/
int main()
{
Student a1;
a1._name = "zhang";
a1._sex = "男";
a1._tell = 188;
a1.Display();
system("pause");
return 0;
}
class Student
{
public:
char _name;
char _sex;
int _tell;
void Display()
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
};
class Student
{
public:
char* _name;
char* _sex;
int _tell;
void Display()
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
}
};
通过类里面对象的变化和运算结果可以看出:
1:类的大小只和对象有关,与成员函数无关。
2:求类的大小是存在字节对齐现象(就像结构体的字节对齐)。
为什么要字节对齐?
简单的说就是用空间换时间。牺牲一部分内存让程序运行的更快。因为cpu每次访问以某个数的整数倍进行读取数据。内存对齐是方便cpu工作。
当类里面什么也没有定义的时候计算出来的大小是多少?
通常情况下是1。为甚么不是0呢?我们可以这样思考,如果是0就代表这个类不存在,然而事实是存在的,只是没有定义对象,那么就让1代表这个类是存在的。
隐含this指针:
1. 每个成员函数都有一个指针形参,它的名字是固定的,称为this指针,this指针是隐式的。(构造函数比较特殊,没有这个隐含this形 参)
2. 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参this指针。
3. this指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加this指针的参数定义,也不能在调用时 显示传递对象的地址给this指针。
class Student
{
public:
char* _name;
char* _sex;
int _tell;
void Display() //void Display(student* this) (this指针指向调用该函数的对象)
{
cout<<_name<<" "<<_sex<<" "<<_tell<<" "<<endl;
//cout<<this->_name<<" "<<this->_sex<<" "<<this->_tell<<endl;
}
};
函数中的6个默认成员函数:
1构造函数:
2拷贝构造函数:
3析构函数:
4赋值运算符的重载:
5取地址操作符的重载:
6const修饰取地址操作符的重载:
构造函数:
1. 函数名与类名相同。
2. 无返回值。
3. 对象构造(对象实例化)时系统自动调用对应的构造函数。
4. 构造函数可以重载。
5. 构造函数可以在类中定义,也可以在类外定义
6. 如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动 生成缺省的构造函数。
7. 无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。
class Date
{
public:
//构造函数,给对象分配空间,初始化
//无参构造函数
Date()
{}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省构造函数
Date(int year = 2008, int month = 8, int day = 8)//如果不传参则默认缺省参数,如果传参则用传递参数
{
_year = year ;
_month = month ;
_day = day ;
}
//半缺省构造函数
Date(int year, int month = 8, int day = 8) //需要将未缺省参数(year)传入,其他参数不传则默认,传则利用传入参数。
{
_year = year;
_month = month;
_day = day;
}
void Display() //类内声明定义
{
cout<<_year<<" "<<_month<<" "<<_day<<" "<<endl;
}
private :
int _year ;
int _month ;
int _day ;
};
拷贝构造:(利用同类对象进行初始化的特殊拷贝构造)
1. 拷贝构造函数其实是一个构造函数的重载。
2. 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。(思考为什么?)
3. 若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会,依次拷贝类成员进行初始化。
class Date1
{
public:
Date1(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date1(const Date1& d) //参数中包含一个隐含的this指针,指向目的对象
{
_year = d._year; //this->_year = d._year;
_month = d._month; //this->_month = d._month;
_day = d._day; //this->_day = d._day;
}
void Display()
{
cout<<_year<<" "<<_month<<" "<<_day<<" "<<endl;
}
private:
int _year ;
int _month ;
int _day ;
};
void test1()
{
Date1 d1(2017,7,1); //构造
d1.Display();
Date1 d2(d1); //拷贝构造
d1.Display();
Date1 d3 = d2; //拷贝构造 (d2(d1)和 d3 = d2都是拷贝构造,形式不同,意义一样)
d1.Display();
}
int main()
{
test1();
system("pause");
return 0;
}
析构函数:(当一个对象生命周期结束时就调用析构函数来销毁这个函数,构造函数(包括拷贝构造)运行多少次,析构函数就要运行 多少次)
1. 析构函数在类名加上字符~。
2. 析构函数无参数无返回值。
3. 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 注意析构函数体内并不是删除对象,而是做一些清理工作。
class Date2 {
public :
// 默认的析构函数 如果程序中存在动态开辟的空间需要自己重新写析构函数
~Date2()
{}
private :
int _year ;
int _month ;
int _day ;
};
运算符重载:(拷贝构造和赋值运算符重载很相像但还是有区别)
1. operator+ 合法的运算符 构成函数名(重载<运算符的函数名:operator< )。
2. 重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
//拷贝构造
Date2(const Date2& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
Date2& operator=(const Date2& d)
{
if(this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
二者区别:
拷贝构造函数是创建的对象,使用一个已有对象来初始化这个准备创建的对象。(用一个存在的对象去初始化一个不存在的对象)
赋值运算符的重载是对一个已存在的对象进行拷贝赋值。(两个对象都存在)
Date2 d1;
Date2 d2 = d1; //拷贝构造
Date2 d3;
d3 = d1; //赋值运算符重载