当前位置: 首页 > news >正文

c++面试:类定义为什么可以放到头文件中

这个问题是刚了解预编译的时候产生的疑惑。

  1. 声明是指向编译器告知某个变量、函数或类的存在及其类型,但并不分配实际的存储空间。声明的主要目的是让编译器知道如何解析程序中的符号引用。
  2. 定义不仅告诉编译器实体的存在,还会为该实体分配存储空间(对于变量)或者提供具体的实现(对于函数)。定义只能出现一次,以避免重复定义错误。
  3. 类定义描述了一个类型(或称“蓝图”),它定义了该类型的成员变量、成员函数以及其他特性。类定义本身并不分配内存,它只是提供了创建对象的模板。【很明显与函数定义和变量定义很不相同】对象定义则是基于某个类创建的具体实例,它会在内存中分配空间。
  4. 编译器将类中声明的函数视为内联函数。当你在类定义内部定义成员函数时(即直接在类体内部提供函数体),这些函数默认为是inline的,如果使用 inline ,则意味着编译器会在调用此函数的地方把函数的目标代码直接插入,而不是放置一个真正的函数调用,实际作用就是这个函数事实上已经不再存在,而是像宏一样被就地展开了,因此不存在重复定义的问题。

总而言之,类定义其实只是描述了一个类型,并不会分配具体的空间,在类内实现的函数编译器认为是inline,调用时会展开成具体的代码,所以不存在重复定义。因此类定义可以放到头文件中被不同的源文件引用也不会产生重复定义的问题。

两次导入头文件

预编译指令是以井号(#)开头的指令,它们在编译器进行编译之前执行。预编译指令不是C++语句,因此它们不以分号(;)结尾。预编译指令包括但不限于以下几种:

  • #include:用于包含头文件,将头文件的内容插入到源文件中。系统提供的头文件使用尖括号<>括起来,而用户自定义的头文件使用双引号""括起来。
  • #define:用于定义宏,宏可以是简单的符号常量或带参数的宏。宏定义在预处理阶段会被替换成相应的文本。
  • #if、#ifdef、#ifndef、#else、#elif、#endif:这些条件编译指令用于根据条件判断是否编译某部分代码。
// add.h
int add(int, int);
class Person{
public:int age;
}// add.cpp
#include "add.h"
#include "add.h"
int add(int a, int b) {return a + b;
}

我们使用g++ -E add.cpp -o add.i的命令可以得到预编译后的文件

// 遇见#include "add.h",将add.h中的内容展开// 第一次执行#include "add.h"展开成如下内容
int add(int, int);
class Person{
public:int age;
}// 第二次执行#include "add.h"展开成如下内容
int add(int, int);
class Person{
public:int age;
}int add(int, int);
class Person{
public:int age;
}int add(int a, int b) {return a + b;
}

可以看到Person这个类被定义了两次,很明显是不合理的,我们可以通过#ifndef来解决。

// add.h
#ifndef __ADD_H // 如果没有定义过__ADD_H这个宏
#define __ADD_H // 那么就定义这个宏int add(int, int);
class Person{
public:int age;
}#endif

第一次引入add.h的时候还没有定义过__ADD_H,那么就定义这个宏以及头文件里的内容,第二次引入add.h的时候,已经定义过__ADD_H了,那么就不将内容替换进去。对于cpp而言,还可以使用#pragma once

#pragma once
int add(int, int);
class Person{
public:int age;
}

定义和声明

在C++中,定义和声明是两个不同的概念,它们各自有着明确的用途和含义。理解这两者的区别对于编写正确且高效的C++代码至关重要。

  • 声明是指向编译器告知某个变量、函数或类的存在及其类型,但并不分配实际的存储空间。声明的主要目的是让编译器知道如何解析程序中的符号引用。例如:
extern int a; // 声明一个名为a的整型变量,但不分配内存
int add(int x, int y); // 声明一个名为add的函数,但不提供实现
class MyClass; // 前置声明,仅声明了MyClass的存在

声明允许你在代码的一个部分提到某个实体,并在另一个部分提供其实现或定义。这对于模块化编程特别有用,因为它使得你可以将接口与实现分离。

  • 定义不仅告诉编译器实体的存在,还会为该实体分配存储空间(对于变量)或者提供具体的实现(对于函数)。定义只能出现一次,以避免重复定义错误。
int a = 10; // 定义了一个名为a的整型变量,并初始化为10int add(int x, int y) {return x + y;
} // 定义并实现了add函数void MyClass::myFunction() {// 函数实现
}

一个实体只能有一个定义(遵循“一个定义原则”,ODR),但在多个源文件中可以进行声明。并且定义会导致为变量分配存储空间或为函数生成机器码,而声明不会。

类为什么可以放到头文件中

类或者结构体只是描述了对数据的组织方式,并不需要申请空间,所以是声明 ,因此可以在多个文件中引用。

  • 类定义:类定义描述了一个类型(或称“蓝图”),它定义了该类型的成员变量、成员函数以及其他特性。类定义本身并不分配内存,它只是提供了创建对象的模板。
  • 对象定义:对象定义则是基于某个类创建的具体实例,它会在内存中分配空间。

