【C++学习】内联函数 | nullptr空指针 | 初步认识面向对象 | 类访问限定符 | 封装 | 类对象的内存对齐

简介: 【C++学习】内联函数 | nullptr空指针 | 初步认识面向对象 | 类访问限定符 | 封装 | 类对象的内存对齐

写在前面:

上一篇文章我介绍了引用和auto相关的知识,


如果有兴趣的话可以去看看:http://t.csdn.cn/j6jsI


这篇文章大概能够讲完C++入门的一些语法,开始类和对象的学习之旅。


目录


写在前面:


1. 内联函数


2. nullptr空指针


3. 初步认识面向对象


4. 类的引入


5. 类访问限定符


6. 封装


7. 类对象的内存对齐


写在最后:


1. 内联函数

我们先来看这样一个情况:

#include 
using namespace std;
int Add(int x, int y) {
  return x + y;
}
int main()
{
  for (int i = 0; i < 10000; i++) {
  cout << Add(i, i + 1) << endl;
  }
  return 0;
}

这段代码循环调用了一万次这一个Add函数,


创建销毁了一万次这个函数的函数栈帧,造成了大量的资源消耗,


我们该怎么解决这样的问题呢?


在学习C语言的时候,我们一般使用宏函数来解决这样的问题,


来看代码:

#include 
using namespace std;
#define Add(x, y) ((x) + (y))
int main()
{
  for (int i = 0; i < 10000; i++) {
  cout << Add(i, i + 1) << endl;
  }
  return 0;
}

使用宏函数本质上是一种替换,


将 Add ( i, i + 1 ) 替换成 ( ( i ) + ( i + 1 ) )


宏函数有他的优点也有缺点,


优点:不需要建立栈帧,提高调用效率;


缺点:复杂,容易出错,可读性差,不能调试,没有类型安全的检查。


C++就想着新增一种方法来解决这样的问题,减少宏函数的缺点,也就是内联函数。


内联函数的用法很简单,就是在函数前面加一个关键字:inline


#include 
using namespace std;
inline int Add(int x, int y) {
  return x + y;
}
int main()
{
  for (int i = 0; i < 10000; i++) {
  cout << Add(i, i + 1) << endl;
  }
  return 0;
}

加上inline 之后函数就会变成内联函数,


内联函数会在调用的地方展开,这样就没有函数调用了,


这就是内联函数,他不需要建立栈帧,提高了效率,


不复杂,不容易出错,可读性强,可以调试,几乎是一招解决了所有问题。


那你可能会问,如果内联函数这么牛逼,我们能不能把所有函数都搞成内联呢?


但是宏函数和内联函数都有一个适用场景,


他们适用于短小的频繁调用的函数,太长的是不适合的,会导致代码膨胀的问题。


实际上,内联仅仅只是一个建议,最终是否是内联,是编译器自己决定的。


一般而言,比较长的函数是不会成为内联的,一般递归也不会成为内联。


另外,在默认的debug模式下,一般是不支持内联的。


补充:如果要用内联,就别搞声明和定义分离,直接写就行了。


2. nullptr空指针

C语言已经有NULL了,为什么C++还要添加nullptr呢?


来看这段代码:


#include 
using namespace std;
void f(int x) {
  cout << "f(int x)" << endl;
}
void f(int* x) {
  cout << "f(int* x)" << endl;
}
int main()
{
  f(0);
  f(NULL);
  return 0;
}


NULL代表的是空指针,我们用NULL想调用第二个函数,但是,


这段代码的输出:

f(int x)
f(int x)

为什么会出现这样的情况?


我们可以看看NULL的底层是怎么样的:



他实际上就是宏定义出来的0,


我们来看nullptr:


#include 
using namespace std;
void f(int x) {
  cout << "f(int x)" << endl;
}
void f(int* x) {
  cout << "f(int* x)" << endl;
}
int main()
{
  f(0);
  f(NULL);
  f(nullptr); // #define nullptr ((void*)0)
  return 0;
}


输出:


f(int x)
f(int x)
f(int* x)

他调用的就是第二个函数,


为什么呢?


其实就是因为nullptr的类型是 void*,算是给NULL打的一个补丁,


所以我们以后一般尽量都使用nullptr就行。


3. 初步认识面向对象

用一个经典的例子来解释面向对象和我们之前学习的面向过程的区别:


比如说一个外卖系统:


如果是用面向过程的思想解决:


就可以分成几个步骤:上架,点餐,派单,送餐等等。


如果是用面向对象的思想解决:


就可以分成几个对象:平台,商家,骑手,用户等等。


面向对象的优势是可以在同一个抽象的系统中实例化出多个对象,


关注的是对象和对象之间的关系和交互。


这些听起来很抽象,慢慢理解就行。


4. 类的引入

其实我们之前C语言就有结构体这一种自定义类型,


到了C++,结构体就被升级成了类,来看例子:

