C++第四节课 - 内联函数 + 初认类

news2024/11/25 16:36:38

一、auto关键字

C++中可以使用typeid打印变量的类型

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	int b = a;
	auto c = a;
	auto d = 1 + 1.11;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

但是上面看不出来auto的实际作用,但是auto对于类型很长的名字的作用非常大!

        如果一个函数需要运行很多次,普通的函数需要创建多次函数栈帧,造成效率低下!此时我们可以使用宏函数!

        宏函数:通常在预处理阶段进行展开,使用宏定义(如C语言中的#define)来替代代码。它们不占用内存空间,直接在编译时替换。

  • 优点:不需要建立栈帧,提高调用效率;
  • 缺点:复杂,容易出错,可读性差,不能调试!

Auto的补充:

1、Auto的使用规则

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;  // b和a是等价的!
	auto& c = x;   // c是x的引用!
	return 0;
}

        用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须
加&!

auto常用于for循环:

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;  // b和a是等价的!
	auto& c = x;   // c是x的引用!

	int arr[] = { 1,2,3,4,5 };
	for (auto& x : arr)
	{
		x++;
	}
	for (auto& x : arr)
	{
		cout <<x<<endl;
	}
	return 0;
}

        for (auto& x : arr):这是一个范围基于的 for 循环,auto& 表示 x 是 arr 中每个元素的引用。使用引用可以避免复制元素,提高效率。 

2、Auto不能推导的场景

  • auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
  • auto不能直接用来声明函数
void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {4,5,6};
}

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有
lambda表达式等进行配合使用。

二、内联函数

引入:内联函数

概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

缺点: 

每次调用的指令都是一样的,那么当调用多次,重复的指令不会相乘,而是相加起来! 

内联展开是将Func中的50条指令插入到调用的位置上去!(导致可执行程序变大!)

因此,内联函数适用于短小的频繁调用的函数!

需要注意的是:inline对于编译器仅仅只是一个建议,最终是否成为inline,需要编译器自己决定!

像下面类似的函数就算加上了inline也会被否决掉!

  • 比较长的函数;
  • 递归函数;

默认的debug模式下,inline不会起作用,否则不方便调试!(默认如果起作用,相当于指令展开了,没有创建函数栈帧,无法进入函数内部,不能调试!)

改成releas可以起作用,但是releas不容易查看汇编代码!(需要自己设置)

  • 右键->属性->C/C++ -> 常规 右边的调试信息格式改为程序数据库;
  • C/C++ -> 优化 右边的内联函数扩展  ,改为只适用!

此时再次运行就会发现,指令中没有call指令!

但是如果我们对函数做一些修改,将函数体变长!

此时就算我们设置了,编译器仍有可能不会执行inline!

注意点:

        inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到!

此时会显示:编译通过了,但是无法链接上! 

从声明处得知:该函数是一个内联函数,此时编译器会想把内联函数展开,但是此时只有声明没有地址!因此编译器会制动call地址从符号表,但是找不到,因此发生链接错误!(内联函数不会进入符号表,不会生成地址!)

因此我们采用下面方式:

直接将函数的声明+实现写在.h文件中!(可以只写定义,此时用到的地方直接拿来展开!)

三、指针空值nullptr

在传统的C头文件(stddef.h)中,可以看到如下代码:

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。 

void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}
        程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的 初衷相悖。(如果要调用下面的函数还需要将NULL的类型强制转化!)
        在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	f(nullptr);
	return 0;
}

nullptr会自动匹配指针类型!

注意点:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

为什么函数的参数类型为int*,但是可以传入void*的空指针呢?

在 C 和 C++ 中,当函数的参数类型为 `int*`,而传入的指针类型为 `void*` 时,传入空指针(如 `NULL` 或 `nullptr`)时会发生隐式转换,但需要注意以下几点:

1. 空指针的隐式转换:如果你传入的是空指针(例如 `NULL` 或 `nullptr`),那么无论是 `void*` 还是其他类型的指针,都会被视为有效的空指针。空指针可以被隐式转换为任何类型的指针,包括 `int*`。例如:

   void* ptr = nullptr; // 或者 ptr = NULL;
   int* intPtr = ptr;   // 隐式转换,合法

