C++ Primer Notes(4): 变量初始化和作用域

server/2025/1/15 1:41:19/

这篇笔记是关于 C++ Primer 的 2.2 节 Variables 的记录和思考。

1. 变量是什么

变量是有名字的对象: variables are named objects.

那么什么是对象(object)? 一块具有类型的内存,称为对象。

在这里插入图片描述
而没有名字的对象, 则称为 unnamed objects, 或者说“匿名对象”。

2. 定义变量

指定类型,指定变量名字, 可选地给出初始化器。这就是变量定义。举例:

int sum = 0, value, units_sold = 0;
Sales_item item;
std::string book("0-201-78345-X");

3. 变量初始化

1)初始化指的是定义变量的时候,一并获得了指定的值。

2)最基本的初始化,是 Type name=value 的形式。
3)C++11 新增了列表初始化: Type name{value}, 推荐使用:
在这里插入图片描述
对于built-in 类型,这种初始化还能帮忙发现类型窄化(narrow)并报错编译错误:
在这里插入图片描述
4)当定义变量时没有指定初始化器,这就是“默认初始化”(default initialized)

  • 对于built-in 类型,默认初始化指的是:
    • 如果变量位于函数内,则没有初始化,取值是不确定的
    • 如果变量位于函数外,则有初始化,取值是0
  • 对于 class 类型,类的定义决定了使用这一类型定义变量时,是否需要指定初始化器。比如下面的 class A 就必须指定 initializer:

在这里插入图片描述
看到这里我确实体会到,C++ Primer不适合入门: 如果用户只有C语言经验,在不知道class的基本知识的情况下,是没法理解初始化器这里的说明的。所谓的 top-down 方式只不过是作者的一厢情愿罢了。

书上又特意强调一次:如果没有初始化,对于内置类型并且在函数内部那取值是不确定的,对于class类型的对象则取值取决于类的定义:
在这里插入图片描述

  1. 没初始化的变量导致运行时问题
    如果变量没初始化,然后被用到,则容易导致难以排查的运行时错误;最佳实践是,对于 built-in 类型,始终初始化。
    在这里插入图片描述
    作者狡辩说,编译器不需要检查没初始化的变量。呵呵。一边在推荐,另一边毫无作为,真的矛盾:
    在这里插入图片描述
  2. malloc 或 new 的内存,如何初始化?
    这是我自己想到的,书上这一节压根没提。 在 OpenCV 中,cv::Mat() 的内部实现是调用了 fastMalloc() 函数,而 fastMalloc() 则是基于 malloc() 实现的,取值不确保是0.

这种做法在我看来是错误的,是不负责的,因为这把初始化的任务交给了用户,而用户并不都知道 cv::Mat() 的实现, 一旦用户忘记初始化, 得到的图像内容可能是有变化的,导致bug非常难排查。

正确做法应该是,申请图像像素内容时就做初始化, 考虑用 new 替代 malloc。 新的问题来了: new 的写法是怎样的?

#include <iostream>int main()
{int* data = new int[10 * 10]; // 结果不一定为0,是undefined行为//int* data = new int[10 * 10](); // 结果一定为0for (int i = 0; i < 10; i++){for (int j = 0; j < 10; j++){std::cout << data[i*10 + j] << ", ";}std::cout << std::endl;}delete[] data;return 0;
}

第一种写法, int* data = new int[10 * 10] 不一定产生全0结果,或者说,非常容易产生全0结果,但是不能总是保证是全0,它是编译器决定的。 那么为了让编译器不给我们使绊子,我们祭出 Address Sanitizer 让它强制为别的值:

g++ -fsanitize=address -g test13.cpp -o test13

在这里插入图片描述
然后修改初始化写法为第二种:

int* data = new int[10 * 10]()

再次开启asan编译和运行,结果仍然是全0,说明是有效的初始化了的
在这里插入图片描述

参考: 从 -1094795586 到内存初始化

4. 变量声明

举例:

int i; // 声明,并且定义变量 i
extern int j; // 声明,但是不定义变量 j
extern double pi = 3.1416; // 这是定义. 并且不能放在函数里

关于声明(declaration)和定义(definition)的详细区别,cppreference 给出了详细说明:

https://en.cppreference.com/w/cpp/language/definition

5. 作用域(Scope)

定义在函数之外的变量,叫做全局变量,拥有全局作用域(global scope)。

定义在函数内的变量,显然,也是定义在 {} 内的变量, 它的作用域叫做 block scope.

相对关系: 根据 scope 的大小,区分为 inner scope 和 outer scope。

