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寄存器自动传递,不需要用户传递。

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