2. 非空指针的转换:如果你尝试将一个非空的 `void*` 指针传递给一个期望 `int*` 的函数,编译器不会自动进行转换。你需要显式地将 `void*` 转换为 `int*`,例如:

   void* ptr = /* some valid memory */;
   int* intPtr = static_cast<int*>(ptr); // 显式转换

3. 函数参数:如果函数的参数是 `int*`,并且你传入的是 `void*`,编译器会要求你进行显式转换,除非你传入的是空指针(如 `nullptr`)。例如:

   void func(int* p) {
       // 函数体
   }

   void* voidPtr = nullptr; // 空指针
   func(static_cast<int*>(voidPtr)); // 显式转换

4. **C++11 及以后的版本**:在 C++11 及以后的版本中,使用 `nullptr` 是推荐的做法,它是一个类型安全的空指针常量,避免了 `NULL` 可能引起的混淆。

总结:当函数的参数为 `int*`,而传入的指针类型为 `void*` 时,传入空指针会自动被视为有效的空指针,但对于非空指针,必须进行显式转换。空指针的隐式转换是安全的。

类和对象

一、初步认识

二、引入 

struct升级成了类

struct stack
{
	int* a;
	int top;
	int capicity;
};

int main()
{
	struct stack s1;
	stack s2;
	return 0;
}

类名就是类型!

因此我们可以采用以前C语言的定义方式来定义s1;

还可以使用C++的方式来定义s2;

类里面可以定义重名的函数(类里面也定义了一个域---类域)

C++/C中只要是花括号括起来的,就是定义了一个域!

成员函数和成员变量的顺序没有要求!

示例:

typedef int DataType;
struct Stack
{
	// 定义成员变量
	DataType* arr;
	size_t size;
	size_t capacity;
	// 定义成员函数
	void Init(size_t capacity = 8)
	{
		arr = (DataType*)malloc(sizeof(DataType) * capacity);
		if (arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		size = 0;
		capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// 扩容
		arr[size] = data;
		++size;
	}
	DataType Top()
	{
		return arr[size - 1];
	}
	void Destory()
	{
		if (arr)
		{
			free(arr);
			arr = nullptr;
			capacity = 0;
			size = 0;
		}
	}
};
int main()
{
	struct Stack s;
	s.Init(10);
	s.Push(1);
	s.Push(2);
	s.Push(3);
	cout << s.Top() << endl;
	s.Destory();

	Stack s2;
	s2.Init();
	s2.Push(2);
	s2.Push(3);
	s2.Destory();
	return 0;
}

三、类的定义

C++中将struct升级为了一种特殊的类(主要是为了兼容以前的C)

在 C++ 中,`class` 和 `struct` 都用于定义用户自定义的数据类型,但它们之间有一些关键的区别:

1. 默认访问控制:
   - class:默认的成员访问控制是 `private`。这意味着如果没有显式指定访问修饰符,类的成员(变量和函数)将是私有的。
   - struct:默认的成员访问控制是 `public`。这意味着如果没有显式指定访问修饰符,结构体的成员将是公共的。

   class MyClass {
       int x; // 默认 private
   };

   struct MyStruct {
       int x; // 默认 public
   };

2. 继承的默认访问控制:
   - class:在继承时,默认的继承访问控制是 `private`。这意味着如果没有显式指定继承类型,基类的成员将被视为私有。
   - struct:在继承时,默认的继承访问控制是 `public`。这意味着如果没有显式指定继承类型,基类的成员将被视为公共。

   class Base {};
   class Derived : Base {}; // 默认 private 继承

