C++核心知识点全解析(一)

C++核心知识点全解析(一)

1. C++中值传递和引用传递的区别?

1) 值传递: 在函数调用时,会触发一次参数的拷贝动作,所以对参数的修改不会影响原始的值。如果是较大的对象,复制整个对象,效率较低。

2) 引用传递: 函数调用时,函数接收的就是参数的引用,不会触发参数的拷贝动作,效率较高,但对参数的修改会直接作用于原始的值。


2. C 和 C++ 的区别

可以考虑从以下几个方面回答:

1) 面向对象还是面向过程:

  • C语言是一门面向过程的语言,侧重于通过过程(函数)来解决问题。
  • C++是一门多范式语言,主要支持面向对象,侧重于使用类和对象来组织代码。

2) 继承:

  • C++支持继承,允许一个子类继承一个或多个父类,达到代码复用的目的。
  • C语言中没有继承的概念。

3) 函数重载:

  • C++支持函数通过参数类型和参数个数的重载。
  • C语言不支持重载,函数名必须唯一才行。

4) 模板:

  • C++支持模板,支持静态和动态形式的多态。
  • C语言对此都不支持。

5) 内存管理:

  • C++使用new 和delete操作符来管理内存,也支持使用智能指针来动态管理内存。
  • C语言需要使用malloc和free来申请和释放内存。

6) 标准库:

  • C++的STL标准库能力比C语言丰富的多,比如vector、string、list、map等等,还有很多算法相关的能力,这些C语言都没有。

3. 什么是C++的左值和右值?有什么区别?

什么是左值? 什么是右值?

  • 左值:可以出现在赋值运算符的左边,并且可以被取地址,通常是有名字的变量
  • 右值:不能出现在赋值运算符的左边,不可以被取地址,表示一个具体的数据值,通常是常量、临时变量

一般可以从两个方向区分左值和右值。

方向1:

  • 左值:可以放到等号左边的东西叫左值。
  • 右值:不可以放到等号左边的东西就叫右值。

方向2:

  • 左值:可以取地址并且有名字的东西就是左值。
  • 右值:不能取地址的没有名字的东西就是右值。

示例:

int a = b + c;

a是左值,有变量名,可以取地址,也可以放到等号左边,表达式 b+c 的返回值是右值,没有名字且不能取地址,&(b+c)不能通过编译,而且也不能放到等号左边。

int a = 4; // a是左值,4作为普通字面量是右值

4. 什么是 C++ 的移动语义和完美转发

回答重点

移动语义和完美转发都是C++11引入的新特性。

移动语义

一种优化资源管理的机制。常规的资源管理是拷贝别人的资源。而移动语义是转移所有权,转移了资源而不是拷贝资源,性能会更好。

移动语义通常用于那些比较大的对象,搭配移动构造函数或移动赋值运算符来使用。

class A { public: A(int size) : size_(size) { data_ = new int[size]; } A(){} A(const A& a) { size_ = a.size_; data_ = new int[size_]; cout << "copy " << endl; } A(A&& a) { this->data_ = a.data_; a.data_ = nullptr; cout << "move " << endl; } ~A() { if (data_ != nullptr) { delete[] data_; } } int *data_; int size_; }; int main() { A a(10); A b = a; A c = std::move(a); // 调用移动构造函数 return 0; }

如果不使用std::move,会有很大的拷贝代价,使用移动语义可以避免很多无用的拷贝,提供程序性能,C++所有的STL都实现了移动语义,方便我们使用。例如:

std::vector<string> vecs; ... std::vector<string> vecm = std::move(vecs); // 免去很多拷贝

完美转发

完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。

那如何实现完美转发呢,答案是使用std..forward,可参考以下代码:

void PrintV(int &t) { cout << "lvalue" << endl; } void PrintV(int &&t) { cout << "rvalue" << endl; } template<typename T> void Test(T &&t) { PrintV(t); PrintV(std::forward<T>(t)); PrintV(std::move(t)); } int main() { Test(1); // lvalue rvalue rvalue int a = 1; Test(a); // lvalue lvalue rvalue Test(std::forward<int>(a)); // lvalue rvalue rvalue Test(std::forward<int&>(a)); // lvalue lvalue rvalue Test(std::forward<int&&>(a)); // lvalue rvalue rvalue return 0; }

分析:

  • Test (1): 1 是右值,模板中T&&t这种为万能引用,右值1传到Test函数中变成了右值引用,但是调用PrintV)时候,t变成了左值,因为它变成了一个拥有名字的变量,所以打印Ivalue而PrintV(std:.forward<T>(t))时候,会进行完美转发,按照原来的类型转发,所以打印rvalue,PrintV(std::move(t))毫无疑问会打印rvalue。
  • Test(a):a是左值,模板中T&&这种为万能引用,左值a传到Test 函数中变成了左值引用,所以有代码中打印。
  • Test(std::forward<T>(a)):转发为左值还是右值,依赖于T,T是左值那就转发为左值,T是右值那就转发为右值。

5. 什么是 C++ 的列表初初始化

回答重点

C++11中引入了列表初始化,它的语法比较简单,就是可以使用花括号0来初始化变量或对象。列表初始化可以应用于内置类型、用户自定义类型(类、结构体等)以及其它容器等。它有以下几点好处:

  • 方便,基本上可以替代普通括号初始化
  • 可以使用初始化列表接受任意长度
  • 可以防止类型窄化,避免精度丢失的隐式类型转换

下面深入看下列表初始化的几个用法:

1. 基础数据类型、

int a{10}; // 列表初始化 int a = {19}; // 列表初始化(也可以不使用等号)

2. 初始化数组

int arr[3] = {1, 2, 3}; // 使用花括号初始化数组

3. 类对象初始化,构造函数需要支持列表初始化

class Point { public: int x, y; Point(int a, int b) : x{a}, y{b} {} }; Point p{1, 2}; // 使用花括号初始化对象

4. 容器初始化

std::vector<int> vec = {1, 2, 3, 4};

5. 防止类型窄化

int x{3.14}; // error,float转int会触发类型窄化

6. 聚合类型的列表初始化

聚合类型是指没有用户定义的构造函数、没有私有或受保护的非静态数据成员、没有基类以及没有虚函数的类、结构体或联合体。对于聚合类型,列表初始化会直接按顺序初始化其成员。

struct Aggregate { int a; double b; }; Aggregate agg{1, 2.3}; // 初始化a为1,b为2.3

扩展知识

什么是类型窄化?

  • 从浮点类型到整数类型的转换
  • 从long double到double或float的转换,以及从double 到float的转换,除非源是常量表达式且不发生溢出
  • 从整数或无作用域枚举类型到不能表示原类型所有值的整数类型的转换,除非源是其值能完全存储于目标类型的常量表达式

示例代码:

int main() { int a = 1.2; // ok int b = {1.2}; // error float c = 1e70; // ok float d = {1e70}; // error float e = (unsigned long long)-1; // ok float f = {(unsigned long long)-1}; // error float g = (unsigned long long)1; // ok float h = {(unsigned long long)1}; // ok const int i = 1000; const int j = 2; char k = i; // ok char l = {i}; // error char m = j; // ok char m = {j}; // ok,因为是const类型,这里如果去掉const属性,也会报错 }

std::initializer list

我们平时开发使用STL过程中可能发现它的初始化列表可以是任意长度,有没有想过它是怎么实现的

呢?

答案是 std:initializer_list,看这段代码:

struct CustomVec { std::vector<int> data; CustomVec(std::initializer_list<int> list) { for (auto iter = list.begin(); iter != list.end(); ++iter) { data.push_back(*iter); } } };

std:initializerlist<T>,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可

以转换为T。


6. C++ 中 move 有什么作用?它的原理是什么?

回答重点

move是C++11引入的一个新特性,用来实现移动语义。它的主要作用是将对象的资源从一个对象转移到另一个对象,而无需进行深拷贝,减少了资源内存的分配,可提高性能。它的原理很简单,我们直接看它的源码实现:

// move template <class T> LIBC_INLINE constexpr cpp::remove_reference_t<T> &&move(T &&t) { return static_cast<typename cpp::remove_reference_t<T> &&>(t); }

从源码中你可以看到,std::move的作用只有一个,无论输入参数是左值还是右值,都强制转成右值。


7. 介绍 C++ 中三种智能指针的使用场景?

回答重点

C++中的智能指针主要用于管理动态分配的内存,避免内存泄漏。

C++11标准引入了三种主要的智能指针:std::unique_ptr std::shared_ptr std::weak_ptr

1. std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,意味着同一时间内只能有一个unique_ptr指向一个特定的对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。

使用场景:

  • 当你需要确保一个对象只被一个指针所拥有时。
  • 当你需要自动管理资源,如文件句柄或互斥锁时。

示例代码:

#include <iostream> #include <memory> class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; } void test() { std::cout << "Test::test()\n"; } }; int main() { std::unique_ptr<Test> ptr(new Test()); ptr->test(); // 当ptr离开作用域时,它指向的对象会被自动销毁 return 0; }

2. std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象。内部使用引用计数来确保只有当最后一个指向对象的shared_ptr被销毁时,对象才会被销毁。

使用场景:

  • 当你需要在多个所有者之间共享对象时。
  • 当你需要通过复制构造函数或赋值操作符来复制智能指针时。

示例代码:

#include <iostream> #include <memory> class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; } void test() { std::cout << "Test::test()\n"; } }; int main() { std::shared_ptr<Test> ptr1(new Test()); std::shared_ptr<Test> ptr2 = ptr1; ptr1->test(); // 当ptr1和ptr2离开作用域时,它们指向的对象会被自动销毁 return 0; }

3. std::weak_ptr

std::weak_ptr是一种不拥有对象所有权的智能指针,它指向一个由std::shared_ptr管理的对象。weak_ptr用于解决 shared_ptr之间的循环引用问题。

使用场景:

  • 当你需要访问但不拥有由shared_ptr管理的对象时。
  • 当你需要解决shared_ptr之间的循环引用问题时。
  • 注意weak_ptr肯定要和 shared_ptr搭配使用。

示例代码:

#include <iostream> #include <memory> class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; } void test() { std::cout << "Test::test()\n"; } }; int main() { std::shared_ptr<Test> sharedPtr(new Test()); std::weak_ptr<Test> weakPtr = sharedPtr; if (auto lockedSharedPtr = weakPtr.lock()) { lockedSharedPtr->test(); } // 当sharedPtr离开作用域时,它指向的对象会被自动销毁 return 0; }


Read more

Python NumPy入门指南:数据处理科学计算的瑞士军刀

Python NumPy入门指南:数据处理科学计算的瑞士军刀

作者:唐叔在学习 专栏:唐叔学python 标签:Python NumPy、数据分析、科学计算、机器学习基础、数组操作、Python数据处理、人工智能基础、Python编程 摘要 NumPy是Python科学计算的基础库,提供了高性能的多维数组对象和工具。本文唐叔将带你从零开始了解NumPy的核心概念、常用操作和实际应用场景,助你在数据分析、机器学习等领域快速上手。无论你是Python初学者还是想提升数据处理能力,这篇文章都将成为你的实用指南。 文章目录 * 摘要 * 一、NumPy是什么?为什么它如此重要? * 二、NumPy安装与基础使用 * 2.1 安装NumPy * 2.2 导入NumPy * 2.3 创建第一个NumPy数组 * 三、NumPy核心功能详解 * 3.1 数组属性 * 3.2 创建特殊数组 * 3.3 数组索引与切片

By Ne0inhk
基于Python的医院运营数据可视化平台:设计、实现与应用(上)

基于Python的医院运营数据可视化平台:设计、实现与应用(上)

一、引言 1.1 研究目的与意义 在信息技术日新月异的当下,医疗行业正处于深刻的变革之中,逐渐朝着信息化、智能化方向大步迈进。医院每天都会产生海量的数据,涵盖患者信息、诊疗记录、药品库存、设备使用状况等多个关键领域。这些数据宛如一座蕴藏丰富的宝藏,若能加以科学有效的管理与分析,将为医院的运营管理提供强大的支持,成为提升医疗服务质量、优化资源配置的关键要素。然而,传统的数据处理方式,如过度依赖 Excel 表格和简单的统计工具,在面对如此庞大且复杂的数据时,显得力不从心,效率极为低下,难以满足医院日益增长的复杂分析需求。 基于此,本研究旨在构建一个基于 Python 的医院运营数据可视化平台,将 Python 语言的强大功能与数据可视化技术深度融合,为医院运营管理开辟全新的路径。Python 作为一种高级编程语言,以其简洁性、易读性以及丰富多样的扩展库,如 Pandas、Matplotlib 等,在数据处理和可视化领域展现出卓越的优势,能够高效地实现数据清洗、分析以及可视化图表的生成。 本研究具有重要的现实意义。对于医院管理而言,该平台能够极大地提高运营管理的效率。

By Ne0inhk
Python中的“==“与“is“:深入解析与Vibe Coding时代的优化实践

Python中的“==“与“is“:深入解析与Vibe Coding时代的优化实践

🌟 Python中的"=="与"is":深入解析与Vibe Coding时代的优化实践 * 1. 🧐 `==`与`is`的本质区别 * 2. 🕵️‍♂️ `is`判断对象身份 - 数组与常量池案例 * 案例1:列表对象的身份 * 案例2:小整数常量池 * 案例3:字符串驻留 * 3. 🔍 `==`与`__eq__`魔法函数 * 4. 🔎 类型判断的正确姿势:使用`is` * 5. 🚀 Vibe Coding时代的提示词优化 * 场景1:解释概念 * 场景2:代码生成 * 场景3:调试帮助 * 📊 对比总结表 * 💡 实际应用建议 * 🌈 结语 在Python的奇妙世界中,==和is这两个看似简单的操作符常常让初学者感到困惑。它们如同双胞胎,外表相似却性格迥异。本文将带你深入探索它们的区别,并通过生动的案例和图表展示它们的应用场景,

By Ne0inhk
微分的本质:从“变化率”到“线性映射”的飞跃 —— 可视化 Python 教程

微分的本质:从“变化率”到“线性映射”的飞跃 —— 可视化 Python 教程

引言 微积分是科学的语言,而微分是其灵魂。从一维导数到流形上的切映射,微分的本质始终是一个线性映射。本文将从这一核心观点出发,系统梳理微积分中一系列重要概念:导数、微分、雅可比矩阵、方向导数、梯度、链式法则、Hessian、切映射、拉回等,揭示它们背后的统一结构。更重要的是,我们将用 Python 代码可视化这些概念,让你直观地看到微分如何“线性化”非线性函数。 本文所有代码均使用 Python 3 + NumPy + Matplotlib 编写,你可以复制到自己的环境中运行,观察图形变化。 1. 一维导数的重新解读——从“数”到“线性映射” 1.1 传统定义的局限 对于一元函数 (f:\mathbb{R}\to\mathbb{R}),导数定义为 [ f’

By Ne0inhk