从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针(上)

简介: 从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针

       前面提到C++是面向对象的语言,但不是纯面向对象,因为要兼容C语言,

所以C++可以面向对象和面向过程混编,像Java是纯面向对象的语言,只有面向对象,

就算你想实现一个排序也要写一个类出来……

本章将正式开始学习C++中的面向对象。

1. 面向对象

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

C++是基于面向对象编程(Object Oriented Programming,OOP),关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。举个栗子,比如设计简单的外卖系统:

面向过程:关注实现下单、接单、送餐这些过程。体现到代码层面 -- 方法/函数

面向对象:关注实现类对象及类对象间的关系,用户、商家、骑手以及他们之间的关系。

体现到代码层面 —— 类的设计及类之间的关系。

1.1 类的引入

在C语言中,结构体中只能定义变量,而在C++中,结构体内不仅可以定义变量,还可以定义函数。因为在 C++ 里,struct 也跟着升级成了类。

因为 C++ 兼容 C 里面结构体的用法,所以 C++ 就可以直接使用类名来定义了:

struct Student 
{
  char name[10];
  int age;
  int id;
};
int main()
{
  struct Student s1;  // 兼容C
  Student s2;         // C++就可以直接使用类名,Student类名,也是类型。
 
  strcpy(s1.name, "小明");
  s1.id = 10001;
  s2.age = 20;
 
  strcpy(s2.name, "小红");
  s2.id = 10002;
  s2.age = 19;
 
  return 0;
}

既能用 struct Student s1 来定义,还能直接使用 Student s2,通过使用类名直接定义。


这体现了 C++ 兼容 C 的特点。


但是如果这是在 C语言 里, stuct Student 才是它的类型,


直接使用 Student 定义是不可以的。


它其实就是一个结构,可以理解成和之前学的结构体是 "一样的" ,只是定义的方式既兼容了 C 还兼容了 C++ ,下面我们还会认识到一些它的不同之处。


如果是在C语言里,结构体里只能定义变量,就是一个多个变量的集合。


如果我们想要将 s1 中的变量进行初始化,还得一个个写,很麻烦,

但是在C++里,不仅可以定义变量,还可以定义函数(方法)

定义一个 "初始化" 函数:

struct Student 
{
    /* 成员变量 */
  char name[10];
  int age;
  int id;
 
    /* 成员方法 */
  void Init(const char* name, int age, int id) {
    ...
  }
};

在 C++ 中一般称这些变量为成员变量,称这些函数为成员方法

这个时候似乎发现了一些新的问题,这个成员方法的参数名取的好像和成员变量里一样了,

比如访问 name 的时候到底是成员变量里的 name 还是成员方法里的 name 呢?

这就让人区分不开了,为了能够更好的区分哪个是成员变量,我们在定义成员变量名时可以给它们做一些标记:下面是几种常见的表示为成员变量的 "风格" :

① 前面加斜杠 :

char _name[10];

② 后面加斜杠:

char name_[10]

③ 前面加个 m (表示成员 member):

char mname[10]


这个并没有明确的规定,不同的公司也有不同的风格。本博客常用第一种风格

这样就可以区分开来了:

struct Student 
{
  /* 成员变量 */
  char _name[10];
  int  _age;
  int  _id;
 
  /* 成员函数 */
  void Init(const char* name, int age, int id) 
    {
    strcpy(_name, name);
    _age = age;
    _id = id;
  }
};

为了方便测试,再来写一个简单的打印函数,调用它们进行一个打印:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
struct Student 
{
  /* 成员变量 */
  char _name[10];
  int  _age;
  int  _id;
  /* 成员函数 */
  void Init(const char* name, int age, int id) 
  {
    strcpy(_name, name);
    _age = age;
    _id = id;
  }
 
  void Print() 
  {
    cout << _name << " " << _age << " " << _id << endl;
  }
};
 
int main()
{
  struct Student s1;
  Student s2;
 
  /* 初始化 */
  s1.Init("小明", 20, 10001);
  s2.Init("小红", 19, 10002);
 
  /* 打印 */
  s1.Print();
  s2.Print();
 
  return 0;
}

总结:C++ 对我们的 struct 进行升级了,升级为类了。它兼容以前的用法,又有了新的用法。


1.2 class 关键字

刚才引入部分讲了 struct ,知道了它在 C++ 里升级成了类。其实 C++ 也有自己的亲儿子,就是 class,class语法和struct一样,注意类定义结束时后面要加分号。


但我们把上面代码的struct改成class居然报错了,这又是为什么呢?因为 C++ 讲究 "封装" ……C++ 这里是它把数据和方法都放在了类里面。

这和C语言是不同的,C语言里数据是数据,方法是方法。

这里我们就来提一下 面向对象OOP的三大特性:封装、继承、多态。