   struct BaseStruct {};
   struct DerivedStruct : BaseStruct {}; // 默认 public 继承

3. 用途:
   - class:通常用于定义具有封装、继承和多态特性的复杂数据类型,强调数据的隐藏和保护。
   - struct:通常用于定义简单的数据结构,强调数据的公共访问,常用于数据聚合。

4. 功能:
   - 在 C++ 中,`class` 和 `struct` 在功能上是相同的。两者都可以包含成员变量、成员函数、构造函数、析构函数、运算符重载等。唯一的区别在于默认的访问控制

5. 兼容性:
   - `struct` 可以被视为一种特殊类型的 `class`,因此可以在 `struct` 中使用所有 `class` 的特性。

总结:`class` 和 `struct` 的主要区别在于默认的访问控制和继承方式。选择使用 `class` 还是 `struct` 通常取决于设计意图和代码的可读性。

四、访问限定符

C++一共有三种访问限定符

注意点:

1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问,但是在类中可以调用;(此处protected和private是类似的)(暂定认为protected和private是一样的!)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)

问题:C++中struct和class的区别是什么?

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

五、类的定义方式

类的定义方式有两种:

  • 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
    联函数处理。因此C++一般长的函数不会在类中定义,但是如果真的在类中定义了代码较长的函数,编译器会自动处理为不是内联!

  •   类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

命名规则的规范化:

// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
 void Init(int year)
 {
 // 这里的year到底是成员变量,还是函数形参?
 year = year;
 }
private:
 int year;
};

        在 Init 函数中,参数 year 和类的成员变量 year 同名。这会导致一个问题:在函数体内,year 默认指向的是函数的参数,而不是类的成员变量。 (局部域优先)

        代码中的 year = year; 实际上是将函数参数 year 赋值给自己,这样的赋值没有任何效果,因为它只是将参数的值赋给了参数本身。类的成员变量并没有被初始化或赋值。 

所以一般采用下面的方法:

class Date
{
public:
 void Init(int year)
 {
 _year = year;
 }
private:
 int _year;
};
// 或者这样
class Date
{
public:
 void Init(int year)
 {
 mYear = year;
 }
private:
 int mYear;
};

使用_/m修饰成名变量,将成员变量和参数进行区分!

六、封装

在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
        封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
        封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

        对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以
及键盘插孔等,让用户可以与计算机进行交互即可。
        在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节控制哪些方法可以在类外部直接被使用。 

上面的代码就采用了封装的思想(将具体操作封装成一个函数),下面的代码就是由于没有封装,因此看着比较乱;

采用这种方式将数据和方法封装成一个类,将方法设置为公有,数据设置为私有;

此时使用者只能通过函数来访问,而不能直接访问数据! 

七、类的作用域

目前遇到了类域,局部作用域,全局域,命名空间域!

  • 在不同的域中可以定义同一变量!
  • 域会限制对它的访问,编译器有自己的搜索规则,一般现在局部域进行访问,如果在类中会先在类域中进行搜索!类中的搜索顺序是局部-> 类中->全局(命名空间)
  • 局部域和全局域会影响生命周期;(局部变量和全局变量的作用域(即它们可以被访问的范围)直接影响它们的生命周期。局部变量的生命周期较短,仅在其所在的函数或代码块内有效,而全局变量的生命周期较长,贯穿整个程序的执行。)
  • 类域和命名空间域不会影响生命周期;(类域和命名空间域的作用域并不改变变量的生命周期。类的成员变量的生命周期依赖于对象的生命周期,而命名空间中的变量的生命周期与全局变量相同。因此,虽然它们的作用域可能会影响变量的可见性,但不会影响它们的创建和销毁时机。)

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时需要使用 :: 
作用域操作符指明成员属于哪个类域

class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;
}

在.h中的成员变量是声明而不是定义!

对于变量来说,声明和定义真正的区别是开不开空间!(不开空间就是声明!)

在.cpp文件中进行这样的操作就是定义了一个对象!(类实例化对象,也叫做对象定义!)

直接通过类访问是声明,不能进行初始化!

但是可以通过实例化对象来进行初始化! 

八、实例化对象的大小

结论:对象的大小只考虑成员变量,不考虑成员函数!

  • 210和213两行调用的是不同的成员变量;
  • 211和214调用的是同一个成员函数!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2123574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ActiveMQ 的网络连接及消息回流机制

