🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中有关类和对象的初步了解.
金句分享:
✨应该有更好的方式开始新的一天,而不是千篇一律的每天醒来.✨
前言
一、面向过程与面向对象
C
语言作为一种面向过程的编程语言,注重解决问题的过程和步骤,通过函数和控制流程的设计来组织程序。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
现实实例:制作一个简单的三明治。
面向过程分析:(C语言)
- 收集所需材料和工具:面包、黄油、火腿、生菜、刀子、砧板等。
- 将切好的面包放在砧板上。
- 使用刀子涂抹黄油在面包片上。
- 在其中一片面包上放上火腿和生菜。
- 将另一片面包盖在火腿和生菜上,使之成为一个完整的三明治。
- 可选:将整个三明治切成两半或四等份。
- 完成。
面向对象分析:(C++)
- 定义一个"三明治"类,它具有属性(面包、黄油、火腿、生菜)和方法(涂抹黄油、放置火腿和生菜、组装成三明治)。
- 创建一个"三明治"对象。
- 调用对象的方法,按照特定的顺序执行:
- 调用涂抹黄油的方法,在面包片上涂抹黄油。
- 调用放置火腿和生菜的方法,在其中一片面包上放置火腿和生菜。
- 调用组装成三明治的方法,将另一片面包盖在火腿和生菜上。
- 可选:调用切割的方法,将整个三明治切成两半或四等份。
- 完成。
在面向过程分析中,我们按照步骤逐一执行操作,强调流程和步骤的线性顺序。
而在面向对象分析中,我们将问题抽象为一个对象,该对象具有属性和方法,通过调用对象的方法来实现功能,强调对象的行为和内部状态的封装。
总之面向对象以后,重点不再关注做事的具体过程,而是关注其中涉及哪些对象
二、类
2.1 类的介绍
还记得C
语言阶段学习过的结构体吧?在结构体中我们可以定义各种类型的变量,但是我们不能在结构体中定义函数.
C
语言中:
同样一段代码在C++
中,结构体内不仅可以定义变量,也可以定义函数。
C++
中:
为什么呢?因为C++
中将结构体升级为了==“类”.在类==中是可以定义函数的,通常被称为成员函数.
在C++
中,class
关键字用于定义一个类。类是一个用户定义的数据类型。
类体中内容称为类的成员:可以包含属性(成员变量)和操作/方法(成员函数)。
2.2 类的定义方式
使用class
关键字可以创建一个新的类,并定义它的特征(如数据成员和成员函数)。类可以用于封装数据和行为,并提供对外部程序的接口。通过类的实例化,可以创建对象,并访问其成员变量和成员函数。在面向对象编程中,类是非常重要的一个概念,它使得程序更加模块化,易于维护和扩展。
(1)声明和定义全部放在类体中.
注意:成员函数如果在类中定义,编译器默认是按内联函数(inline)处理.(同样如果函数体过长也是不会产生内联的.)
#include <iostream>
using namespace std;//在工程代码中不建议展开可能会产生命名冲突
class Person {
public:
// 构造函数(后面会讲,这里按普通成员函数理解)
Person(char* n, int a) {
name = n;
age = a;
}
// 成员函数
void introduce() {
cout << "欢迎来到CSDN!\n我是" << name << ",我的年龄是" << age << "岁" << endl;
}
private:
//成员变量
char* name;
int age;
};
int main() {
char name[] = "初阶牛";
Person person(name, 18);
person.introduce();
return 0;
}
运行结果:
欢迎来到CSDN!
我是初阶牛,我的年龄是18岁
这个类的名字叫做 Person
,它有两个私有成员变量:name
和 age
。
类还有一个公有的成员函数:introduce
。introduce
函数用于打印出个人信息,即打印出对象的 name
和 age
属性。
在 main
函数中,我们创建了一个名为 person
的 Person
对象,并通过构造函数初始化了它的成员变量。然后我们调用了 introduce
函数来展示个人信息。
通过使用成员函数和成员变量,我们可以对对象进行操作和访问其属性,从而使类具有更多的功能和灵活性。请注意,在 C++
中需要使用 iostream
库进行输入输出操作,并使用 main
函数创建类的对象并调用成员函数。
(2)类的声明和"成员函数"分离
即类声明放在.h文件中,成员函数定义放在.cpp文件中.
注意:成员函数名前需要加类名::
2.3 类的访问限定符
在C++
中,类的访问限定符(访问修饰符)用于控制类的成员对外部代码的可见性和访问权限。C++
提供了三个主要的访问限定符:public
、private
和protected
。
-
公共访问(
public
):使用public关键字来指定。公共成员可以从任何地方访问,包括外部代码和其他类。公共成员在整个程序中可见。 -
私有访问(
private
):使用private关键字来指定。私有成员只能在声明它们的类内部访问。其他任何外部代码或其他类都无法直接访问私有成员,包括子类。 -
受保护访问(
protected
):使用protected
关键字来指定。受保护成员只能在声明它们的类内部访问以及该类的子类中访问。外部代码无法直接访问受保护成员。
我们暂时这里将私有访问(private
)和受保护访问(protected
)看作相同的,后续再区分.
注意:
-
C++
中class(类)的默认访问级别是私有访问(private
)。类的成员将默认为私有成员,只能在类内部访问。 -
struct
(结构体)为public
(因为struct
要兼容C
语言),在C
语言中,外部可以访问结构体中的成员变量.
访问限定符的选择取决于设计需求和封装原则。公共成员允许类的用户直接访问,而私有成员则隐藏了实现细节并提供了更好的封装。受保护成员专门用于派生类访问,并且在类外部不可见。
2.4 封装的介绍
封装的定义:(灰常重要)
是指将数据和方法放在一起.将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。
在现实世界中,手机是一个复杂的设备,它包含了许多内部组件和功能,如屏幕、摄像头、声音、通信等。对于我们普通用户来讲,手机只需要提供给我们我们点击的屏幕,和手动控制开关机的按键就可以了,它内部具体是怎么实现功能的我们并不关心,如果让用户去关心CPU如何设计,主板上的线路如何布局,这显然是不合理的,手机的封装也就体现了管理,帮助用户更方便的使用手机
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
三、“类” 与 “对象” 之间的关系
我们先看一下test2
函数.
class Person {
public:
//成员函数
Person(char* n, int a);
void introduce();
private:
//私有成员变量
char* name;
int age;
public:
float weight;
};
void test2()
{
//报错
Person.weight = 60.5;//报错,类只是声明,并没有申请空间,不能用于存放数据
//正确写法
char name[] = "初阶牛";
Person cjn(name, 18);//通过类实例化出 cjn这个对象.
cjn.introduce();
cjn.weight = 60.5;//实例化出来的对象是有空间的,可以存储数据
}
-
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;
-
一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
有一个很形象的比喻,类就好比是建筑的图纸,类只是一些声明,并不没有去申请实际的空间,就好比图纸只是设计形状,并没有占有空间.类不能存储数据,就类似于图纸不能住人.
通过类实例化出的对象后就分配的实际空间,对象可以用于存储数据,就像图纸设计出来房子后,房子里面就可以住人了.
3.1 类的大小计算
试着猜一下下面People
类的大小.
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class People
{
public:
void Print()
{
int a = 0;
double b= 1.2;
cout << _name << endl;
}
private:
char _name;
int _age;
};
int main()
{
cout << sizeof(People) << endl;
return 0;
}
运行结果:
8
解释:
为什么是8呢?因为类在计算大小时也要考虑内存对齐.char _name
占1个字节(偏移量为0),int _age
占四个字节(4-7偏移量).共八个字节.
为什么不计算成员函数的大小呢?
那就要说到类的设计方式了,因为成员函数消耗的内存相对都比较大,而每个对象都是使用同一个成员函数,如果每个对象都给成员函数开辟空间,这就比较浪费了,所以C++中的类采用下图这种方式存储:
将;类的成员函数放在公共代码段,需要使用的时候调用即可,对象之间公用同一个成员函数.这种设计方式有效的节省了类实例化出对象后的空间消耗.
那小伙伴掌握如何计算类的大小了吗?
不妨猜一下下面A类和B类的大小.
// 只有成员函数的类
class A {
public:
void test() {}
};
//空类
class B
{};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
运行结果:
1
1
这是因为没有成员变量的类或者空类也是会在占用一个字节,因为需要占位,表示对象的存在.
3.2 this指针
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class Person {
public:
//成员函数
Person(char* n, int a)
{
_name = n;
_age = a;
}
void introduce()
{
cout << _name << endl;
}
private:
//私有成员变量
char* _name;
int _age;
};
int main()
{
char name1[] = "初阶牛";
char name2[] = "CSDN";
Person person1(name1, 18);
Person person2(name2, 18);
//这两个调用的是同一个函数吗?
person1.introduce();
person2.introduce();
return 0;
}
运行结果:
初阶牛
CSDN
上面这段代码中这两个调用的是同一个函数吗?如果是同一个,为什么打印的结果却不一样?
person1.introduce();
person2.introduce();
解释:
调用的是同一个函数,之所以打印的结果不一样是因为C++
编译器给每个“非静态的成员函数“增加了一个隐藏的指针(this
指针)参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
没理解的看过来:
其实就是编译器帮我们传了参,不需要用户进行手动传参.
结构体内存对齐复习:传送门
3.3 深入理解this指针
为了深入理解this指针,下面有两道题可以做一下:
第一题:this
指针本身存储在哪里?
A. 栈区
B. 堆区
C. 对象中
D. 常量区
答案:
栈区,因为
this
指针就是一个形参,只不过是被编译器默认传递,形参是存放在栈区的 函数栈帧建立时压栈,函数结束时,销毁.
第二题:下面这两段代码分别会出现什么情况?
//代码1:
class A
{
public:
void Print()
{
cout << "HELLO CSDN" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 代码2:
class A
{
public:
void Print()
{
cout << _age << endl;
}
private:
int _age;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
答案:
代码1:
正常运行,虽然this
是空指针,但是并没有对this
指针进行解引用,传递空指针是不会报错的.
代码2:
运行崩溃,对this空指针进行解引用,属于非法访问.
运行图如下:
四、C与C++对比
对比C
语言,帮助更好的理解C++
的封装特性.
C
语言数据和方法是分离的,给予C程序员很大的操作空间.这样也就使得对C
程序员的要求很高.太自由了!
比如:
对于一个用C语言实现的栈.很多数据在栈的外部可以被随意的修改和使用,这样就对程序员的要求极高.对于不规范的编程,(一会通过接口(函数),一会自己在外界直接访问)很容易造成混乱
C++
程序员受封装的保护,对于栈中的很多操作只能通过调用对应的接口实现,更好的约束了程序员的操作规范
C实现栈:
//C语言版本
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int DataType;
typedef struct Stack
{
DataType* array;
int capacity;
int size;
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == ps->array)
{
assert(0);
return;
}
ps->capacity = 3;
ps->size = 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
if (ps->array)
{
free(ps->array);
ps->array = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
void CheckCapacity(Stack* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity * 2;
DataType* temp = (DataType*)realloc(ps->array,
newcapacity * sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
ps->array = temp;
ps->capacity = newcapacity;
}
}
void StackPush(Stack* ps, DataType data)
{
assert(ps);
CheckCapacity(ps);
ps->array[ps->size] = data;
ps->size++;
}
int StackEmpty(Stack* ps)
{
assert(ps);
return 0 == ps->size;
}
void StackPop(Stack* ps)
{
if (StackEmpty(ps))
return;
ps->size--;
}
DataType StackTop(Stack* ps)
{
assert(!StackEmpty(ps));
return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->size;
}
int main()
{
Stack s;
StackInit(&s);
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackPop(&s);
StackPop(&s);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackDestroy(&s);
return 0;
}
C++实现栈:
typedef int DataType;
class Stack
{
public:
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = 3;
_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; }
void Destroy()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
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;
};
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;
}
文章来源:https://www.toymoban.com/news/detail-573038.html
最后补充一个小知识:
局部域和全局域会影响生命周期
类域和命名空间域不会影响生命周期.文章来源地址https://www.toymoban.com/news/detail-573038.html
到了这里,关于C++都有对象了,你还没有吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!