类和对象
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。在c++中通常使用类(class)代替结构体。
注意:C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
class Name
{
//成员函数及变量
};
类的访问限定符及封装
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
说明
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)。
class Preson
{
publc:
void showPreson()
{
cout<<_name << " " << _gander << " " << _age << endl;
}
public:
char* _name;
char* _gander;
int _age;
};
封装
面向对象的三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
class Preson
{
public:
//方法
private:
//数据
}
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
class Preson
{
public:
void showPreson();
public:
char* _name;
char* _gander;
int _age;
}
void Preson::showPreson()
{
cout << _name << " " << _gander << " " << _age << endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化
- 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
- 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什
么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
void test()
{
Preson a;//实例化
a._name = "张三";
a._gander = "男";
a._age = 13;
a.showPreson();
}
类的大小
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8,gcc中的对齐数为4 - 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
this指针
概念
C++编译器给每个“成员函数“增加了一个隐藏的指针参数,让该指
针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
class Preson
{
public:
void showPreson();
void setPreson(char* name,char* gander,int age);
public:
char* _name;
char* _gander;
int _age;
};
void Preson::showPreson()//Preson::showPreson(*this)
{
cout << _name << " " << _gander << " " << _age << endl;
// this->_name this->_gander this->_age
}
void Preson::setPreson(char* name,char* gander,int age)
{
_name = name;//this->_name=name
_gander = gander;
_age = age;
}
void test()
{
Preson a;
a.setPreson("张三", "男", 13);//对函数来说,this指针是a。
a.showPreson();
cout << "类的大小为"<< sizeof(a) << endl;
}
特性
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
两个问题
1.this指针存在哪里?
编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。
2.this指针可以为空吗?
可以为空,当我们在调用函数的时候,如果函数内部并不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空(当我们在其中什么都不放或者在里面随便打印一个字符串),如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针引用)就跟C中一样不能进行空指针的引用。
类的六个默认函数
任何一个类(包括空类)在我们不写的情况下,都会自动生成下面6个默认成员函数。
class Name{ };
构造函数
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。虽然构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
class Date
{
public:
//Date() {}
Date(int year, int month, int date);//构造函数
void DateInfo();
private:
int _year;
int _month;
int _date;
};
Date::Date(int year,int month,int date)
{
_year = year;
_month = month;
_date = date;
}
void Date::DateInfo()
{
cout << _year << " " << _month << " " << _date << endl;
}
void test()
{
//Date x;如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
Date y(2005, 12, 15);
y.DateInfo();
}
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year=1999,int month=12,int day=12)
:_year(year)
, _month(month)
, _day(day)
{
cout << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date a;
}
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 类类型成员(该类没有默认构造函数)
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
explicit关键词
对于单个参数的构造函数,还具有类型转换的作用。用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。
class Date
{
public:
explicit Date(int year=1999,int month=12,int day=12)
:_year(year)
, _month(month)
, _day(day)
{
cout << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date a;
}
static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量。
- 静态的成员变量一定要在类外进行初始化。
- 静态的成员变量不能在构造函数初始化列表位置进行初始化
- 静态的成员变量不包含在各个对象内,但每个对象都可以访问是共享的,静态的成员变量不会影响类的大小
- 可通过对象访问,也可以通过类名::静态成员变量进行访问
用static修饰的成员函数,称之为静态成员函数。 - 静态成员函数不可通过对象调用
- 不可访问非静态成员变量
- 没有this指针
- 不能设置为const类型
注意:静态成员函数不可以调用非静态成员函数
class Date
{
public:
explicit Date(int year = 1999, int month = 12, int day = 12)
:_year(year)
, _month(month)
, _day(day)
{
cout << _year << "-" << _month << "-" << _day;
}
// 非静态成员变量,可以在成员声明时给缺省值。
private:
int _year;
int _month;
int _day;
static int n;
};
int Date::n = 2;
void test()
{
Date a;
}
析构函数
概念
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
特性
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 不能重载
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
class String
{
public:
String(char* str)
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String"<<endl;
free(_str);
}
private:
char* _str;
};
void test()
{
String t("hello world");
}
拷贝构造函数
概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特性
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
1)按传值传递过程中不断创建临时变量。
2)通过拷贝函数不断创建临时变量。
class Date
{
public:
Date(int year = 1999, int month = 11, int day = 20)
{
_year = year;
_month = month;
_day = day;
cout << _year << " " << _month << " " << _day << endl;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date a(2000, 13, 35);
Date b(a);
}
- 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。(在不涉及资源时可用,若涉及资源可能会出现错误)
赋值运算符重载
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
class Date
{
public:
Date(int year = 1999, int month = 11, int day = 20)
{
_year = year;
_month = month;
_day = day;
cout << _year << " " << _month << " " << _day << endl;
}
bool operator==(const Date& d)
{
return this->_day == d._day && this->_month == d._month && this->_year == d._year;
}
//前置++
Date operator++()
{
this->_day += 1;
return *this;
}
//后置++
Date operator++(int)
{
Date temp(*this);
temp._day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
若涉及资源管理,必须由用户显式提供赋值运算。
赋值运算符主要有四点:
- 参数类型
- 返回值
- 检测是否自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
注意:.* 、:: 、sizeof 、?: 、. 以上5个运算符不能重载。
const成员
const修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public:
Date(int year = 1999, int month = 11, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
void printDate()const
//class Date(const)*(const)-------->this指向内容不可更改,提高安全性
{
_day = 30;//若不在private中需要修改的值前加mutable是不能修改的
cout << _year << " " << _month << " " << _day << endl;
}
void printfDate()
//class Date*(const)-------->this指向不可改
{
_day = 30;
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
mutable int _day;//加mutable后可以在const修饰的成员函数中修改_day。
};
void test(){
Date a;
a.printfDate();
a.printDate();
}
- const类型对象不能调用普通成员函数
- 普通类型对象(可读可改)可调用const类型成员函数(只能读不能改)
- const类型成员函数不可以调用普通类型成员函数
- const类型成员函数可以调用const类型成员函数
- 普通类型成员函数可以随意调用
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date(int year = 1999, int month = 11, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
mutable int _day;
};
void test(){
Date a;
cout << &a << endl;
}
友元
友元分为:友元函数和友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
explicit Date(int year = 1999, int month = 12, int day = 12)
:_year(year)
, _month(month)
, _day(day)
{};
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
void test()
{
Date a;
cout << a << endl;
}
- 友元函数可访问类的私有成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
友元关系不能传递。
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
class Date;
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 15, int minute = 43, int second = 30)
: _hour(hour)
, _minute(minute)
, _second(second)
{};
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
cout << _year << "-" << _month << "-" << _day << endl;
cout << _t._hour << "-" << _t._minute << "-" << _t._second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
内部类
概念
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的
类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
- 内部类可以定义在外部的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void find(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
};
};
int A::k = 1;
void test()
{
A::B b;
b.find(A());
}