C++构造函数与初始化全面指南:从基础到高级实践

C++构造函数与初始化全面指南:从基础到高级实践

1. 构造函数基础概念

构造函数是C++中一种特殊的成员函数,它在创建类对象时自动调用,用于初始化对象的数据成员。构造函数的核心特点包括:

  • 与类同名
  • 无返回类型(连void都没有)
  • 可以重载(一个类可以有多个构造函数)
  • 通常声明为public(除非有特殊需求)

1.1 默认构造函数

默认构造函数是不需要任何参数的构造函数。如果用户没有定义任何构造函数,编译器会自动生成一个默认构造函数。

class MyClass {
public:MyClass() { // 默认构造函数std::cout << "默认构造函数被调用" << std::endl;}
};// 使用
MyClass obj; // 调用默认构造函数

2. 构造函数初始化方式

2.1 赋值初始化(传统方式)

class Point {int x;int y;
public:Point(int a, int b) {x = a; // 赋值初始化y = b; // 赋值初始化}
};

2.2 初始化列表(推荐方式)

C++更推荐使用成员初始化列表,它在构造函数体执行前完成初始化,效率更高。对于const成员和引用成员,初始化列表是必须使用的唯一方式。

为什么const成员和引用成员必须使用初始化列表?

  1. const成员的不可变性

    • const成员一旦被初始化后,其值不可再修改
    • 如果允许在构造函数体内赋值,这实际上是对const成员的二次赋值(编译器会先默认初始化,再尝试赋值),违反了const语义
  2. 引用成员的本质

    • 引用是别名,必须在创建时绑定到一个已存在的对象
    • 引用一旦绑定后,无法再指向其他对象
    • 在构造函数体内"初始化"引用会导致引用在声明时未绑定(非法)
class ConstRefDemo {const int constValue;  // const成员int& refValue;         // 引用成员int normalValue;       // 普通成员
public:// const和引用成员必须使用初始化列表ConstRefDemo(int cv, int& rv) : constValue(cv), refValue(rv) {normalValue = 0;  // 普通成员可以在构造函数体内赋值}// 错误示例:/*ConstRefDemo(int cv, int& rv) {constValue = cv;  // 错误!const成员不能在构造函数体内赋值refValue = rv;    // 错误!引用必须在定义时绑定normalValue = 0;}*/
};

初始化列表的优势

  1. 对于const成员和引用成员,必须使用初始化列表
  2. 对于类类型成员,避免先默认构造再赋值
  3. 初始化顺序更明确(按成员声明顺序而非列表顺序)
  4. 效率更高,直接初始化而非先默认构造再赋值

3. 特殊构造函数

3.1 拷贝构造函数

拷贝构造函数用于用一个已存在的对象初始化新对象。

class MyString {char* data;
public:MyString(const MyString& other) { // 拷贝构造函数data = new char[strlen(other.data) + 1];strcpy(data, other.data);}
};

3.2 移动构造函数(C++11)

移动构造函数用于"窃取"临时对象的资源,避免不必要的拷贝。

class MyString {char* data;
public:MyString(MyString&& other) noexcept : data(other.data) { // 移动构造函数other.data = nullptr; // 使原对象处于有效但未定义状态}
};

4. 委托构造函数(C++11)

一个构造函数可以调用同类的另一个构造函数,避免代码重复。

class Rectangle {int width, height;
public:Rectangle() : Rectangle(1, 1) {} // 委托给下面的构造函数Rectangle(int w, int h) : width(w), height(h) {}
};

5. 初始化顺序问题

成员的初始化顺序取决于它们在类中的声明顺序,而非初始化列表中的顺序。这对const成员和引用成员尤其重要,因为它们必须正确初始化。

class Example {int a;const int b;  // const成员int& c;       // 引用成员
public:Example(int val) : c(a), b(val), a(10) {} // 实际初始化顺序:a(10) → b(val) → c(a)// 注意:虽然初始化列表中c写在前面,但实际按声明顺序初始化
};

6. 特殊成员的初始化

6.1 const成员初始化

const成员必须在初始化列表中初始化。

class ConstDemo {const int value;
public:ConstDemo(int v) : value(v) {} // 必须这样初始化// 错误示例:/*ConstDemo(int v) {value = v; // 错误!const成员不能在构造函数体内赋值}*/
};

6.2 引用成员初始化

引用成员也必须在初始化列表中初始化。

class RefDemo {int& ref;
public:RefDemo(int& r) : ref(r) {} // 必须这样初始化// 错误示例:/*RefDemo(int& r) {ref = r; // 错误!引用必须在定义时绑定}*/
};

7. 默认构造函数与=default

C++11允许显式要求编译器生成默认实现:

class DefaultDemo {const int value = 42; // C++11允许类内初始化const成员std::string& ref;     // 引用仍然必须在构造函数中初始化
public:DefaultDemo(std::string& s) : ref(s) {} // 引用必须在这里初始化DefaultDemo() = delete; // 禁止默认构造(因为引用必须初始化)
};

8. 构造函数实战建议

