1. 结构体
在引入类时,先回顾结构体定义以及对齐等相关知识
(1)结构体的定义
struct student
{
int age;
char sex;
char name;
};
(2)结构体的对齐
宗旨为:先内部对齐,再整体对齐
struct student
{
int age;//对齐参数为4,对齐为4个字节
char sex;//对齐参数为1,因为4是1的倍数,所以为5
double b;//对齐参数为4,5不是4的倍数,所以在之前添3位
char name;//对齐参数为1,所以为9位
};//又因为9不是最大对齐参数4的倍数,所以添3位,所以结构体大小为12
)
(3)在结构体内定义函数(在c++中适用)
struct student
{
int _age;
char _sex[5];
char _name[10];
void set(const char*name, const char*sex, int age)//在c++中,可以在结构体内定义函数
{
strcpy(_name, name);
strcpy(_sex, sex);
_age = age;
}
};
2. 类
(1)类的声明以及定义
--类的定义通常有两种方式
·声明和定义都在.h文件中
class student
{
int _age;
char _sex[5];
char _name[10];
void set(const char*name, const char*sex, int age)
{
strcpy(_name, name);
strcpy(_sex, sex);
_age = age;
}
};
·在.h文件中声明,在.cpp文件中定义
(.h文件)
class student
{
int _age;
char _sex[5];
char _name[10];
void set(const char*name, const char*sex, int age)
}
(.cpp文件)
void student::set(const char*name, const char*sex, int age)
{
strcpy(_name, name);
strcpy(_sex, sex);
_age = age;
}
(2)类的作用域
·c++有三大特性:封装、继承、多态
·访问限定符分为三类:公有(public),私有(private),保护(protected)
·在类中成员默认为私有的,在结构体中默认为公有的
保护和私有在类外无法直接使用
namespace N1
{
int a = 10;
}
int a = 10;
void func()
{
cout<<"func()" << endl;
}
class student
{
public:
int _age;
char _sex[5];
char _name[10];
void set(int a)
{
a = a;
}
void print()
{
cout << a << endl;
}
private:
int a;//私有,类外无法使用
};
int main()
{
student s;
s.set(30);//进入类中,
cout << a << endl;//因为在类中只是进行了赋值,所以输出流找最近的a的值为10
cout << N1::a << endl;//在明明空间中找到了a.所以值为20
s.print();
return 0;
}
(3)类的大小
· 类没有实际的大小,只包含了类中成员变量
·而空累的默认大小为1
由此,我们可以提出一个问题,编译器是如何识别类的?
·首先,通过类名找到类
·然后进入到类中识别成员变量
·最后对成员函数进行修改
·在类的成员函数中,有一种默认的指针,称为this指针
--this指针
·this指针不属于对象中的一员,它相当于类的另一种表现形式
·this指针是成员函数的第一个默认隐含参数,编译器自动调用与维护,类编写者不能显示传递
·this指针的形式为const* :指针不可以改,但是它所指向的空间的内容可修改
·this指针只有在非静态函数中才可以使用
·this作为指针,与引用最大的区别为
--引用必须要初始化,引用可以当成 int*const 类型的指针来处理
--指针在自加时是给所指的类型自加,偏移了大小,但是引用是给自己自加,所以引用相对而言安全
--没有null引用,但是有null指针
--引用的大小由引用类型决定,但是指针的大小只是由自身的空间所占字节数决定
--没有多级引用,但是有多级指针
--指针需要手动寻址,引用通过编译器寻址
既然提到了引用,下面就来分析引用的用法
3.引用
(1)引用的概念
引用不是新定义一个变量,而是给已经存在的变量起一个新的名字,所以编译器不会给引用开辟新的空间,因为它与引用的变量占同一块空间。
void func()
{
int a = 10;
int& ra = a;//类型& +引用变量=引用实体
int& rb = a;
}
--引用在定义时必须初始化
--一个变量可以有多个引用
-- 但是一个引用只可以引用一个实体
(2)常引用
--const int a = 10;
const int& ra = a;//引用了a常量,并且&ra的内容无法更改
--double b = 20;
const int &rb = b;//编译时会出错,因为类型不同
(3)数组引用
int ar[10];
int(&arr)[10] = ar;//数组引用形式
(4)函数参数引用
--void swap(int& left, int &right)//通过参数引用的方法,相当于指针
--函数返回值
int& test(int& left)//通过参数引用的方法,相当于指针
{
left += 1;
return left;
}
--int& test(int left){
return left;//在栈上开辟了left形参的空间,所以不能返回,返回值若为引用类型,则需要在函数内建立形参
引用的分析就先到这里,下面进入回到类的知识点中。接下来,主要介绍类的六个默认函数
4.类的默认成员函数
(1)构造函数
--构造函数是一个特殊的成员函数,因为它的名字与类名相同,并且它在对象的生命周期内只调用一次,保证每个数据成员都有一个合适的初始值。
--class student
{
public:
student(int age, char sex)
{
_age = age;
_sex = sex;
}
private:
int _age;
char _sex;
}
int main()
{
student s1(16, '女');
}
--构造函数可以重载,实参决定它调用那个构造函数
--有初始化列表(可以不用)
student(int age, char sex)
: _age(age)
, _sex(sex)
{}
初始化列表是以冒号开始,逗号分隔的表达式
有以下几种成员,必须要放在初始化列表的位置进行初始化
·引用成员变量(在拷贝析构函数中会有体现)
·const成员变量
· 类类型成员 (有非缺省的构造函数)
(2)拷贝构造函数
只有单个形参,该形参为本身类型对象的引用,创建对象时使用已经存在的对象进行引用
public:
student(int age, char sex)
: _age(age)
, _sex(sex)
{}
student(const student &s)//引用
:_age(s._age)
, _sex(s._sex)
{}
}
int main()
{
student s1(16, '女');
student s2(s1);//student (const student &s1)
}
(3)析构函数
--概念:和构造函数的功能相反,在对象被销毁的时候,由编译器自动调用,完成类的一些资源清理和汕尾工作。
student(const student &s)
:_age(s._age)
, _sex(s._sex)
{}
~student() //析构函数的格式
{
if (p)
{
free(p);
}
}
--特性:
·没有参数返回值
·一个类中只有一个析构函数,系统自动生成缺省的析构函数
·当一个对象生命周期结束后,(即完成了自己任务之后)系统会自动调用析构函数
·析构函数并不是删除对象,而是做一些清理工作。
(4)赋值操作符重载
--重载操作符是具有特殊函数名的函数,所以它也是一种函数。
格式为oprator接所要定义的操作符符号。
具有返回值和形参表,形参数和操作符的操作数目相同
--student &operator=(const student &s)
{
if (this != &s)
{
_age = s._age;
_sex = s._sex;
}
return *this;
}
int main()
{
student s1(16, '女');
student s2(s1);//student (const student &s1)
student s3 = s2;//因为自定义对象类不能直接赋值,所以需要对= 进行操作符重载
student s4 = s3 = s2;
//相当于s4.oprator=(s3.oprator=(s2));
}
--注意:
·不能通过连接其他符号来创建新的操作符:比如operator@
·重载操作符必须有一个类型或者枚举类型的操作数
·用于内置类型的操作符,其含义不能改变,例如:内置的整型+ ,不能改变其含义
·重载函数的形参有一个默认的形参this,限定为第一个形参
· 一般的算术操作符重载为非成员函数,但是赋值运算符为成员函数
·==和!=一般要成对重载
·下标操作符[]:一个形参为非const成员返回引用,一个是const成员返回引用
· 解运算符重载*和->操作符,不显示任何参数
· 前置时++ 必须返回被增量的引用,后缀式必须返回旧值,并且是引用返回。
s2 = ++s1;
//相当于s1.operator++(),无参数
//student &operator++(),返回值为引用
s2 = s1++;//s1.opreator++(10),必须传参
//student operator++(int),参数默认为int,不需要改变外部的值
·输入操作符>>和输出操作符<< 必须定义为类的友元函数。
(5)取地址操作符以及const修饰的取地址运算符重载
--
Class student
{
const student* operator&()const-》修饰this指针
{
return this;
}
student* operator&()
{
return this;
}
}
int main()
{
student s3;
const student s4;
cout << &s3 << endl;
(6)除了以上的6个默认函数之外,还有一个函数比较重要 ,那就是友元函数
--概念:
可以直接访问类的私有成员,它是定义在外部的普通函数,不属于任何类,但是需要在类的内部声明,声明时要加friend关键字
--特点:
·该函数能访问类声明的私有部分
·该函数位于类的作用域中
·该函数必须有一个this指针
--class student
{
friend void print(const student &s);//友元函数的声明
public:
student(int age, char sex)
:_age(age)
, _sex(sex)
{}
private:
int _age;
char _sex;
};
void print(const student &s)//在类外定义
{
cout << s._sex <<" "<< s._age << endl;
}
int main()
{
student s3(16, '女');
print(s3);//通过友元函数可以访问到类内部的成员
return 0;
}
--说明
·友元函数可以访问类的私有成员,但不是类的成员函数
·不能用const修饰
·可以在类的任何地方声明
·一个函数可以是对个类的友元函数(相当于进入了另一个类内,被授权)
--优缺点
·优点: 提高了程序的运行效率,不必开辟栈帧,空间
·缺点:破坏了类的封装性和隐藏性
--注意
·友元关系不能继承
·友元关系是单向的,不具有交换性
·不能传递