在c语言中的结构体,在c++中被叫做类,由于c++兼容c,因此由原先的定义方式是允许的,但在c++中,关于类经常使用的关键字不是struct,而一般用class,而且区别于以前的结构体,在类中不只可以有成员的声明,还可以直接写成员函数,或者是成员函数的声明
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
例如:
//写一个简单的类
class Data
{
//私有的一般写成员声明
private:
int _year;//成员声明的变量一般在命名前加个_是为了区别成员函数的形参
int _month;
int _day;
public:
void Init(int year, int month, int day)//类的成员函数可以直接在类里面定义
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年";
cout << _month << "月";
cout << _day << "日" << endl;
}
};
int main()
{
Data n1;
n1.Init(2023,07,31);
n1.Print();
system("pause");
return 0;
}
上面的例子中涉及到一个语法知识叫访问限定符
访问限定符有三种:public(公用)、protected(保护)、private(私有)
(目前暂时认为保护和私有是一样的)
一般在类里要声明哪一部分是公用的,哪一部分是私有的,公用部分是可以在外部接口调用或者改变的部分,一般是成员函数,私有的则一般是成员声明或者不希望被改变的部分
在定义类时,除了像上面的直接将定义写到类里,通常更多的是将声明放在类中,函数的定义放在另一个文件中,这里就涉及到一个类的作用域的概念,每一个类都是相对独立的一个域,在定义部分要在函数名前用域作用符去找到相对应的类作用域
例子:
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
类中的成员函数不被计算在类的大小中,它被放到另一块公共区域中,类的大小同样遵循着和结构体大小计算的对齐原则,只算成员声明部分,当类是一个空类或者没有成员时,则默认为1,用于占位
this指针是在调用类函数的过程中,经过编译器的处理,将对象的地址作为隐藏的形参传到函数中,使得类函数能找到相对应的对象进行操作
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
构造函数为了解决对象成员的初始化问题,在定义类时,会默认生成构造函数,默认生成的构造函数对内置类型不做处理,对自定义类型会去调用其相对应的构造函数,构造函数也可以自己定义
构造函数还存在初始化列表,初始化列表用于每个独立的对象成员单独的初始化,所有的对象成员都在初始化列表上进行独立的初始化,定义方式如下
class 类名
{
public:
类名()//构造函数
:对象成员名(初始化的值),
...//可初始化多个,用逗号分割
{函数体}
private:
对象成员的声明;
}
例子:
class A
{
public:
A()
:_a(1),//初始化列表
_b(2),
_c(3)
{
//函数体
}
private:
int _a;
int _b;
int _c;
}
初始化列表中进行初始化时,会优先采用括号内的值,当括号内不给值或者不进行显式的初始化时,会默认进行初始化,采用缺省值,当缺省值不存在时,遵循自定义类型会调用相关的构造函数,内置类型不做处理
注意:由于一些特别情况,建议每次创建类时,每个对象成员都在初始化列表进行初始化
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 对象成员的初始化顺序按照声明的顺序来定,与初始化列表的顺序无关。
析构函数用于自动处理类对象的销毁,默认生成的会对内置类型进行销毁,对自定义类型去调用其本身的析构函数,在一些情况下,用默认生成的析构函数即可,但如果涉及内存操作和管理的类,需要根据具体情况,自己去定义析构函数
析构函数的定义:
~类名()
{
//函数体;
}
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
拷贝构造也是构造函数的一种,它主要解决对象的拷贝问题,默认生成的拷贝构造对内置类型不做处理,对自定义类型回去调用对于的拷贝构造,在自定义拷贝构造函数时,参数要传const &类型,不然会陷入死递归
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。
4. 编译器生成的默认拷贝构造函数只能进行浅拷贝(值拷贝)
5. 拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)//构造函数
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)//拷贝构造
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()//析构函数
{
cout << "~Date():" << this << endl;
}
private://成员对象
int _year;
int _month;
int _day;
};
Date Test(Date d)//拷贝构造
{
Date temp(d);//拷贝构造
return temp;//拷贝构造
}
int main()
{
Date d1(2022,1,13);//构造
Test(d1);
return 0;
}
函数原型:返回值类型 operator操作符(参数列表)
作用:运算符重载是针对自定义类型在使用运算符时,可以对运算符进行重新定义,使得自定义类型也能根据实际需求去使用运算符,使用的规则也是自己定义
运算符重载可以定义在全局使用,但更多时候是类内部进行定义
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
注意: .* :: sizeof ? : . 这5个运算符不能重载。
定义:const可以用来修饰类成员函数,实际上修饰的是成员函数内的this指针,表明被修饰的成员函数内,不可对成员参数进行任何修改,将const修饰的“成员函数”称之为const成员函数
使用方式:在成员函数后面直接加const,例: void fun(...) const;
作用:在调用类成员函数时,有时候穿参时会使用 const对参数进行修饰,而this指针由于被隐藏,因此可能会存在权限发大的问题,使用该语法是用来补充这点的不足
介绍:对于单参数的构造函数,具有类型转换的功能,在定义时可以自动的进行类型转换,若是不想其自动转换,可以使用explicit关键字去限制,关键字加在函数声明前
概念:
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
特性:
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,只进行一次初始化
2.静态成员的定义必须在类外面,定义在全局,定义时不加关键字,用类名+: :去找到声明
3.类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
应用场景之一:判断程序运行后类调用了多少次析构
要判断调用多少次析构,可以在类中定义一个静态变量,对每次调用进行统计
概念:
由于类的封装特性,一般情况下,类外面的函数无法改变和调用类成员参数,在一些特殊的情况下,可能需要调用或者改变类成员参数时,可以在类内对该函数进行友元声明:
格式:
friend 函数声明;
(一般不建议使用友元函数,因为会破坏类的封装性)
特性:
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
概念:
与友元函数类似,在一个类中对另一个类进行友元声明,则被声明的类即可使用该类的类成员变量
特性:
1.友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
2.友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
概念:在类的内部也可以定义一个类,在内部的类可以直接使用外部的成员变量,相当于是外部类的友元类,但外部类不能直接使用内部的
概念:在一些情况下需要通过类对象去调用某些函数或者成员作返回值,但又不需要用到该对象本身时,可以使用匿名对象去调用,能够提高效率,匿名对象的生命周期只存在与定义那一行,用完即立刻销毁
概念:在拷贝构造时,如果存在一些重复多余的拷贝构造,则编译器可能会自动帮你优化
例子:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
比特就业课
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 传值返回
f2();
cout << endl;
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
本篇内容讲了关于类和对象的部分比较常用的语法,相比于c,在语法上有许多扩展,这些语法在结合具体的例子能更好的理解,建议可以自定义一个日期类进行练习,日期类细节上的搭建也会后续进行补充更新