C++入门3+类和对象上2

简介: C++入门3+类和对象上2

2.为什么C++要引入class关键字来定义类呢?

首先要说明C++中的struct也是可以定义类的

只不过用class和用struct定义的类有一些区别

什么区别呢?

struct中的结构体的属性默认是公有的,也就是说结构体类型的成员变量是可以直接访问该结构体(类)的属性的

而class定义的类的属性默认是私有的,也就是说该结构体类型的成员变量是不可以访问该结构体的属性的

那么为什么要这么做呢?公有有什么不好的地方呢?私有有什么好的地方呢?

如果公有了,可能会发生下面这种现象:

int main()
{
  Stack st1;
  st1.Init();
  //比方说我们要判断这个栈是否为空
  //正常来说我们就是要调用Empty这个函数接口来判断这个栈是否为空
  if (st1.Empty())
  {
    //一顿操作
  }
  else
  {
    //一顿操作
  }
  //可是,如果这个程序员的代码素养并不好,那么他可能会想
  //你不就是想要判空吗,还需要调个函数,我直接看一下top等不等于0不就行了吗
  //那么他可能会写出这样的代码
  if (st1.top == 0)
  {
    //一顿操作
  }
  else
  {
    //一顿操作
  }
  //但是这个自作聪明的程序员忽视了一点,你定义栈的时候喜欢让top初始化为0,
  //那别人就是喜欢初始化为-1也没任何错误啊
  //你怎么知道这个栈的定义者定义的时候把top初始化为0还是-1呢?
  //你不知道啊
  //那么你还自作聪明的以你的想法去判断栈是否为空,这是一个不一定正确的想法
  //可能就会产生bug,而这种bug产生的原因就是你这个程序员的代码素养不好
  //因此C++创始人就想去限制你,强迫你去调用对应的接口来执行你想完成的操作
  //怎么限制呢,就是让你访问不到top,只能借助Empty这个函数来判空
  return 0;
}

也就是说:

C语言中代码的规范性极大程度上取决于程序员个人的代码素养

而我们的C++创始人针对这个问题

想要强制程序员:强制调用接口对数据成员进行操作从而规范代码

因此C++引入了class这个关键字

class定义的类默认是私有,但是函数也私有了,这怎么办?

这就要介绍一下类的访问限定符和封装的知识了

2.类的书写形式

1.类的访问限定符和封装

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户来使用

一般情况下都用class定义类,只有极少数情况下喜欢用struct

所以可这样来定义一个类

class Stack
{
private:
  int* a;
  int top;
  int capacity;
public:
  //这里的Init和Push可以不用
  //因为类也是一个域,只有在同一个域中才会构成重载
  //类中的函数可以访问该类中的成员
  void Init()
  {
    a = nullptr;
    capacity = top = 0;
  }
  void Push(int x)
  {
    //扩容
    //a[top++] = x;
  }
  bool Empty()
  {
    return top == 0;
  }
};

2.类的声明和定义分离

我们知道C语言中再写大型项目时经常会分头文件和源文件进行声明和定义分离

那么在C++的类中也可以这样

下面以栈为例

跟C语言有区别的地方在于在源文件中定义函数时需要加上类的域访问限定符

例如这里的

void Stack3::Init()
{
//具体实现
}

有一点需要特别说明:

C++也支持某些函数直接在类里面定义,而另一些函数进行声明和定义分离

但是有一个默认规则:直接在类中定义的函数默认加上inline的修饰

C++中类的推荐写法是:

长的函数:声明和定义分离来写

短的函数:直接在类中定义,默认会加上inline修饰

3.类的大小及类的实例化

1.类的大小

类作为一个数据类型,就必然会有它在内存中所对应的大小,

而类跟结构体类似,但是类中还有成员函数,那么这个类的大小是多少呢?

这里类C的大小是8个字节

根据我们在C语言阶段学过的结构体内存对齐的知识,我们可以计算出如果这个类只有成员变量_c和_i,那么这个结构体的内存大小就是8

那么成员函数不占用类的空间吗?

答案是:不占用

为什么呢?