  1. 优先使用初始化列表:特别是对于类类型成员、const成员和引用成员
  2. 注意初始化顺序:按照成员声明顺序编写初始化列表
  3. const和引用成员的特殊处理:它们必须在初始化列表中初始化
  4. 合理使用explicit:防止单参数构造函数的隐式转换
  5. 考虑=default和=delete:明确表达设计意图
  6. 移动语义:对于资源管理类,实现移动构造和移动赋值

9. 完整示例代码(包含const和引用成员)

#include <iostream>
#include <string>class Student {
private:const std::string name;  // const成员int& ageRef;             // 引用成员const int id;            // const成员double scores[3];public:// 委托构造函数Student(std::string n, int& age, int i) : name(std::move(n)), ageRef(age), id(i) { // const和引用成员必须在此初始化for (double & score : scores) {score = 0.0;}}// 不能有默认构造函数,因为引用成员必须初始化// Student() = delete; void printInfo() const {std::cout << "Name: " << name << "\nID: " << id << "\nAge: " << ageRef << "\nScores: ";for (double score : scores) {std::cout << score << " ";}std::cout << std::endl;}// 不能修改const成员// void setName(const std::string& newName) { name = newName; } // 错误!// 可以通过引用成员修改原变量void incrementAge() { ageRef++; }
};int main() {int age = 20;Student s1("Alice", age, 1001);s1.printInfo();age = 21;  // 修改age会影响s1中的ageRefs1.incrementAge(); // 通过引用成员修改原变量s1.printInfo();return 0;
}

10. 关键总结