1、ActiveMQ 的网络连接 activeMQ 如果要实现扩展性和高可用性的要求的话&#xff0c;就需要用用到网络连接模式。 NetworkConnector&#xff1a;主要用来配置 broker 与 broker 之间的通信连接 如上图所示&#xff0c;MQ 服务器1 和MQ 服务器2 通过 NewworkConnector 相连&…

PhotoZoom 9安装包下载-图片无损放大编辑PhotoZoom 9 Pro软件安装

PhotoZoom 9 Pro是一款专业的图像放大软件&#xff0c;旨在提供高质量的图像放大和改进功能。适用于摄影师、设计师、印刷专业人员以及任何需要将图像放大到更大尺寸并保持高质量的用户。它使用先进的放大算法&#xff0c;能够有效地处理不同类型的图像&#xff0c;并在保持细节…

使用切换 JDK 的方式优化部署微服务占用内存过高的问题

使用切换 JDK 的方式优化部署微服务占用内存过高的问题 一、前言二、下载 J9 虚拟机的JDK三、切换 JDK1、上传到服务器2、解压3、修改 JDK 路径4、解决 JDK 没有切换成功的问题 一、前言 前段时间在服务器部署了微服务项目&#xff0c;但即使限制了每个服务的堆&#xff0c;内…

苹果首款AI手机发布!iPhone 16全新AI功能体验感拉满

苹果于2024年秋季盛大发布iPhone 16系列&#xff0c;带来前所未有的AI智能体验。iPhone 16系列不仅硬件全面升级&#xff0c;更融入了尖端的AI技术&#xff0c;为用户带来更加智能化的生活体验。 在科技春晚的舞台上&#xff0c;苹果不负众望地揭开了iPhone 16系列的神秘面纱。…

【贪心算法】(一)贪心算法理论及基础习题

【贪心算法】贪心算法理论及基础习题&#xff08;一&#xff09; 理论基础简单例题分发饼干K次取反后最大化的数组和柠檬水找零买卖股票的最佳时机 Ⅱ单调递增的数字摆动序列 两个纬度权衡问题分发糖果根据身高重建队列 理论基础 什么是贪心&#xff1a; 贪心的本质是选择每一…

[N-152]基于java贪吃蛇游戏5

开发工具eclipse,jdk1.8 文档截图&#xff1a; N-152基于java贪吃蛇游戏5

通过Python调用Excel VBA宏:扩展自动化能力的深度探索

目录 1. 引言 1.1 自动化办公的重要性 1.2 Python与Excel VBA的结合优势 2. Python调用Excel VBA宏的基本原理 2.1 Excel VBA宏的基本概念 2.2 Python调用VBA宏的方法 3. 安装与准备 3.1 安装pywin32库 3.2 配置Excel以允许宏运行 4. Python调用VBA宏的实例 4.1 导出…

基于单片机的室内装修环境检测系统设计

基于单片机的室内装修环境检测系统&#xff0c;该系统可实现苯、甲醛、氨气等有害气体的浓度检测和超标报警。该系统以STC89C52RC单片机为核心&#xff0c;由传感器检测室内装修环境中苯、甲醛、氨气的浓度以及温湿度&#xff0c;将以上测量出来的数据传送到PCF8591转换器&…

Java+vue的医药进出口交易系统(源码+数据库+文档)

外贸系统|医药进出口交易系统 目录 基于Javavue的服装定制系统 一、前言 二、系统设计 三、系统功能设计 仓储部门功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设…

15.2 JDBC数据库编程2

15.2.1 数据库访问步骤 使用JDBC API连接和访问数据库&#xff0c;一般分为以下5个步骤: (1) 加载驱动程序 (2) 建立连接对象 (3) 创建语句对象 (4) 获得SQL语句的执行结果 (5) 关闭建立的对象&#xff0c;释放资源 下面将详细描述这些步骤 15.2.2 加载驱动程序 要使…

开发中的网络问题逻辑推理分析

基于TCP/IP的逻辑推理&#xff0c;大部分软件从业人员都不是很懂&#xff0c;导致很多问题都被误认为诡异问题。有些人是惧怕TCP/IP网络书籍中的复杂知识内容&#xff0c;有的是被wireshark[1]显示的深红色内容所干扰。 经典案例1&#xff1a; 例如有一个DBA遇到了性能问题&a…