通常我们会将类定义在头文件中,在.cpp文件中实现方法,cpp中的类函数才会生成字节码,才是真实的类方法实现(定义),而.h中的类函数则相当于只是一个【接口声明】,不会生成真正的代码。

编译器将类中声明的函数视为内联函数。因此,当您调用这个类函数时,有两个选项:

  1. 默认视为内联:当你在类定义内部定义成员函数时(即直接在类体内部提供函数体),这些函数默认为是inline的,如果使用 inline ,则意味着编译器会在调用此函数的地方把函数的目标代码直接插入,而不是放置一个真正的函数调用,实际作用就是这个函数事实上已经不再存在,而是像宏一样被就地展开了,因此不存在重复定义的问题。
  2. 弱符号:在某些情况下,如果编译器决定不对某个inline函数进行内联,它会将该函数作为“弱符号”处理。这意味着,尽管该函数在多个翻译单元中有定义,但在链接阶段只会保留一个副本。链接器会选择其中一个定义作为最终使用的版本,而忽略其他重复的定义。这确保了即使有多个定义存在,也不会导致链接错误。

假设我们有一个简单的类定义如下:

// MyClass.h
#pragma onceclass MyClass {
public:void inlineFunction() { std::cout << "Inline function called" << std::endl; } // 内联函数void nonInlineFunction(); // 声明非内联函数
};

然后在对应的.cpp文件中定义非内联成员函数:

// MyClass.cpp
#include "MyClass.h"
#include <iostream>void MyClass::nonInlineFunction() { // 定义非内联函数std::cout << "Non-inline function called" << std::endl;
}

在另一个源文件中使用这个类:

// main.cpp
#include "MyClass.h"int main() {MyClass obj;obj.inlineFunction();obj.nonInlineFunction();return 0;
}

在这个例子中:

  • inlineFunction 是在类定义内部定义的,因此它被视为inline函数。即使在多个源文件中包含MyClass.h,也不会违反ODR,因为这些定义是相同的。
  • nonInlineFunction 的定义只存在于MyClass.cpp中,符合ODR的要求,因为它在整个程序中只有一个定义。

相关文章:

c++面试:类定义为什么可以放到头文件中

这个问题是刚了解预编译的时候产生的疑惑。 声明是指向编译器告知某个变量、函数或类的存在及其类型&#xff0c;但并不分配实际的存储空间。声明的主要目的是让编译器知道如何解析程序中的符号引用。定义不仅告诉编译器实体的存在&#xff0c;还会为该实体分配存储空间&#…...

PythonFlask框架

文章目录 处理 Get 请求处理 POST 请求应用 app.route(/tpost, methods[POST]) def testp():json_data request.get_json()if json_data:username json_data.get(username)age json_data.get(age)return jsonify({username: username测试,age: age})从 flask 中导入了 Flask…...

Kotlin开发(六):Kotlin 数据类,密封类与枚举类

引言 想象一下&#xff0c;你是个 Kotlin 开发者&#xff0c;敲着代码忽然发现业务代码中需要一堆冗长的 POJO 类来传递数据。烦得很&#xff1f;别急&#xff0c;Kotlin 贴心的 数据类 能帮你自动生成 equals、hashCode&#xff0c;直接省时省力&#xff01;再想想需要多种状…...

冬天适合养什么鱼?

各位鱼友们&#xff0c;冬天来了&#xff0c;是不是还在为养什么鱼而烦恼&#xff1f;别担心&#xff0c;今天就来给大家好好推荐一些适合冬天养的鱼&#xff0c;让你的水族箱在寒冷的冬天也能生机勃勃&#xff01; 一、金鱼&#xff1a;冬日里的“小暖男” 金鱼绝对是冬季养鱼…...

【C++动态规划 状态压缩】2597. 美丽子集的数目|2033

本文涉及知识点 C动态规划 LeetCode2597. 美丽子集的数目 给你一个由正整数组成的数组 nums 和一个 正 整数 k 。 如果 nums 的子集中&#xff0c;任意两个整数的绝对差均不等于 k &#xff0c;则认为该子数组是一个 美丽 子集。 返回数组 nums 中 非空 且 美丽 的子集数目。…...

前端-Rollup

Rollup 是一个用于 JavaScript 的模块打包工具&#xff0c;它将小的代码片段编译成更大、更复杂的代码&#xff0c;例如库或应用程序。它使用 JavaScript 的 ES6 版本中包含的新标准化代码模块格式&#xff0c;而不是以前的 CommonJS 和 AMD 等特殊解决方案。ES 模块允许你自由…...

20【变量的深度理解】

一说起变量&#xff0c;懂点编程的都知道&#xff0c;但是在理解上可能还不够深 变量就是存储空间&#xff0c;电脑上的存储空间有永久&#xff08;硬盘&#xff09;和临时&#xff08;内存条&#xff09;两种&#xff0c;永久数据重启电脑后依旧存在&#xff0c;临时数据只…...