#include 
using namespace std;
//并且在C/C++里面用{}括起来的位置都是一个域,这里是就是类域
struct Stack {
  //成员函数(类内可以放成员函数)
  void Init() {
  //...
  }
  void Push() {
  //...
  }
  //...等等
  //成员变量
  int* a;
  int top;
  int capacity;
};
// C++兼容C语言,struct以前的用法都能继续用
// 而struct升级成了类,类的类名能直接当类型使用
int main()
{
  struct Stack st1;
  Stack st2;
  //调用的时候就可以这样调用
  st2.Init();
  st2.Push();
  return 0;
}

其实C++更喜欢使用的是class,也就类,class和struct基本上没什么区别,


当我们把struct改成class之后:



发现编译器报错了,这是为什么?


这里就要说到另一个知识点。


5. 类访问限定符

C++给出了三种访问限定符:


public(公有)


private(私有)


protected(保护)


而公有表示的是能在类外面访问,私有和保护表示的是不能在类外面访问。


私有和保护在平时的使用上是一样的,只有在继承的时候有所区别。


这个时候就能回答为什么改成class之后编译器会报错了,


因为struct类域默认是公有,而class的类域默认是私有。


(这个设计其实就是为了兼容C语言)


那么回归正题,平时我们在定义的类时候,


我们习惯将成员变量放在private私有,将成员函数放在public公有。


说人话就是:我想给你用的就放公有,不想让你碰到的就放在私有:


#include 
using namespace std;
//并且在C/C++里面用{}括起来的位置都是一个域,这里是就是类域
class Stack {
public:
  //成员函数(类内可以放成员函数)
  void Init() {
  //...
  }
  void Push() {
  //...
  }
  //...等等
private:
  //成员变量
  int* a;
  int top;
  int capacity;
};
// C++兼容C语言,struct以前的用法都能继续用
// 而struct升级成了类,类的类名能直接当类型使用
int main()
{
  struct Stack st1;
  Stack st2;
  //调用的时候就可以这样调用
  st2.Init();
  st2.Push();
  return 0;
}


这个时候又出现了新的问题,


来看下面这段代码:

#include 
using namespace std;
class Date {
public:
  void Init(int year) {
  year = year;
  }
private:
  int year;
};
int main()
{
  Date d;
  d.Init(2023);
  return 0;
}

这段代码中 Init 函数里面的 year = year ,你知道是谁赋值给谁吗?


编译器并没有报错,跑过了,


这里我再讲的清楚一点,这两个year究竟是成员变量还是函数形参?


因为这样的原因,我们一般习惯给成员函数加一点标记:

#include 
using namespace std;
class Date {
public:
  void Init(int year) {
  _year = year;
  }
private:
  int _year;
};
int main()
{
  Date d;
  d.Init(2023);
  return 0;
}


给成员函数前面加上一个_ ,我习惯这样区分,其实还有其他的区分方式,


每个人又不一样的代码风格,这个就看个人喜好或者是其他的需求了。


这里是没有硬性的要求的。


6. 封装

其实我们将给别人用的成员函数放在公有,


把成员变量放在私有,其实这就是封装思想的一种体现。


封装是什么?


将数据和数据的方法有机结合,隐藏对象的属性和实现细节,


仅对外公开接口来和对象进行交户的行为就是封装。


封装的本质其实是一种更好的管理。


这里补充一句:类内的成员函数默认都是内联函数。


7. 类对象的内存对齐

我们在学习C语言结构体的时候,曾经学过结构体的内存对齐,


那么下面这个类的内存对齐是多少呢?  

#include 
using namespace std;
class Stack {
public:
  void Init() {
  //...
  }
  void Push() {
  //...
  }
  //...等等
private:
  //成员变量
  int* a;
  int top;
  int capacity;
}; 
int main()
{
  Stack st1;
  cout << sizeof(st1) << endl;
  return 0;
}

输出:


12

是的,你没有看错,类的内存对齐计算方法是跟结构体一模一样的,


而且,类的成员函数是不被计算在内的。


这个时候你可能会有疑问,为什么类的成员函数没有被计算在内?


来看这样一个例子:

#include 
using namespace std;
class Stack {
public:
  void Init() {
  //...
  }
  void Push() {
  //...
  }
  //...等等
//private:
  //成员变量
  int* a;
  int top;
  int capacity;
}; 
int main()
{
  Stack st1;
  st1.top = 0;
  st1.Init();
  Stack st2;
  st2.top = 10;
  st2.Init();
  return 0;
}


我将类内成员的访问限定设置成公有,


那么 st1 的成员变量 top 跟 st2 的成员变量 top 是同一个变量吗?


显然不是,他们有着各自独立的空间,存放着不同的数据,


那么,st1 调用的 Init 函数和 st2 调用的 Init 函数他们调用的是同一个函数吗?


实际上,他们调用的就是同一个函数,


不然要是每实例化一个新的对象就要拷贝一份成员函数,那浪费的资源可太多了。


那问题来了,这个函数他存放在哪里呢?为什么两个对象都能调用的到?


这个问题就由我下一篇文章再来揭晓了。


写在最后:

以上就是本篇文章的内容了,感谢你的阅读。


如果感到有所收获的话可以给博主点一个赞哦。


如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~


相关文章
|
9天前
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
29 4
2023/11/10学习记录-C/C++对称分组加密DES
|
2月前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
92 11
|
2月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
30 1
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
120 13
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
37 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
142 4
|
4月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
4月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)