C++入门2——类与对象1(类的定义和this指针)

简介: C++入门2——类与对象1(类的定义和this指针)

1. 对面向对象与面向过程的初步认识

C++入门1中我们已经知道:C语言是面向过程的,C++是面向对象的。

那哗啦啦说了一大堆,到底什么是面向过程编程、什么是面向对象编程呢?面向对象编程难道就是面对面,对着自己的恋爱对象写代码吗?——哈哈!开个玩笑,当然不是这样的。下面请让我用吃饺子的例子初步解释一下什么是面向过程编程、什么是面向对象编程吧!

C语言吃饺子

C语言是面向过程的,关注的是过程,注重实现这个功能的步骤,第一步干什么、第二步干什么......分析出求解问题的步骤,通过函数调用逐步解决问题。

那么用C语言得到一碗饺子的方法就可以表达为以下图解:

C++吃饺子

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。在面向对象编程中,抛弃了函数,想要实现一个功能不再是通过函数的叠加调用实现的了,而是通过对象。

那么C++用吃饺子就可以直接打开外卖APP找到饺子,下单就可以了。在这个场景中,外卖APP就是对象。人不需要关注饺子是怎么擀皮,怎么剁馅,怎么包的......

2. 类

2.1 类的引入

在讲解类之前,我们先回忆一下用C语言实现顺序表、以及初始化和实现头插时是怎样定义的:

struct SeqList
  {
    int a[1000];          
    int size;    
  };
 
void SLInit(struct SeqList* SL);
void SLPushFront(struct SeqList* SL);

在C语言中,结构体与函数(数据和方法)是分离的

但在C++中,C++在兼容所有C语言struct用法的同时,又把struct升级成了类

同时,在C语言申请结构体变量时需要struct SeqList SL;如果不想写struct又必须用到typedef(如果忘记了C语言结构体的基础知识,详看初识结构体,相信你一定会有更深的印象),如此繁琐,C++就摒弃了这种写法,在申请结构体变量时,类名就是类型,不需要再加struct,直接就可以SeqList SL;

在C++中,函数也可以直接定义在类(结构体)里面:

struct SeqList
{
  int a[1000];
  int size;
 
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};

同样的,C语言在调用函数时需要这样写:

struct SeqList
  {
    int a[1000];          
    int size;    
  };
 
void SLInit(struct SeqList* SL);
void SLPushFront(struct SeqList* SL, int x);
 
int main()
{
  struct SeqList SL;
  SLInit(&SL);
  SLPushFront(&SL, 1);
  SLPushFront(&SL, 2);
  return 0;
}

C++就可以这样写:

struct SeqList
{
  int a[1000];
  int size;
 
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};
 
int main()
{
  SeqList SL;
  SL.SLInit(&SL);
  SL.SLPushFront(1);
  SL.SLPushFront(2);
  return 0;
}

综上所述,谁更方便和简洁显而易见。


可是在C++中,结构体的定义更喜欢用class替代struct,但是把class换成struct又会使程序发生一些变化。

2.2 类的定义

上面说到,把class换成struct会使程序发生一些变化,具体发生哪些变化呢?下面就让我们详细探究一下。

与结构体相似:

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。

类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

2.3 类的访问

知道了类的定义,其实就与结构体有很大相似性,那么新的问题又来了,类又如何来进行访问呢?

类的访问限定符:

1. public修饰的成员在类外可以直接被访问

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

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

4. 如果后面没有访问限定符,作用域就到 } 即类结束。

5. class的默认访问权限为private,struct为public(因为struct要兼容C)

(现阶段认为protected与private相同,不同点会在后面详细探究)

具体使用请看如下代码:

①数据为私有,函数为公有

class SeqList
{
private:
  int a[1000];
  int size;
 
public:
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};
int main()
{
  SeqList SL;
  SL.SLInit(&SL);
  SL.SLPushFront(1);
  SL.SLPushFront(2);
  return 0;
}

验证:

②数据和函数都为私有:

class SeqList
{
private:
  int a[1000];
  int size;
 
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};

这时的函数被定义为了私有,所以不能被正常访问:


可是上面已经说过了,struct与class的用法是相同的呀!我也知道了C++经常用的是class,那他们二者是不是一点区别也没有呢?

