知乎C++问题整理

本文探讨如何在C++中通过合理选择STL容器,减少内存操作,特别是避免不必要的复制操作,从而优化性能。此外,文章还详细解释了数组与指针的区别,以及在使用C/C++时关于0与NULL的细微差别。最后,提供了计算带指针的结构体大小的方法,并解释了为何添加指针会导致结构体大小变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何兼顾性能,合理选择C++STL容器?

ANSER:

首先要搞清楚,如果STL有性能问题,那么问题出在哪里?

STL可以简单地认为就是算法+数据结构,所有容器的算法选择和实现都是经过精心设计和严格测试的,几个主流STL实现都不会有大问题。

性能问题通常都出在内存数据操作上,内存操作有三种,内存读取、内存复制和内存分配

所以选择合适容器的依据就是要尽量减少内存操作尤其是复制操作,比如频繁中间插入删除就不要选Vector,频繁随机访问就不要选list

除了选错容器这种低级错误外,性能瓶颈基本都是出在容器内的对象身上,解决方案:

  1. 容器内放对象指针,而不是实例,对象生命周期自己管理,只有在真的有性能问题时才考虑这样做,因为内存泄漏的风险还是挺高的。
  2. 自己定义allocator实现对象内存池,只有在确认内存分配是瓶颈时才用。
  3. 不拿两个容器实例做赋值操作,传入参数用指针或引用,传出的参数用swap来实现容器数据传递,务必确认自己清楚知道在做什么。

关于C++ 数组和指针的问题?

对数组取地址时,数组名不会被解释为其地址。等等,数组名难道不被解释为数组的地址吗?不完全如此:数组名被解释为其第一个元素的地址,而对数组名应用地址运算符(即&)时,得到的是整个数组的地址

short tell[10];        //声明一个长度为20字节的数组(short型变量大小为2字节)
cout << tell << endl;  //显示&tell[0]
cout << &tell << endl; //显示整个数组的地址

