类和对象
c++把结构体升级成了类,也可以兼用c语言的结构体用法
类是一个整体,在类里面定义的变量,在类这个整体里面都可以使用,并不是像以前一样,只会从下往上找,类所定义的值,它会在这个整体里面进行寻找,所以相对而言少了一些限制。
struct ListNode
{
int val;
ListNode* next;//c++在第一次定义了类之后,可以直接使用这个类的名字
}
1.类的定义
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。这个函数并不是全局函数,它是这个类的函数。
例:
class Stack //在定义类的时候推荐不要使用默认,将自己需要的去定义 即可
{
public:
//成员函数
void Init(int n = 4)
private:
//成员变量
int* a;
int size;
int capacity;
}
void Stack::Init(int n)
{
a = (int*)malloc(sizeof(int)* n);
if(nullptr == a)
{
perror("malloc fail");
exit(-1);
}
capacity = n;
size = 0;
}
类的两种定义方式:
- 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
- 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
2.类的限定保护符
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
3.对类对象实例化
- 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
为什么成员变量在对象中,成员函数不在对象中?
每个对象的成员变量是不一样的,需要独立存储,每个对象调用成员函数是一样的,放到共享区域(代码段)
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类(没有成员变量的类)的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。这1byte不存储有效数据,只是用于占位,标识对象被实例化定义出来了
4.this指针
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传。
this存在哪里?-- 栈,因为它是隐含的形参
意思应该是 -> * 都是表面,就要看你传过去this后,要不要解引用,若空就崩溃
void Init(int year, int month ,int day)
{
cout << this << endl;
_year = year;
_month = month;
_day = day;
}
Date* ptr = nullptr;
func();//如果是在类里面定义的函数,这里就不能调用,如果是全局函数则正常调用
ptr->func();//正常调用,这个并没有用this进行解引用
ptr->_year;//运行崩溃
ptr->Init(2023,2,15);//运行崩溃,后面用this进行解引用了
(*ptr).func(); //正常运行
解引用的行为取决于你要访问的东西在不在对象里面,而不是取决于是否使用了那个符号
cpp
1、数据和方法都封装到类里面
2、控制访问方式。愿意给你访问公有的,不愿意给你访问私有的。
c语言
1、数据和方法是分离的
2、数据访问控制是自由的,不受限制的
4.赋值运算符重载
4.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@ 重载操作符必须有一个类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: . 注意以上5个运算符不能重载
返回值为了支持连续赋值,保持运算符的特性
d1 = d2 = d3;
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d.month;
_day = d._day;
}
return *this;
}
//计算相等
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
operator==(d1,d2);
d1 == d2; //如果有相应运算符重载,则转换成调用operator==(d1,d2);
cout << (d1 == d2) << endl;
return 0;
}
4.2赋值运算符重载
1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
Date operator(int);//这里的int只做规定的参数, //并不能修改成其他类型
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
友元函数:在相应的类里面写入某一函数的声明,这个函数就可以调用该类的私有。
类里面短小函数,适合做内联函数,直接在类里面进行定义。
friend void printWidth( Box box );
const成员函数
//cosnt 修饰 *this
//this的类型变成const A*
void Print() const
{
cout << a << endl;
}
void Func(const A& d)
内部不改变成员变量的成员函数,最好加上const,const对象和普通对象都可以调用。
除了取地址和赋值都需要进行重载,因为它们是默认成员函数。
1.再谈构造函数
1.1 构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Stack
{
public:
//无参的构造函数
Stack()
{}
//有参的构造函数
Stack(int size, int capacity, int date)
{
int _size = size;
int _capacity = capacity;
int* _date = date;
}
private:
int _size;
int _capacity;
int* _date;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量
的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
1.2 初始化列表
a、哪个对象调用构造函数,初始化列表示它所有成员变量定义的位置
b、不管是否显示在初始化列表写,那么编译器会对每个变量都会在初始化列表初始化
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Stack
{
public:
A()
:_a(1)
,_b(2);
{
_a++;
_b--;
}
private:
int _a = 1;//声明
int _b = 1;
};
int main()
{
A aa;//对象整体的定义
//必须给每个成员找一个定义的位置
return 0;
}
注:所有成员最好在初始化列表进行初始化
初始化的顺序是按照声明的顺序去进行的,并不是按照初始化列表的顺序进行初始化
类中包含一下成员,必须放在初始化列表进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
1.2 explicit关键字
用explicit修饰构造函数,将会禁止构造函数的隐式转换
//单参数构造函数 c++98支持
A aa1(1); //直接调用的构造函数
A aa2 = 1; //隐式类型转换 采用构造+拷贝构造+优化-》构造
//多参数构造函数 c++11支持
A a2(1,2);
A a1 = {2,2};
explicit:不允许隐式类型装换再进行赋值,只进行构造函数再赋值,而不进行构造和拷贝构造后再进行赋值。
class A
{
public:
explicit A(int a)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa.a1)
{
cout << "A(const A& aa)" << endl;
}
private:
int _a1;
}
int main()
{
//单参数的构造函数 c++98
A aa1(2);//构造函数
//加了explicit之后并不会允许下面转换的发生
A aa2 = 1;//隐式类型转换 构造+拷贝+优化-》构造
const A& ref = 100;//隐式类型转换,临时对象具有常性,并没有优化
//多参数构造函数
A aa3(1,1);c++98支持
A aa4 = {2, 2};//c++11支持,c++98并不支持
return 0;
}
2.1 static成员
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
class A
{
private:
//静态变量不属于某一类,属于所有类
stacic int count;
}
匿名对象,生命周期只在这一行,一次性用品。
cout << Solution().Sum_Solution(10) << endl;
3.友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
3.1友元函数
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
3.2友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以 在Date类中访问
- 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承
你是我盆友的盆友,但是你不一定是我的盆友
4.内部类(不太常用)--- 了解一下就可以
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,
它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越
的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访
问外部类中的所有成员。但是外部类不是内部类的友元
5.拷贝对象时的一些编译器优化
void func1(A aa)
{
}
void fun2(const A& aa)
{
}
A fun3()
{
A aa;
return aa;
}
A fun4()
{
return A();
}
int main()
{
A aa1 = 1;//构造+拷贝构造——》优化为直接构造
func1(aa1);//无优化
func1(1);//构造+拷贝构造——》优化为直接构造
func1(A(3));//构造+拷贝构造——》优化为直接构造
func1(aa1);//无优化
func2(2);//无优化
func2(A(3));//无优化
func3();
A aa1 = func3(); //拷贝构造+拷贝构造 --》优化为一个拷贝构造
func4();
A aa2 = func4(); //构造+拷贝构造+拷贝构造 --》构造
return 0;
}
对象返回总结:
1、接收返回值对象,尽量拷贝构造接收,不要赋值接收。(直接创建一个并用拷贝构造进行初始化)
2、函数中返回对象时,尽量返回匿名对象。
函数传参总结:
函数传参尽量采用 const& 引用传参。