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