大数据学习之Kafka消息队列、Spark分布式计算框架一

Kafka消息队列 章节一.kafka入门 4.kafka入门_消息队列两种模式 5.kafka入门_架构相关名词 Kafka 入门 _ 架构相关名词 事件 记录了世界或您的业务中 “ 发生了某事 ” 的事实。在文档中 也称为记录或消息。当您向 Kafka 读取或写入数据时&#xff0c;您以事件的 形式执行…...

基于Flask的旅游系统的设计与实现

【Flask】基于Flask的旅游系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统采用Python作为后端开发语言&#xff0c;结合前端Bootstrap框架&#xff0c;为用户提供了丰富…...

“AI视频智能分析系统:让每一帧视频都充满智慧

嘿&#xff0c;大家好&#xff01;今天咱们来聊聊一个特别厉害的东西——AI视频智能分析系统。想象一下&#xff0c;如果你有一个超级聪明的“视频助手”&#xff0c;它不仅能自动识别视频中的各种元素&#xff0c;还能根据内容生成详细的分析报告&#xff0c;是不是感觉特别酷…...

算法随笔_31:移动零

上一篇:算法随笔_30: 去除重复字母-CSDN博客 题目描述如下: 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,…...

改进候鸟优化算法之二:基于混沌映射的候鸟优化算法(MBO-CM)

基于混沌映射的候鸟优化算法(Migrating Birds Optimization based on Chaotic Mapping,MBO-CM)是一种结合了混沌映射与候鸟优化算法(Migrating Birds Optimization,MBO)的优化方法。 一、候鸟优化算法(MBO)简介 候鸟优化算法是一种自然启发的元启发式算法,由Duman等人…...

在Docker 容器中安装 Oracle 19c

在 Docker 容器中安装 Oracle 19c 是可行的&#xff0c;但它相较于其他数据库&#xff08;如 MySQL、PostgreSQL 等&#xff09;会复杂一些&#xff0c;因为 Oracle 数据库有一些特定的要求&#xff0c;如操作系统和库的依赖&#xff0c;以及许可证问题。 不过&#xff0c;Ora…...

使用Avalonia UI实现DataGrid

1.Avalonia中的DataGrid的使用 DataGrid 是客户端 UI 中一个非常重要的控件。在 Avalonia 中&#xff0c;DataGrid 是一个独立的包 Avalonia.Controls.DataGrid&#xff0c;因此需要单独通过 NuGet 安装。接下来&#xff0c;将介绍如何安装和使用 DataGrid 控件。 2.安装 Dat…...

MySQL中的读锁与写锁:概念与作用深度剖析

MySQL中的读锁与写锁&#xff1a;概念与作用深度剖析 在MySQL数据库的并发控制机制中&#xff0c;读锁和写锁起着至关重要的作用。它们是确保数据在多用户环境下能够正确、安全地被访问和修改的关键工具。 一、读锁&#xff08;共享锁&#xff09;概念 读锁&#xff0c;也称为…...

Dest1ny漏洞库:用友 U8 Cloud ReleaseRepMngAction SQL 注入漏洞(CNVD-2024-33023)

大家好&#xff0c;今天是Dest1ny漏洞库的专题&#xff01;&#xff01; 会时不时发送新的漏洞资讯&#xff01;&#xff01; 大家多多关注&#xff0c;多多点赞&#xff01;&#xff01;&#xff01; 0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚…...

python学opencv|读取图像(四十九)原理探究:使用cv2.bitwise()系列函数实现图像按位运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位异或运算&#xff1a; 两个等长度二进制数上下对齐&#xff0c;相…...

【面试】【编程范式总结】面向对象编程(OOP)、函数式编程(FP)和响应式编程(RP)

一、编程范式总结 编程范式是指开发软件时采用的一种方法论或思维方式&#xff0c;主要包括面向对象编程&#xff08;OOP&#xff09;、**函数式编程&#xff08;FP&#xff09;和响应式编程&#xff08;RP&#xff09;**等。这些范式的不同特性和适用场景&#xff0c;帮助开发…...

创建要素图层和表视图

操作方法: 下面按照步骤学习如何使用Make Feature Layer和Make Table View工具 1.在arcmap中打开活动地图文档 2.导入arcpy模块 3.设置工作空间 arcpy.env.workspace "<>" 4.使用try语句,使用Make Feature Layer工具创建内存副本 try:flayer arcpy.Ma…...

51单片机入门_01_单片机(MCU)概述(使用STC89C52芯片;使用到的硬件及课程安排)

文章目录 1. 什么是单片机1.1 微型计算机的组成1.2 微型计算机的应用形态1.3 单板微型计算机1.4 单片机(MCU)1.4.1 单片机内部结构1.4.2 单片机应用系统的组成 1.5 80C51单片机系列1.5.1 STC公司的51单片机1.5.1 STC公司单片机的命名规则 2. 单片机的特点及应用领域2.1 单片机的…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...