当前位置: 首页 > news >正文

类与对象(上)

1.引入

C语言是面向过程的,但C++是面向对象的,这一点很好地体现在了类上面,什么是类?

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数,这时struct就上升到了类:

typedef int DataType;
struct Stack
{void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}DataType* _array;size_t _capacity;size_t _size;
};
int main()
{Stack s;s.Init(10);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;
}

在这个用c++中的类模拟实现的栈中,在类中定义了函数,并且在类中对变量定义的顺序也没有特别规定,c++默认都在一个类中,是一个整体,并且可以在其中定义函数。

2.类的定义

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面 号不能省略
类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者 成员函数
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成 联函数处理。(建议不要在类中加上inline内联,因为类中默认函数内联,但具体是否内联取决于函数大小,是否递归以及编译器)。
2. 类中成员函数声明放在.h文件中,定义放在.cpp文件中,注意:成员函数名前需要加类名::,(否则在类中定义的某些变量无法正常使用)并且如果要用到缺省参数,注意在声明中提前定义!
类的访问限定符及封装:
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

补充:C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。

成员变量命名规则的建议:
class Date
{
public:void Init(int year){_year = year;}
private:int _year;//这里加斜杠就是防止形参与这里的变量混淆,以示区分
};
类的作用域:
1.类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
2.在不同的类中可以定义同名函数,这也是因为存在类的作用域。
类的实例化:

1.用类类型创建对象的过程,称为类的实例化,类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它

2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
举个例子:
typedef int DataType;
class Stack
{
public:void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType* _array;size_t _capacity;size_t _size;
};
int main()
{Stack s;s._capacity = 1;return 0;
}

这里必须先要创建一个栈出来,不能直接对类中的元素赋值,可以理解为类只是一张图纸,什么都没有,只有根据这张图纸盖出房子来,才可以住人。

类对象模型:
如何计算类对象的大小?
1.类中函数是放在 公共代码段的,因此在计算类的大小时不需要计算函数所占用的空间,只需要计算成员变量的占用空间。
2. 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,(其原理与结构体内存对其规则是相同的,即是所占用的空间大小是最大对齐数的整数倍,其余的空间浪费掉,根据自定义类型中结构体内存对齐的具体介绍,本质上是空间换取时间的做法)
结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
3. 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象,因此当类中没有成员函数或者成员变量时,这是去计算这个类的大小,就是一个字节,这是编译器规定的。

3.this指针

先来看一段代码的结果:

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1, d2;d1.Init(2022, 1, 11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

明显这其中的Init函数以及Print函数都是放在公共代码段的,那么为什么会打印出不一样的结果呢?为什么不会发生冲突呢?

这是因为this的作用,C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

this 指针的特性:
1. this指针的类型:类类型* const,(这里this指针指向的地址被const修饰,因此不可以在成员函数中修改this指针的地址,但可以修改指向的内容),成员函数中, 不能明显传值this指针,但在类中可以引用this指针。
2. 只能在“成员函数”的内部使用。
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中 不存储this指针。 (this指针的实质上是形参,即在栈上开辟空间,存储地址)
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
eg:
// 1.下面程序编译运行结果是?
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}

这里打印print()并没有对this解引用,因此即使this指针为空,运行也是正确的!        

// 1.下面程序编译运行结果是?
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}

这时候就不一样了,将this的空指针传值过去后,对this试图解引用访问_a,这当然是错误的,这时就会报错。

4.默认成员函数

任何类在什么都不写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

4.1构造函数

如何对一个类进行初始化呢?我们可以在类中写一个init函数,然后完成类的实例化之后,调用类中的初始化函数,在C++11中存在另一种初始化方式;

概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。

特性:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
typedef int DataType;
class Stack
{
public:Stack(DataType* a, int n){cout << "Stack(DataType* a, int n)" << endl;_array = (DataType*)malloc(sizeof(DataType) * n);if (NULL == _array){perror("malloc申请空间失败!!!");return;}memcpy(_array, a, sizeof(DataType) * n);_capacity = n;_size = n;}Stack(int capacity = 4){cout << "Stack(int capacity = 4)" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){CheckCapacity();_array[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top() { return _array[_size - 1]; }int Empty() { return 0 == _size; }int Size() { return _size; }private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity * sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}
private:DataType* _array;int _capacity;int _size;
};

上面这段代码演示了用类来作为构造函数名并且构造函数构成重载。

如果我们在main函数中这样去使用构造函数呢?

int main()
{Stack s(1);Stack ss();return 0;
}

那么第一种就是正确的,而第二种是错误的,原因是:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明,编译器会报警告:

1>\test.cpp(370,8): warning C4930: “Stack ss(void)”: 未调用原型函数(是否是有意用变量定义的?)

6. 不实现构造函数的情况下,编译器会生成默认的构造函数。C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是int,char,double或者是指针,自定义类型就是我们使用的class/struct/union等自己定义的类型。
再来看例子:我们将编译器对内置类型自动调用默认构造函数产生的初始值打印出来:
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
};
int main()
{Date d;d.Print();return 0;
}

结果却是随机值,这是为什么?我们不写构造函数,那么编译器会自动调用默认构造函数,通常对内置类型不做处理,这在不同的编译器上有着不同的效果,有的可能会主动赋值为0,这里演示的是在vs2022上的处理结果,而自定义类型会去调用它的默认构造。

难道只能让编译器产生随机值吗?C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

例如:

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
总结一下:
1.一般情况下,有内置类型成员的,就需要自己去写构造函数,而不能用编译器自己生成的默认构造函数,因为这样会产生随机值。
2.如果类中全部都是自定义类型成员,那么可以考虑让编译器去自动调用构造函数。

http://www.mrgr.cn/news/83380.html

相关文章:

  • 大语言模型需要的可观测性数据的关联方式
  • 寒假2.8
  • js 中文拼音排序
  • qml PageIndicator详解
  • 【数据结构】双向链表(真正的零基础)
  • shell+kafka实现服务器健康数据搜集
  • Python自学 - 类进阶(可调用对象)
  • 《HeadFirst设计模式》笔记(下)
  • 第27章 汇编语言--- 设备驱动开发基础
  • RNN之:LSTM 长短期记忆模型-结构-理论详解(Matlab向)
  • win32汇编环境,怎么进行乘法运算的
  • 测试开发之面试宝典
  • 01 springboot集成mybatis后密码正确但数据库连接失败
  • JVM与Java体系结构
  • SQL从入门到实战-2
  • 【华为云开发者学堂】基于华为云 CodeArts CCE 开发微服务电商平台
  • Mysql进阶篇
  • 01 Oracle自学环境搭建
  • Lambda expressions in C++ (C++ 中的 lambda 表达式)
  • L1G5000 XTuner 微调个人小助手认知
  • Microsoft 已经弃用了 <experimental/filesystem> 头文件
  • 力扣算法题(基于C语言)
  • 2025年第三届“华数杯”国际赛B题解题思路与代码(Python版)
  • Qt学习笔记第81到90讲
  • 油猴支持阿里云自动登陆插件
  • SpringBoot3