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

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

一.内联函数

C++针对于C语言的宏函数进行了优化,设计出了内联函数这一语法

其中在著名书籍《Effective C++》中,作者提出了一个很好的条款

他建议在C++程序中使用

enum 枚举常量,const 定义的常量来替代宏定义的常量

使用内联函数来替代宏函数

1.宏函数的缺点

关于这些情况的举例,大家可以看我的另一篇博客:

C语言预处理及宏和函数的区别与各自优劣点的详解

2.宏函数的优点

3.内联函数的语法

这里以Add函数为例

inline int Add(int a,int b)
{
  return a+b;
}

只需要在函数前面加上关键字inline即可

可见语法非常简单

4.内联函数的优缺点

5.内联函数的使用条件

既然内联函数这么好,那么我们是不是就可以把所有的函数都设计成为内联函数呢?

当然是不行的,因为内联函数也是需要在调用的地方被展开的,只不过不是简单的宏的字符串替换而已

而且:

一般来说C++程序中10行以上的代码就无法成为内联函数

在《C++ primer》中关于inline的建议:

6.内联函数的展开

如果我们想看一下内联函数是怎么展开的呢?

这里使用的是VS2019,在默认配置下内联函数是不会展开的

那么怎么才能展开它呢?

在debug模式下,需要对编译器进行设置(因为debug模式下,编译器默认不会对代码进行优化)

右键找到属性

在调试信息格式这里修改为程序数据库这个选项

修改后:

在内联函数扩展这里改为只适用于_inline(/Ob1)

然后点击应用,确定

然后我们调试查看汇编代码

然后我们去掉inline,调试查看汇编代码

发现内联函数中没有call指令,也就是说inline函数并没有开辟栈帧,直接在原位置展开了

7.内联函数的一大注意事项

内联函数是不可以将声明跟定义分离的,为什么呢?

因为内联函数会直接在调用的地方展开,所以不需要去调用(汇编代码当中的call命令),

因此内联函数就不需要将地址存到符号表当中,因此在链接的时候通过符号表去查找函数的定义时便找不到内联函数的定义,因此会发生链接时错误

1.内联函数声明跟定义分离

下面给大家演示一下这个错误

我们定义了test.h,test.cpp,main.cpp并且生成解决方案:编译器爆出了链接时错误

那么请大家再看一下前面这段代码

2.内联函数声明跟定义分离的"奇怪"现象

这里为什么调用getf函数就可以成功执行f函数了呢?

因为getf函数是在test.cpp文件中定义的,也就是说getf函数的定义是跟f函数的定义放在同一个cpp文件中的,

因此getf函数想要寻找f函数根本不需要等到链接阶段(因为它们都被编译到了同一个.o目标文件中,而链接阶段是链接多个.o目标文件的阶段)

综上,内联函数声明跟定义分离的话,这个内联函数只能在它所定义的cpp文件中使用,在其他cpp文件中无法使用

因此内联函数不要声明跟定义分离

二.C++11对于C++语法的补充

1.auto关键字

1.auto关键字可以自动推导类型

//auto关键字
//可以自动推导类型
int main()
{
  int a = 0;
  int b = a;
  auto c = a;
  auto d = &a;
  auto* e = &a;
  auto& f = a;//f是a的别名,a是int,所以f也是int,因为f就是a
  f++;
  cout << typeid(c).name() << endl;//typeid可以打印对象的类型
  cout << typeid(d).name() << endl;
  cout << typeid(e).name() << endl;
  cout << typeid(f).name() << endl;
  //指针可以显式写,也可以隐式写
  //但是引用只能显式写
  /*
  int
  int *
  int *
  int
  */
  return 0;
}

auto的真正有价值的用法:定义对象时,如果该对象的类型较长,用auto比较方便

//auto真正的用法:定义对象时如果类型较长,用它比较方便
#include <vector>
#include <string>
int main003()
{
  vector<string> v;
  vector<string>::iterator it = v.begin();
  //简化写法:让这个类型定义的短一些,方便
  auto it = v.begin();
  return 0;
}

这里的vector容器和string容器都属于C++STL库中的知识,我们以后会进行重点介绍

iterator:迭代器,我们以后也会重点介绍

这里的vector容器就相当于数据结构中的顺序表,string就相当于数据结构中的字符串

2.auto的局限性

1.auto不能做参数

因为无法进行自动推导

可以使用模板来解决(关于模板的知识我们以后会进行重点介绍)

2.auto不能用作返回值

auto不能做返回值(新的规则可能支持了,但是VS中不支持,而且auto作为返回值的类型并不好,就像是python中的函数返回值类型一样)