因为:类C所定义出的每一个对象都有只属于它的一份数据,这一份数据是唯一的,是与其他人的数据互相独立的数据,就像是我们每一个人的身份证号都不同,每个人的年龄,性别,姓名等等都是只属于我们自己的数据,他人数据的变化对我们没有任何影响

所以类必须包含这些成员变量

而成员函数就像是一些公共设施,比如:公园,图书馆等等,不属于我们任何人

但是我们任何人都有权利去使用这些公共设施,如果这些公共设施只属于我们每个人,也就是每一个人都有一份公园,图书馆等等,那是不可能的,那也是非常消耗社会资源的一种分配方式

在这里也是同一个道理,这些成员函数并不是每个人所必需的,是可以为所有人所共同享用的,因此这些成员函数并不需要包含在对应的类当中,而是存储在了公共代码区

2.不含有任何成员变量的类的大小

那么如果一个类不含有任何成员变量呢?

无成员变量的类的大小:1

为什么呢?

这是规定的,这1个字节并不存储有效数据,就是为了表示定义的对象存在过

但是这个无成员变量的类也是有意义的

例如后面讲的仿函数都是不定义成员变量的

如果这个类的大小为0的话,那么实例化出来的对象岂不是0字节,也就是这个对象连地址都没有,也就是根本就不存在,这就跟实例化冲突了

3.类的实例化

那么什么是类的实例化呢?

说白了,就是定义一个类Date类型的变量,(注意C++中类定义变量可以称为对象,一回事,不过更习惯称之为对象)

int main()
{
  Date d;//这就是类的实例化
  return 0;
}
类和对象就类似于设计图和房子
只有在建造了房子之后,这个设计图中的桌子,床等等才会有
• 1
• 2

4.this指针

1.this指针的引出

我们在定义一个类的时候可能会出现下面这种情况:

我们明明初始化了,为什么还会这样?

因为Date类中的成员变量跟Init初始化函数的形参重名了,编译器会就近认为

这里的year=year的赋值是形参给形参自身赋值,所以并没有改变成员变量

那么怎么办呢?

第一种解决方法:

用简称代替形参,不过并不是很好,因为形参名称不写全了可能会产生歧义,而且需要去猜测形参的含义会加大程序员的工作难度,影响项目开发

第二种解决方法:

成员变量加上一些修饰符:

例如

在前面或后面加上_

或者在前面加上m_(m:member成员的意思)

或者m第一个首字母大写(驼峰法命名)

等等等等

第三种解决方法:this指针

2.被编译器隐藏的this指针

然后我们回头看看C语言和C++中对于Stack类的定义

可以发现它们的定义其实是一样的,只不过C++省略了this指针,把this指针隐藏了

编译器帮我们添加了this指针

那么为什么会报错呢?

是因为我们在形参列表中把this指针给显式定义了,其实编译器想告诉我们的是:

你不需要管this指针的事情,我会帮你做好的,你只需要管怎么去定义和使用函数即可

你要是管了,那就本末倒置了,你所要做的不是去为this指针的事情操心,而是去好好地写好你的函数,调用好你的函数就可以了

其实这里的

d1.Print()
的本质就是Print(&d1);
同理:
Date* dp=&d1;
dp->Print();
的本质就是:Print(dp);

3.this指针的特性

1.this指针的两个经典题目

下面请大家看一下这个代码,做一下这个选择题

1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
   void Print()
   {
   cout << "Print()" << endl;
   }
private:
   int _a;
};
int main()
{
   A* p = nullptr;
   p->Print();
   return 0;
}

答案是C,为什么呢?

首先这个代码没有任何语法问题,编译阶段不会对空指针解引用这个行为进行检查的

p不是空指针吗,为什么还能正常运行呢?

因为Print()函数中并没有对this指针进行解引用

没有对空指针进行解引用,因此运行时便不会报错,因此正常运行

那么再请大家看一下这个代码结果是什么呢?

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

首先,这个语法是没有问题的,编译阶段不会报错

但是Print函数当中出现了对空指针的解引用行为,因此这个代码在运行时会报错

2.this指针的特性

this指针被const修饰:

this指针不可以被赋值(即this指针的指向不可以被修改)

但是*this可以被赋值(即this指针所指向的对象的值可以被修改)

void Print(Date* const this)
• 1

还有一点:

this指针不能显式地传实参和形参
但是可以在类里面显式地使用
为什么支持显式地使用呢?
因为后面有一些情况下需要显式地写(有些情况下必须要使用this指针)
在上面的Date类中
void Print()
{
  cout << this->_year << " " << this->_month << " " << this->_day << endl;
}
这么定义Print函数也是可以的

说了这么多,这么麻烦,C++有什么厉害的呢?

类和对象有什么厉害的?

下面给大家表演一下

5.C和C++当中关于Stack的实现对比

这是C语言的实现

#include <assert.h>
typedef int DataType;
typedef struct Stack
{
  DataType* a;
  int capacity;
  int top;
}Stack;
void StackInit(Stack* ps)
{
  assert(ps);
  ps->a = (DataType*)malloc(sizeof(DataType) * 4);
  if (NULL == ps->a)
  {
    assert(0);
    return;
  }
  ps->capacity = 4;
  ps->top = 0;
}
void StackDestroy(Stack* ps)
{
  assert(ps);
  if (ps->a)
  {
    free(ps->a);
    ps->a = NULL;
    ps->capacity = 0;
    ps->top = 0;
  }
}
void CheckCapacity(Stack* ps)
{
  if (ps->top == ps->capacity)
  {
    int newcapacity = ps->capacity * 2;
    DataType* temp = (DataType*)realloc(ps->a,newcapacity * sizeof(DataType));
    if (temp == NULL)
    {
      perror("realloc申请空间失败!!!");
      return;
    }
    ps->a = temp;
    ps->capacity = newcapacity;
  }
}
void StackPush(Stack* ps, DataType data)
{
  assert(ps);
  CheckCapacity(ps);
  ps->a[ps->top] = data;
  ps->top++;
}
int StackEmpty(Stack* ps)
{
  assert(ps);
  return 0 == ps->top;
}
void StackPop(Stack* ps)
{
  if (StackEmpty(ps))
    return;
  ps->top--;
}
DataType StackTop(Stack* ps)
{
  assert(!StackEmpty(ps));
  return ps->a[ps->top - 1];
}
int StackSize(Stack* ps)
{
  assert(ps);
  return ps->top;
}
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()
  {
    _a = (DataType*)malloc(sizeof(DataType) * 4);
    if (NULL == _a)
    {
      perror("malloc申请空间失败!!!");
      return;
    }
    _capacity = 4;
    _top = 0;
  }
  void Push(DataType data)
  {
    CheckCapacity();
    _a[_top] = data;
    _top++;
  }
  void Pop()
  {
    if (Empty())
      return;
    _top--;
  }
  DataType Top()
  {
    return _a[_top - 1];
  }
  int Empty()
  {
    return 0 == _top;
  }
  int Size()
  {
    return _top;
  }
  void Destroy()
  {
    if (_a)
    {
      free(_a);
      _a = NULL;
      _capacity = 0;
      _top = 0;
    }
  }
private:
  void CheckCapacity()
  {
    if (_top == _capacity)
    {
      int newcapacity = _capacity * 2;
      DataType* temp = (DataType*)realloc(_a, newcapacity * sizeof(DataType));
      if (temp == NULL)
      {
        perror("realloc申请空间失败!!!");
        return;
      }
      _a = temp;
      _capacity = newcapacity;
    }
  }
private:
  DataType* _a;
  int _capacity;
  int _top;
};
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;
}

折起来之后C++只有一个class

而C语言有一堆函数,还有一堆形参,而且传参的时候比起C++来很麻烦

怎么样,C++比C语言简洁吧.

为什么会这样呢?这一切都要多亏了类和对象的设计理念

还有编译器为我们隐藏的this指针

以上就是C++入门3和类和对象上的全部内容,希望对大家有所帮助!

相关文章
|
4天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
21 4
|
5天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
18 4
|
28天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
25 4
|
28天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
19 1
|
28天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
20 4
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
19 1