基于SpringBoot+Vue+MySQL的流浪猫狗宠物救助救援网站管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 在当今社会&#xff0c;随着宠物数量的激增及人们关爱动物意识的提升&#xff0c;流浪猫狗问题日益严峻。为解决这一问题&#xff0c;构建一套高效、便捷的流浪猫狗宠物救助救援网站管理系统显得尤为重要。本系统基于SpringBoot…

在VB.net中,TimeSpan有什么属性与方法

标题 在VB.net中&#xff0c;TimeSpan有什么属性与方法 正文 在 VB.NET 中&#xff0c;TimeSpan 结构表示时间间隔&#xff0c;即一段时间&#xff0c;而不表示特定的时间点。TimeSpan 提供了多种属性来获取时间间隔的各个组成部分&#xff0c;以及一些方法来操作这些时间间隔。…

Linux下载新版火狐浏览器,替换默认火狐浏览器,保留桌面任务栏图标快捷方式

Linux下载新版火狐浏览器&#xff0c;替换默认火狐浏览器&#xff0c;保留桌面任务栏图标快捷方式 方式一 替换默认程序入口 下载官方浏览器 火狐浏览器下载地址【官网】 &#xff08;搞清楚你的Linux系统是32位还是64位&#xff09; 解压下载的程序包&#xff0c;建议放到/o…

Leetcode面试经典150题-74.搜索二维矩阵

解法都在代码里&#xff0c;不懂就留言或者私信 二分查找&#xff0c;比较简单 class Solution {/**解题思路&#xff1a;每一行有序、每一列也有序&#xff0c;只是整体不是严格有序的&#xff0c;那我们需要找一个点&#xff0c;只能往两个方向走&#xff0c;往一个方向走是…

【docker】命令之镜像操作

一、前言 之前讲解了docker的安装&#xff0c;这里呢接着上面的内容来介绍docker中的相关命令的操作。这里我们更具一个案例就是启动一个nginx&#xff08;是一个在我们应用市场存在的一个软件包&#xff09;,并尝试对其进行修改&#xff0c;然后发布出去&#xff0c;让别人都能…

Guitar Pro 8.2中文解锁版下载及2024最新图文安装教程

Guitar Pro 8.2中文解锁版是一款深受广大音乐人和音乐爱好者喜爱的吉他打谱软件&#xff0c;帮助所有吉他爱好者学习、绘谱、创作&#xff0c;使用非常简单只需直接在五线谱或六线谱上编辑&#xff0c;即可轻松谱写自己的乐章。 Guitar Pro 8.2中文解锁版基本简介 Guitar Pro 8…

【python】python 安装和 pycharm 安装

1 python 安装 1.1 下载 下载地址&#xff1a;python 官网 1.2 安装 windows 安装为例。 双击.exe文件打开 安装界面 安装完成 1.3 检查安装是否成功 win/start 键r 键 运行窗口输入 cmd 回车 3 输入 python查看 显示版本信息&#xff0c;表示已经安装成功。 …

谷粒商城-P125【gulimall-search】:更改 elasticsearch 版本不生效

谷粒商城-P125【gulimall-search】&#xff1a;更改 elasticsearch 版本不生效 报错信息报错原因解决办法 报错信息 SpringBoot 项目的版本是 2.6.13&#xff0c;默认集成的 elasticsearch 的版本是 7.15.2&#xff0c;我们需要用的版本是 7.4.2。 SpringBoot 版本 SpringB…

pptpd配置文件/etc/pptpd.conf详解

正文共&#xff1a;1111 字 2 图&#xff0c;预估阅读时间&#xff1a;1 分钟 如果要在Linux系统配置PPTP&#xff08;Point-to-Point Tunneling Protocol&#xff0c;点到点隧道协议&#xff09;VPN&#xff0c;一般是使用pptpd软件。pptpd命令通常从配置文件/etc/pptpd.conf中…