  1. const成员和引用成员必须在初始化列表中初始化,这是语言强制要求的
  2. 初始化列表提供了真正的初始化能力,而构造函数体内只是赋值
  3. 这种设计保证了对象构造时的确定性和安全性
  4. 现代C++实践中,应该优先使用初始化列表,不仅是为了满足语法要求,更是为了编写更高效、更安全的代码

理解并正确使用构造函数,特别是对const成员和引用成员的正确初始化,是C++面向对象编程的重要基础。这些规则反映了C++对确定性和效率的核心追求。

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

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

相关文章

大模型长对话中上下文无法承载全部历史,如何压缩或提取重点

在人工智能技术迅猛发展的今天,大模型已经渗透到我们生活的方方面面,尤其是自然语言处理领域,简直是掀起了一场革命。从智能客服到个人助手,从在线教育到心理咨询,大模型驱动的对话系统正在以一种前所未有的方式改变我们与机器的互动模式。特别是那些能够进行多轮对话、甚…

ubuntu20.04安装教程(图文详解)

Ubuntu 24.04 LTS&#xff0c;代号 Noble Numbat&#xff0c;于 2024 年 4 月 25 日发布&#xff0c;现在可以从 Ubuntu 官方网站及其镜像下载。此版本将在 2029 年 4 月之前接收为期五年的官方安全和维护更新。 关于 Ubuntu 24.04 LTS 的一些关键点&#xff1a; 发布日期&am…

数据结构之队列:原理与应用

一、基本原理 队列是一种特殊的线性表队列是一个有序表(可以用数组或链表实现)遵循“先来先服务”的原则&#xff0c;它只允许在表的前端&#xff08;队头&#xff09;进行删除操作&#xff0c;在表的后端&#xff08;队尾&#xff09;进行插入操作 (一) 核心操作 入队&…

Ubuntu 安装 Miniconda 及配置国内镜像源完整指南

目录 Miniconda 安装Conda 镜像源配置Pip 镜像源配置验证配置基本使用常见问题 1. Miniconda 安装 1.1 下载安装脚本 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh1.2 执行安装 bash Miniconda3-latest-Linux-x86_64.sh按回车查看许可协议…

PYTHON通过VOSK实现离线听写支持WINDOWSLinux_X86架构

在当今人工智能快速发展的时代&#xff0c;语音识别技术已经成为人机交互的重要方式之一。本文将介绍如何使用Python结合Vosk和PyAudio库实现一个离线语音识别系统&#xff0c;无需依赖网络连接即可完成语音转文字的功能。 技术栈概述 1. Vosk语音识别引擎 Vosk是一个开源的…

【Java进阶】图像处理:从基础概念掌握实际操作

一、核心概念&#xff1a;BufferedImage - 图像的画布与数据载体 在Java图像处理的世界里&#xff0c;BufferedImage是当之无愧的核心。你可以将它想象成一块内存中的画布&#xff0c;所有的像素数据、颜色模型以及图像的宽度、高度等信息都存储在其中。 BufferedImage继承自…

数据治理系统是什么?数据治理工具有什么用?

目录 一、数据治理系统是什么&#xff1f; 二、数据治理系统的重要性 1. 保障数据质量 2. 确保数据安全 3. 促进数据共享与协作 三、常见的数据治理工具及其特点 1. 数据质量管理工具 2. 数据集成工具 3. 元数据管理工具 四、数据治理工具有哪些作用&#xff1f; 1.…

消息队列-kafka为例

目录 消息队列应用场景和基础知识MQ常见的应用场景MQ消息队列的两种消息模式如何保证消息队列的高可用&#xff1f;如何保证消息不丢失&#xff1f;如何保证消息不被重复消费&#xff1f;如何保证消息消费的幂等性&#xff1f;重复消费的原因解决方案 如何保证消息被消费的顺序…

C++17常量

nullptr nullptr出现的目的是为了替代NULL。在某种意义上来说&#xff0c;传统会把NULL,0视为同一种东 西&#xff0c;这取决于编译器如何定义NULL&#xff0c;有些编译器会将定义为((void*)0)&#xff0c;有些则会直接将其定义 为0。 C不允许直接将void*隐式转换到其他类型。…

计算机网络学习(九)——CDN

一、CDN CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;是一种通过分布式节点将内容更高效地传递给用户的技术架构&#xff0c;广泛应用于加速网站、视频、下载、直播等业务。 CDN 是把内容放到离用户最近的“高速公路入口”&#xff0c;提升访…

Elasticsearch的写入流程介绍

Elasticsearch 的写入流程是一个涉及 分布式协调、分片路由、数据同步和副本更新 的复杂过程,其设计目标是确保数据一致性、可靠性和高性能。以下是写入流程的详细解析: 一、写入流程总览 二、详细步骤解析 1. 客户端请求路由 请求入口:客户端(如 Java 客户端、REST API)…

vue为什么点击两遍才把参数传递过去

先说一下场景&#xff0c;就是我把云服务器这个下拉选择框分别初始化之后&#xff0c;然后点击新建权限然后就打开了右侧的抽屉式的对话框&#xff0c;页面上那个文字信息是传递过来了。那个是正确的&#xff0c;但是我请求接口的时候&#xff0c;发现请求的接口的参数总是要慢…

java代码性能优化

刷题过程中遇到的一些时间复杂度相同&#xff0c;但是常数因子的差距导致的性能差距&#xff0c;遇到持续更新 枚举 VS contains 例如&#xff1a;判断一个字符是不是元音 法一&#xff1a; if(ch a || ch e || ch i || ch o || ch u) 法二&#xff1a; Set<Charact…

OpenGL Chan视频学习-9 Index Buffers inOpenGL

bilibili视频链接&#xff1a; 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 函数网站&#xff1a; docs.gl 说明&#xff1a; 1.之后就不再单独整理网站具体函数了&#xff0c;网站直接翻译会…

基于微服务架构的社交学习平台WEB系统的设计与实现

设计&#xff08;论文&#xff09;题目 基于微服务架构的社交学习平台WEB系统的设计与实现 摘 要 社交学习平台 web 系统要为学习者打造一个开放、互动且社交性强的在线教育环境&#xff0c;打算采用微服务架构来设计并实现一个社交学习平台 web 系统&#xff0c;以此适应学…

生成式人工智能:重构软件开发的范式革命与未来生态

引言 生成式人工智能&#xff08;GenAI&#xff09;正以颠覆性力量重塑软件开发的底层逻辑。从代码生成到业务逻辑设计&#xff0c;从数据分析到用户交互&#xff0c;GenAI通过其强大的推理能力与场景适应性&#xff0c;将传统开发流程的“复杂工程”转化为“敏捷实验”&#…

C++17原生测试编程实践:现代特性与分支覆盖指南

C17原生测试编程实践&#xff1a;现代特性与分支覆盖指南 概述 本文将深入探讨如何利用C17新特性进行原生测试代码编写&#xff0c;实现完全分支覆盖。我们将不依赖任何外部测试框架&#xff0c;而是使用C17标准库构建完整的测试解决方案。 一、C17测试核心工具集 1. 断言工…

RK3568项目(四)--uboot启动流程之启动模式选择

目录 一、引言 二、芯片初始化 ------>2.1、io_domain ------>2.2、调频调压 ------>2.3、控制台初始化 三、平台初始化 ------>3.1、设置mac地址 ------------>3.1.1、vendor分区 ------>3.2、设置serialno ------>3.3、设置下载模式 -------…

Kotlin JVM 注解详解

前言 Kotlin 作为一门现代 JVM 语言&#xff0c;提供了出色的 Java 互操作性。为了更好地支持与 Java 代码的交互&#xff0c;Kotlin 提供了一系列 JVM 相关注解。这些注解不仅能帮助我们控制 Kotlin 代码编译成 Java 字节码的行为&#xff0c;还能让我们的 Kotlin 代码更好地…

Starrocks 物化视图的实现以及在刷新期间能否读数据

背景 本司在用Starrocks做一些业务上的分析的时候&#xff0c;用到了物化视图&#xff0c;并且在高QPS的情况下&#xff0c;RT也没有很大的波动&#xff0c;所以在此研究一下Starrock的实现&#xff0c;以及在刷新的时候是不是原子性的 本文基于Starrocks 3.3.5 结论 Starro…