答案在上面已经提到了:

class的默认访问权限(即不加访问限定符的时候)为private,struct为public(因为struct要兼容C),其他地方二者都是相同的。

验证如下:

class:

class SeqList
{
  int a[1000];
  int size;
 
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};
 
int main()
{
  SeqList SL;
  SL.SLInit(&SL);
  SL.SLPushFront(1);
  SL.SLPushFront(2);
  return 0;
}

换成struct:

struct SeqList
{
  int a[1000];
  int size;
 
  void SLInit(SeqList* SL)
  {
    int a[1000] = { 0 };
    int size = 0;
  }
  void SLPushFront(int x)
  {
    //......
  }
};
 
int main()
{
  SeqList SL;
  SL.SLInit(&SL);
  SL.SLPushFront(1);
  SL.SLPushFront(2);
  return 0;
}

所以,这个问题的正解为:

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private。(在继承和模板参数列表位置,struct和class也有区别,后续会详细介绍。)

3. this指针

3.1 this指针的引出

这里我们写一个交换类:

#include <iostream>
using namespace std;
class Swap
{
public:
  void Init(int a, int b)
  {
    _a = b;
    _b = a;
  }
  void Print()
  {
    cout << _a << " " << _b << endl;
  }
private:
  int _a;
  int _b;
};
 
int main()
{
  Swap s1;
  Swap s2;
  s1.Init(8, 5);
  s2.Init(5, 8);
  s1.Print();
  s2.Print();
  return 0;
}

运行结果:

结果显示s1交换后为5 8,s2交换后为8 5,转到反汇编看一下调用的Print函数是否为同一个函数:

函数地址确实是相同的呀!

那么问题来了,s1、s2都调用的Print函数,我也没有传递任何参数,输出的结果应该是一样的才对呀!编译器是怎么知道s1应该输出5 8,s2应该输出8 5呢?

说到这里,我们的this指针就自然而然地引出了:

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

“该指针”说的就是this指针,也就是说,C++在这里增加了一个隐藏的this指针,在函数调用的时候也是通过指针传参的方式进行的。

如图,相当于:

需要注意的是:这里只是说明了存在this指针,编译器已经帮我们进行了传参,所以不用我们再把this相关的实参和形参再写出来,如果再写出来,编译器就会报错。就好比我们耳熟能详的一句话:“哪有什么岁月静好,不过是有人为我们负重前行!”编译器已经为我们“负重前行”了,我们就不要再添乱把this相关的实参和形参加上了。

3.2 this指针的特性

上面已经说过,我们不能显示地写this的实参和形参:

void Print(Swap* this)//错误
  {
    cout << this->_a << " " << this->_b << endl;
  }

但是却可以在类里面显示地使用:

class Swap
{
public:
  void Init(int a, int b)
  {
    _a = b;
    _b = a;
  }
  void Print()
  {
    cout << this->_a << " " << this->_b << endl;
    cout << _a << " " << _b << endl;
  }
private:
  int _a;
  int _b;
};

另外也需要注意:

事实上this指针使用const修饰的,this指针本身是不能被修改的,即:

//void Print(Swap* const this)

验证如下:

但是它所指向的类型是可以修改的。

综上所述,我们写一段代码验证以上说法,并且打印一下s1、s2的地址:

#include <iostream>
using namespace std;
class Swap
{
public:
  void Init(int a, int b)
  {
    _a = b;
    _b = a;
  }
  void Print()
  {
    //在类里显示地使用this
    cout << this << endl;
    cout << _a << " " << _b << endl;
  }
private:
  int _a;
  int _b;
};
 
int main()
{
  Swap s1;
  Swap s2;
  cout << "&s1=" << &s1 << endl;
  cout << "&s2=" << &s2 << endl;
  s1.Init(8, 5);
  s2.Init(5, 8);
  s1.Print();
  s2.Print();
  return 0;
}

this指针的特性总结:

1. this指针的类型:类型为* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用。

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参,所以对象中不存储this指针。

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

相关文章
|
28天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
50 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
88 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
105 4
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
120 13
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
37 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
141 4
|
4月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
4月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)