作用域的概念说完了,接着说建议:

  • 用到变量的时候再定义它
    其实是应用了 “最小需要原则”。对于不需要这个变量的地方,比如外层block,或者其他函数, 则不需要让它们知道这个变量
  • 避免shadowed variable
    很遗憾,C/C++编译器默认不警告,更不报错。 -Werror=shadow 走起。

再说一些个人的补充思考:
书上提到的变量 scope 都是显示的, 其实还有隐式的scope。 啥意思呢? 就是说,当确定了一个 block (一个 curly brace, { } ), 变量的 scope 仍然是可以进一步确定边界的。 又或者说, 需要考虑变量的生命周期。 最需要考虑的有两个:

  • return 语句
  • throw 语句

return语句就是函数内最后执行的语句吗?

对于C++来说,并不是。 看如下代码:

#include <iostream>class A
{
public:A(const char* a_name): name(a_name){fprintf(stderr, "%s begin\n", name.c_str());}~A(){fprintf(stderr, "%s end\n", name.c_str());}
private:std::string name;
};int main()
{fprintf(stderr, "hello\n");A a1("a1");A a2("a2");fprintf(stderr, "good bye\n");return 0;
}

在这里插入图片描述
可以看到,return 语句之前执行的打印是 “good bye”,对应到红色 scope,但是 return 0 之后仍然有语句被执行,对应到黄色scope,再后来是另一条被执行的语句,对应到绿色scope。

显然在打印 a2 begin 和 a2 end 的时候, 红色的 scope 处于死亡状态。所以说, scope 的概念, 应当精确到变量的生命周期, 而不是简单的划分到 {} 之间。


http://www.ppmy.cn/server/158107.html

相关文章

Java-数据结构-链表(LinkedList)-双向链表

一、LinkedList(无头双向链表) 在之前的学习中&#xff0c;我们已经学习过"单向链表"并通过做题加深了对"单向链表"的认知&#xff0c;而今天我们继续来学习链表&#xff0c;也就是"无头双向链表"~ 在了解"无头双向链表"之前&#x…

用户界面软件04

后果 使用这种架构很容易对两个层面的非功能性需求进行优化&#xff0c;但是你仍然需要小心不要将功能 需求重复实现。 现在&#xff0c;两个层面可能有完全不同的设计。比如&#xff0c;用户界面层可能使用配件模型&#xff08;Widget Model&#xff09;&#xff0c; 以大量的…

OpenCV基础:视频的采集、读取与录制

从摄像头采集视频 相关接口 - VideoCapture VideoCapture 用于从视频文件、摄像头或其他视频流设备中读取视频帧。它可以捕捉来自多种源的视频。 主要参数&#xff1a; cv2.VideoCapture(source): source: 这是一个整数或字符串&#xff0c;表示视频的来源。 如果是整数&a…

JS进阶--JS听到了探囊的回响

深浅拷贝 深拷贝 开发中我们经常需要复制一个对象&#xff0c;如果直接用赋值会有下面的问题 深浅拷贝只针对引用类型&#xff0c;深拷贝拷贝的是对象&#xff0c;不是地址 常见方法&#xff1a; 1. 通过递归实现深拷贝 2. lodash/cloneDeep 3. 通过JSON.stringify()实现…

C语言的循环实现

C语言的循环实现 在计算机编程中&#xff0c;循环是一种基本的控制结构&#xff0c;它使得程序能够反复执行某一段代码&#xff0c;直至满足特定条件。在C语言中&#xff0c;循环主要有三种形式&#xff1a;for循环、while循环和do...while循环。每种循环都有其独特的特点和使…

2025年01月11日Github流行趋势

项目名称&#xff1a;xiaozhi-esp32 项目地址url&#xff1a;https://github.com/78/xiaozhi-esp32项目语言&#xff1a;C历史star数&#xff1a;2433今日star数&#xff1a;321项目维护者&#xff1a;78, MakerM0, whble, nooodles2023, Kevincoooool项目简介&#xff1a;构建…

【Linux】编辑器之神vim使用教程

什么是Vim&#xff1f; Vim是从vi发展而来的文本编辑器&#xff0c;代码补全、编译以及错误跳转等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用&#xff0c;下图为Vim的键盘图 Vim的使用 基本上vi/vim共分为三种模式&#xff1a;命令模式&#xff08;Command Mode&…

一学就废|Python基础碎片,文件读写

文件处理是指通过编程接口对文件执行诸如创建、打开、读取、写入和关闭等操作的过程。它涉及管理程序与存储设备上的文件系统之间的数据流&#xff0c;确保数据得到安全高效的处理。 Python 中的文件模式 打开文件时&#xff0c;我们必须指定我们想要的模式&#xff0c;该模式…