我们先来重点看一下这个 封装

① 数据和方法都被放在了一起。

② 访问限定符

就是因为这个访问限定符,所以这里我们报错了,下面来学习一下访问限定符。


2. 类的访问限定符及封装

2.1 访问限定符

C++ 实现封装的方式:用类将对象的属性与方法结合在一起,让对象更加完善,

通过访问权限选择性地将其接口提供给外部的用户使用。

一共有三种访问限定符,分别是 public(公有)、protected(保护)、private(私有)。

顾名思义,公有就是随便访问,保护和私有就是不让你随便访问得到。

访问限定符说明

① public 修饰的成员,可以在类外面随便访问(直接访问)。

② protected 和 private 修饰的成员,不能在类外随便访问。

 (此处 protected 和 private 是类似的,现在你可以认为他们是一样的,后面我们讲继承的时候才能体现出它们两的区别


这就分出了两个阵营,一个阵营是可以随便访问的,一个阵营是不能随便访问的。

③ class 的默认访问权限为 private,struct 为 public !

这就是为什么我们刚才编译会报错,因为 class 默认访问权限是 private!

那好,既然知道问题所在了,该如何解决让它成功访问呢?

使用访问限定符,加一个 public :

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Student
{
  /* 成员变量 */
  char _name[10];
  int  _age;
  int  _id;
public:
  /* 成员函数 */
  void Init(const char* name, int age, int id) 
  {
    strcpy(_name, name);
    _age = age;
    _id = id;
  }
 
  void Print() 
  {
    cout << _name << " " << _age << " " << _id << endl;
  }
};
 
int main()
{
  struct Student s1;
  Student s2;
 
  /* 初始化 */
  s1.Init("小明", 20, 10001);
  s2.Init("小红", 19, 10002);
 
  /* 打印 */
  s1.Print();
  s2.Print();
 
  return 0;
}

成功运行,我们再来细说一下刚才加进去的访问限定符。

③ 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。

④ 如果后面没有访问限定符,作用域就到 { (最外面花括号)类结束。

也就是说,我们刚才加进去的 public ,

从它开始到下一个访问限定符出现为止的这块范围,都是共有的了,

但是因为后面没有再次出现访问限定符,所以作用域就到类结束为止。


再加一个访问限定符 private 进去看看:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Student
{
  /* 成员变量 */
  char _name[10];
  int  _age;
  int  _id;
public:
  /* 成员函数 */
  void Init(const char* name, int age, int id) 
  {
    strcpy(_name, name);
    _age = age;
    _id = id;
  }
private:
  void Print() 
  {
    cout << _name << " " << _age << " " << _id << endl;
  }
};
 
int main()
{
  struct Student s1;
  Student s2;
 
  /* 初始化 */
  s1.Init("小明", 20, 10001);
  s2.Init("小红", 19, 10002);
 
  /* 打印 */
  s1.Print();
  s2.Print();
 
  return 0;
}

现在, public 能影响到的范围就到 private 出现前为止了,然后在main里的打印函数就会报错。

这就是访问限定符在这里起到的一个作用。

注意事项:

① 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

② 一般在定义类的时候,建议明确定义访问限定符,不要用 struct / class 的默认的访问权限

class Student 
{
private:
  char _name[10];
  int  _age;
  int  _id;
 
public:
  void Init(const char* name, int age, int id) 
    {
    strcpy(_name, name);
    _age = age;
    _id = id;
  }
  void Print() 
    {
    cout << _name << " " << _age << " " << _id << endl;
  }
};

虽然不指定会有默认限定,但是还是建议明确写出来,

因为这样能让他人一眼就看出它是共有的还是私有的。


2.2 封装的意义和本质

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来实现对象进行交互。

① 把数据都封装到类里面。

② 可以给你访问定义成公有,不想给你访问的定义成私有或者保护。

封装的意义

封装的意义是什么?

封装是一种更好的严格管理,不封装是一种自由管理。

那么是严格管理好,还是自由管理好呢?

举一个疫情防控的例子:

某国单日新增一百万,所以是自由的管理好呢?还是严格的管理好呢?

我们和某国其实都是在控制疫情的,但是我们是严格的管理,控制疫情。


封装的本质

       封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

       对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。

       在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

类也是一样,我们使用类数据和方法都封装到了一起,不想让人随意来访的,

就是用 protected / private 把成员封装起来,开放一些共有的成员函数对成员合理的访问。

C语言没办法管理,易出错,全靠个人素质。所以,封装是一种更好、更严格的管理。

从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针(中):https://developer.aliyun.com/article/1513642?spm=a2c6h.13148508.setting.17.5e0d4f0eApSShM

目录
相关文章
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
174 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
266 0
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
308 12
|
9月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
9月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
182 16
|
10月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
9月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
9月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
9月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
482 6