因为调用函数时看不到函数的返回值,所以调用函数时很麻烦,还需要去看那个函数的源代码,太不方便了

因此就算C++引入了auto作为返回值,但是建议不要用auto作为函数的返回值

3.auto不能用来定义数组
4.auto定义时必须初始化

否则无法知道用auto定义的变量到底是什么类型

2.范围for

int main()
{
  int arr[] = { 1,2,3,4,5 };
  //一般情况下这里都用auto
  //因为如果arr变为double,auto也不用改
  //依次取数组中的数据赋值给e对象,自动判断结束,自动++往后走
  for (auto e : arr)
  {
    cout << e << " ";
  }
  cout << endl;
  for (int e : arr)
  {
    cout << e << " ";
  }
  cout << endl;
  //修改数组中的每个数据
  //需要加上&才能修改,这是指针所替代不了的
  //因为指针赋值时需要取地址,而范围for是把数组中的数据进行赋值
  for (auto& e : arr)
  {
    e *= 2;
  }
  for (auto e : arr)
  {
    cout << e << " ";
  }
  cout << endl;
  /*
1 2 3 4 5
1 2 3 4 5
2 4 6 8 10
  */
  return 0;
}
//这里arr传参时退化为指针,所以这里不能范围for
void testfor(int arr[])
{
  for (auto e : arr)//err
  {
    cout << e << " ";
  }
}
//二维数组呢?:也是指针,只不过是指向一维数组的指针
void testfor2(int arr[3][3])
{
  //int (*)[3]:数组指针
  //所以也不可以
  for (auto e : arr)//err
  {
    cout << e << " ";
  }
}

3.nullptr

在C++中NULL的定义跟在C语言中的定义不同

C语言中的: ((void*)0)

C++中的: 0

也就是说C语言中的NULL的确是空指针,而C++中的NULL却是字面常量0

大家看一下下面这种现象

请注意:函数的参数可以不要名字,只给一个类型

那这有什么意义呢?

我们在以后会学习运算符重载,而运算符重载中区分前置++和后置++时就需要用到这种参数

我们以后会重点讲解的

那么怎么修改这个错误呢?

C++11中新引入了一个关键字nullptr

这个nullptr就是(void*(0))

三.类和对象上

C++对于C语言中的结构体进行了优化,将结构体逐步演化成了类

又因为实际需求和C语言中的结构体的语法有所差异,(这个差异接下来会讲到)

所以引入一个关键字class来定义类

1.C++中的结构体对于C语言的优化

1.C++中结构体的定义优化

大家是不是在写C语言代码的时候都对一个现象感到很反感

比方说我要在C语言中定义一个单链表节点

struct SListNode
{
  struct SListNode* next;
  int val;
};
定义一个结构体变量必须要加struct关键字
struct SListNode* phead=NULL;
就算我们typedef起别名了
typedef struct SListNode
{
  //这里还要加上struct.....
  struct SListNode* next;
  int val;
}SLNode;
定义一个结构体变量必须要加struct关键字
SLNode* phead=NULL;

必须要加这个struct是真的挺让人难受的,C++创始人也是这么想的,所以在C++语法中可以这么写

struct SListNode
{
  //这里不需要加struct关键字
  SListNode* next;
  int val;
};
定义一个结构体变量不需要加struct关键字
SListNode* phead=nullptr;

第二个优化:C++中的结构体里面可以放函数

比方说我们要定义一个Stack栈

在C语言中我们只能这样定义

struct Stack
{
  int* a;
  int top;
  int capacity;
};
void StackInit(struct Stack* ps)
{
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}
void StackPush(struct Stack* ps, int x)
{
  //扩容
  //ps->a[ps->top++] = x;
}

而在C++中我们可以这么定义

请注意在C++中结构体,类都属于一个域,在不同的域中可以有重名函数

而且C++中的结构体中的函数访问结构体中的成员变量时可以不加结构体指针直接访问(其实是编译器帮助我们省略了this指针,这个我们在这篇博客最后会介绍this指针)

//C++中结构体
struct Stack1
{
  int* a;
  int top;
  int capacity;
  //这里的Init和Push可以不用加上结构体指针
  //因为类也是一个域,只有在同一个域中才会构成重载
  //类中的函数可以访问该类中的成员
  void Init()
  {
    a = nullptr;
    capacity = top = 0;
  }
  void Push(int x)
  {
    //扩容
    //a[top++] = x;
  }
};

C++中的结构体因此被称为类

不过C++中的结构体依然保留了C语言中结构体的用法,因为C++是兼容C语言的

而在C语言当中结构体中的成员变量是可以通过结构体类型的变量去访问的

也就是说C语言中的结构体中的成员变量是公有的

那么就会发生一个不太好的现象

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