从数字上说,这两个地址相同;但从概念上说,&tell[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此,表达式tell+1将地址值加2,而表达式&tell+1将地址值加20。换句话说,tell是一个short指针(short*),而&tell是一个指向包含10个元素的short数组的指针(short(*) [10])

您可能会问,前面有关&tell的类型描述是如何来的呢?
首先,您可以这样声明和初始化这种指针:

short (*pas) [10] = &tell; //pas指向一个有10个short元素的数组

如果省略括号,优先级规则将使pas先与[10]结合,导致pas是一个包含10个short型指针的数组,因此括号是必不可少的。
其次,如果要描述变量的类型,可将声明中的变量名删除。因此,pas的类型为short(*) [10]
另外,由于pas被设置为tell,因此*pastell等价,所以(*pas) [0]tell数组的第一个元素。

回到问题本身,当int型变量的大小为4字节时,arr是一个4字节内存块的地址,而&arr是一个40字节内存块的地址。虽然这两个内存块的起始位置相同,但是大小不同。

题主可以在代码里加上这两行,对比一下输出结果:

cout << arr + 1 << endl;  //地址+4
cout << &arr + 1 << endl; //地址+40

示例代码:

int  arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    cout << &(arr[0]) << endl;
    cout << arr << endl;
    cout << &arr << endl;
    cout << arr + 1 << endl;
    cout << &arr + 1 << endl;

输出:

0013FE60
0013FE60
0013FE60
0013FE64
0013FE88
请按任意键继续. . .

C/C++ 中 0 与 NULL 区别是什么?用 delete 时,用 p=0,还是用 p=NULL 好?为什么?

ANSER:

首先呢,要明白一点儿,NULL是一个无类型的东西,而且是一个宏。而宏这个东西,从C++诞生开始,就是C++之父嗤之以鼻的东西,他推崇尽量避免宏。而在他的FAQ中,也有相应的一个关于NULL0的解释,也谈到了这一点儿。

C++标准中,我们可以见到一个词语叫做null pointer constant,其实在C++11标准前,是只承认0null pointer constant的。所以,在C++中,我们也经常能听到一个说法,就是赋予null pointer,应该是使用0,而非NULL。而nullptr pointer constant这个词语在C++11发布后,终于再添了一个成员,就是nullptr。而与NULL本质不同的是,nullptr是有类型的(放了在stddef头文件中),类型是 typdef decltype(nullptr) nullptr_t; 而正是因为是有类型的,这给我们编译器实现nullptr的时候带来了更多细节的考虑,当然也给了使用者更多的保障,所以如果你的编译器支持nullptr,请一定使用nullptr

nullptr的出现背景,其实是很简单的,C++哲学上来说就是C++之父一直对null pointer没有一个正式的表示感到非常不满,而更工程的来说,就是关于重载这个问题。

void f(void*)
{
}

void f(int)
{
}

int main()
{
    f(0); // what function will be called?
}

而引入了nullptr,这个问题就得到了真正解决,会很顺利的调到void f(void*)这个版本。

好了,真的以为nullptr就这样了么? 我前面说过了nullptr是有类型的,叫做nullptr_t,这给我们编译器实现带来了诸多要考虑的东西,不幸的话让我们来举点儿奇葩例子吧!

union U
{
    long i;
    nullptr_t t;
};

int main()
{
    U u;
    u.i = 3;
    printf("%ld\n",(long)u.t); // What it is? 0 or 3?
}

那么这是应该符合union语意还是nullptr的语意呢?这在标准中是没有说的,我们也为此争论了非常久。当然在我们编译器的实现还是保持了nullptr的语意,结果是0

nullptr有类型后,还能做什么呢?那当然就是可以捕获异常了。

int main()
{
  try
  {
    throw nullptr;  
  }
  catch(nullptr_t)
  {

  } 
}

你扔一个NULL试试?看他应该用什么收,正是因为没有类型,所以就要用它的本质类型,比如long什么的来说。你扔一个0试试?那就也不是所谓的空指针类型了,就是要用int什么的来收了。

所以,推崇nullptr是有道理的,我们在编译器实现nullptr的时候考虑了非常非常多的细节,还有很多你们可能一直用不到的情况,我们都要用来测试,目的就是保障开发者的使用。再次那句话,如果你的编译器支持nullptr,请一定使用nullptr

最后再扯一点儿,0C++是很神奇的东西。比如纯虚函数为什么是用=0来设置的,不知道有没有同学去考虑过这个问题没有。如果你深刻理解了C++哲学,这应该就是非常简答的问题了。学语言嘛,一定要学到其哲学,你才能知道其之美,其之威力,尤其是C++

如何计算带指针的结构体大小?

问题:

struct X
{
    char a;
    float b;
    int c;
    double d;
    unsigned e;
};

由于存储变量时地址对齐的要求,所以这个结构体大小应该是32。

如果我多定义一个任意型的指针

struct X
{
    char a;
    float b;
    int c;
    double d;
    unsigned e;
    int *f;
};

按照地址对齐的要求,这样结构体大小应该是40,但它仍然是32。

我再加一个任意型的指针

struct X
{
    char a;
    float b;
    int c;
    double d;
    unsigned e;
        int *f;
        double *g;
};

结果这样结构体大小就直接变成40了。如果指针大小是地址总线大小的话,2个指针就是8字节,加上原来的32字节也刚好等于40字节,并且也满足存储变量时地址对齐的要求。但是为什么刚才加一个指针不变,两个就变了。

回答:

一开始的时候是这样的

struct X {
    char a;         // 1 bytes
    char padding1[3];   // 3 bytes
    float b;        // 4 bytes
    int c;          // 4 bytes
    char padding2[4];   // 4 bytes
    double d;       // 8 bytes
    unsigned e;     // 4 bytes
    char padding3[4];   // 4 bytes
};

加了一个指针以后是这样的

struct X {
    char a;         // 1 bytes
    char padding1[3];   // 3 bytes
    float b;        // 4 bytes
    int c;          // 4 bytes
    char padding2[4];   // 4 bytes
    double d;       // 8 bytes
    unsigned e;     // 4 bytes
    int *f;         // 4 bytes
};

再加一个指针以后是这样的

struct X {
    char a;         // 1 bytes
    char padding1[3];   // 3 bytes
    float b;        // 4 bytes
    int c;          // 4 bytes
    char padding2[4];   // 4 bytes
    double d;       // 8 bytes
    unsigned e;     // 4 bytes
    int *f;         // 4 bytes
    double *g;      // 4 bytes
    char padding3[4];   // 4 bytes
};

这样就好懂多了吧?

轮子哥的回答,说明了原理

我来补充一下匿名用户的答案。第一个图是这样的

struct X {
    char a;         // 1 bytes
    char padding1[3];   // 3 bytes
    float b;        // 4 bytes
    int c;          // 4 bytes
    char padding2[4];   // 4 bytes
    double d;       // 8 bytes
    unsigned e;     // 4 bytes
    char padding3[4];   // 4 bytes
};

padding1的存在是因为,offset(b)必须能够被align(b)整除,所以塞三个char

b的偏移字节是它自身字节的整数倍,因此要添加三个字节的偏移

padding2的存在是因为,offset(d)必须能够被align(d)整除,所以塞4个char

原因同上

对于所有基本类型,align(T)==sizeof(T),所以有了上面两条,

align(X)是多少呢?当然就是所有成员里面align最大的那个,是align(d)==8
好了,因此sizeof(X)必须能够被align(X)整除,就有了padding3

整个结构体的大小必须能被其中最大align的树整除。

<think>好的,我现在需要帮助用户找到学习C++的资源和学习方法,从入门到精通。首先,我要回顾用户提供的引用内容,看看里面有哪些有用的信息。引用[1]提到C++的基础识、应用领域和学习路线,引用[2]推荐了一些书籍和安装开发环境,引用[3]则详细列出了教程目录和项目案例。 用户明确想要教程资源和方法,所以应该分步骤来组织回答。首先推荐书籍和在线课程,这部分引用[2]和[3]都有提到,比如《C++ Primer》和在线教程。然后安装开发环境,引用[2]提到了这点,需要具体说明如何安装,比如Visual Studio或Code::Blocks。 接下来是学习路径,引用[3]的目录很详细,可以分阶段:基础语法、面向对象、高级特性、模板和STL、实战项目。每个阶段要列出关键识点,比如基础语法包括变量、函数、控制结构,面向对象包括类和继承,高级特性如多态和虚函数,模板和STL部分需要强调标准库的应用。 实践方法方面,用户需要道如何练习,比如做练习题和项目,引用[3]中的项目案例可以作为例子。还要推荐社区和论坛,帮助解决问题。常见问题部分要解答学习难点,比如指针和内存管理,以及如何应用C++到不同领域,如游戏开发或嵌入式系统。 最后要生成相关问题,可能涉及性能优化、就业方向、调试技巧等。需要确保所有推荐的资源和方法都有引用支持,并且结构清晰,步骤明确。同时检查是否符合用户的要求,比如中文回答、LaTeX格式正确,但问题中没有数学公式,所以可能不需要处理数学内容。总之,整理出系统化的学习路线,结合理论和实践,引用提供的资料,帮助用户高效学习C++。</think>### C++ 学习教程:从入门到精通 以下是系统化的学习路径与资源推荐,结合理论与实践,帮助你高效掌握 C++ 编程。 --- #### **一、学习资源推荐** 1. **经典书籍** - 《C++ Primer》:全面覆盖基础语法与核心概念,适合逐章精读[^2]。 - 《Effective C++》:深入理解 C++ 编程规范与最佳实践。 - 《C++ Programming Language》(Bjarne Stroustrup 著):语言设计者亲撰,适合进阶学习。 2. **在线教程** - **C++ 官方文档**(cppreference.com):权威语法参考与标准库说明。 - **菜鸟教程、RUNOOB**:中文入门教程,适合快速上手基础语法[^3]。 - **YouTube/B站**:搜索“C++ 从入门到精通”系列视频,直观学习编程操作。 3. **开发环境** - **Visual Studio**(Windows):功能强大的 IDE,集成调试工具[^2]。 - **Code::Blocks**(跨平台):轻量级 IDE,适合初学者。 - **CLion**(JetBrains 出品):专业级工具,支持现代 C++ 特性。 --- #### **二、系统化学习路径** ##### **阶段 1:基础语法(1-2 个月)** - **核心内容**: - 变量、数据类型、运算符(如 `int`, `float`, `指针`)。 - 控制结构(`if-else`, `for`, `while`)。 - 函数、作用域与头文件(如 `#include <iostream>`)。 - 数组、字符串与基本输入输出(`cin`, `cout`)。 - **实践建议**: - 编写简单程序(如计算器、斐波那契数列)。 - 理解内存模型与指针操作(例如 `int* p = &a;`)。 ##### **阶段 2:面向对象编程(2-3 个月)** - **核心内容**: - 类与对象(`class`, `public/private`)。 - 构造函数与析构函数(初始化列表、`~ClassName()`)。 - 继承与多态(`virtual`, 纯虚函数)。 - 操作符重载(如 `+`, `<<`)。 - **实践建议**: - 实现小型管理系统(如学生信息管理)。 - 通过项目理解 `this指针` 和 `友元函数`[^3]。 ##### **阶段 3:高级特性(2 个月)** - **核心内容**: - 模板编程(函数模板 `template <typename T>`、类模板)。 - 标准模板库(STL):容器(`vector`, `map`)、算法(`sort`, `find`)。 - 异常处理(`try-catch`)、命名空间(`namespace`)。 - **实践建议**: - 使用 `STL` 实现高效算法(如快速排序、哈希表)。 - 学习文件操作(`fstream`)与多线程基础。 ##### **阶段 4:实战与优化(持续)** - **项目案例**: - 小型游戏开发(如控制台贪吃蛇)。 - 网络通信程序(基于 `Socket` 实现客户端/服务器)。 - 性能优化实践(内存管理、`constexpr` 与移动语义)。 --- #### **三、学习方法与技巧** 1. **编码实践** - 每日坚持编写代码,通过平台如 **LeetCode** 或 **Codewars** 刷题。 - 参与开源项目(GitHub),学习代码规范与协作开发。 2. **调试能力** - 掌握调试工具(如 `GDB` 或 IDE 内置调试器)。 - 学会分析核心转储(Core Dump)与内存泄漏检测(Valgrind)。 3. **社区交流** - 加入论坛(Stack Overflow、乎专栏)解决疑难问题。 - 关注 C++ 标准更新(如 C++11/17/20 新特性)。 --- #### **四、常见问题解答** 1. **Q:C++ 学习难点是什么?** - **A**:指针与内存管理、多态与虚函数机制、模板元编程[^3]。 2. **Q:如何高效应用 C++?** - **A**:结合领域需求(如游戏开发用 `Unreal Engine`,嵌入式开发用 `ARM` 架构优化)[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值