C++变量与基本类型精解

《C++ Primer》第2章(变量和基本类型)核心内容详解

本章是C++编程的基石,系统地讲解了构成程序的基本数据单元及其操作方式。以下通过表格和代码示例,详细解析各核心知识点。

1. 基本内置类型与类型转换

C++的基本内置类型包括算术类型(整型、浮点型、字符型)和特殊类型void。理解它们的尺寸和表示范围是正确选择数据类型的前提。

类别 类型 含义 典型尺寸/范围
布尔型 bool 真/假值 通常1字节,truefalse
字符型 char 基本字符 1字节(8位)
wchar_t 宽字符 2或4字节
char16_t Unicode字符 2字节(C++11)
char32_t Unicode字符 4字节(C++11)
整型 short 短整型 至少16位
int 整型 通常32位
long 长整型 至少32位
long long 长整型 至少64位(C++11)
浮点型 float 单精度 通常6位有效数字
double 双精度 通常10位有效数字
long double 扩展精度 通常10位有效数字

类型转换在混合类型运算或赋值时自动发生,遵循特定规则。

cpp 复制代码
// 示例:隐式类型转换
bool b = 3.14;     // b = true(非零值转为true)
int i = b;         // i = 1(true转为1)
double d = i;      // d = 1.0(整型转浮点)
unsigned u = -1;   // u = 4294967295(32位系统,负数转为大正数)

字面值常量的类型由后缀决定:

cpp 复制代码
20      // 十进制int
020     // 八进制int(值16)
0x20    // 十六进制int(值32)
3.14    // double
3.14f   // float
3.14L   // long double
'a'     // char
L'a'    // wchar_t
u8"hi"  // UTF-8字符串(C++11)

2. 变量定义、初始化与作用域

变量 提供具名的存储空间,其定义必须指定类型。

cpp 复制代码
// 多种初始化方式
int a = 0;        // 拷贝初始化(传统方式)
int b(0);         // 直接初始化
int c = {0};      // 列表初始化(C++11)
int d{0};         // 列表初始化(推荐,防止窄化转换)
auto e = 0;       // auto推导为int

// 默认初始化行为差异
int global_var;    // 全局变量默认初始化为0
void func() {
    int local_var; // 局部变量值未定义!危险!
    static int static_local; // 静态局部变量默认初始化为0
}

作用域规则:

  • 全局作用域:定义在所有函数之外,程序整个执行期有效
  • 块作用域 :定义在{}内,离开块后失效
  • 局部变量隐藏全局变量
cpp 复制代码
#include <iostream>
int value = 100;  // 全局变量

int main() {
    int value = 50;  // 局部变量,隐藏全局变量
    std::cout << value << std::endl;    // 输出50(局部)
    std::cout << ::value << std::endl;  // 输出100(使用作用域运算符访问全局)
    return 0;
}

3. 复合类型:引用与指针深度对比

引用和指针是C++中最重要的复合类型,它们的区别和使用场景需要清晰掌握。

特性 引用 指针
本质 对象的别名 存储地址的对象
初始化 必须初始化 可以不初始化(危险)
绑定/指向 绑定后不可更改 可以改变指向的对象
空值 不能绑定到空 可以指向nullptr
多级 没有引用的引用 可以有指针的指针
操作 直接使用,无需解引用 需要*解引用
cpp 复制代码
// 引用示例:本质是别名
int original = 42;
int &ref = original;  // ref是original的别名
ref = 100;            // 修改ref就是修改original
std::cout << original; // 输出100

// 指针示例:存储地址的对象
int var = 42;
int *ptr = &var;      // ptr存储var的地址
*ptr = 100;           // 解引用ptr修改var的值
std::cout << var;     // 输出100

// 指针的灵活性
int x = 10, y = 20;
int *p = &x;          // p指向x
p = &y;               // p现在指向y(引用不能这样做)
p = nullptr;          // p不指向任何对象(引用不能这样做)

// 指向指针的指针
int val = 42;
int *p1 = &val;
int **p2 = &p1;       // p2是指向指针的指针
std::cout << **p2;    // 输出42

4. const限定符的层级与使用

const限定符创建不可修改的对象,与指针、引用结合时产生不同语义。

cpp 复制代码
// 基本const变量
const int MAX_SIZE = 1024;  // 必须初始化
// MAX_SIZE = 2048;         // 错误:不能修改const变量

// const与引用:对常量的引用
const int ci = 100;
const int &r1 = ci;        // 正确:引用和对象都是const
// int &r2 = ci;           // 错误:不能用非const引用绑定const对象

// 特殊:可以用const引用绑定字面值或表达式
const int &r3 = 42;        // 正确
const int &r4 = ci * 2;    // 正确

// const与指针:顶层const和底层const
int num = 10;
const int *p1 = &num;      // 底层const:指向常量的指针(指针指向的值不能改)
// *p1 = 20;               // 错误:不能通过p1修改num
p1 = nullptr;              // 正确:p1本身可以改变指向

int *const p2 = &num;      // 顶层const:常量指针(指针本身不能改)
*p2 = 20;                  // 正确:可以通过p2修改num
// p2 = nullptr;           // 错误:p2本身是常量

const int *const p3 = &num;// 既是顶层const也是底层const
// *p3 = 30;               // 错误:底层const
// p3 = nullptr;           // 错误:顶层const

// constexpr(C++11):编译时常量
constexpr int size = 100;                 // 编译时确定
constexpr int scale = 2 * size;           // 编译时计算
constexpr int *np = nullptr;              // 指向整数的常量指针

5. 类型处理工具:别名、auto与decltype

随着类型复杂化,C++提供工具简化类型处理。

cpp 复制代码
// 类型别名:两种方式
typedef double wages;          // 传统方式
typedef wages base, *p;        // base是double,p是double*

using Salary = double;         // C++11别名声明(更清晰)
using pString = char*;         // pString是char*的别名

// 复杂类型的别名
typedef char *pchar;           // pchar是char*
const pchar cstr = nullptr;    // cstr是指向char的常量指针(不是指向const char的指针)
const char *cstr2 = nullptr;   // 这是指向const char的指针

// auto类型推导:忽略顶层const,保留底层const
int i = 0;
const int ci = i;
auto a = ci;                   // a是int(忽略顶层const)
auto b = &i;                   // b是int*
auto c = &ci;                  // c是const int*(保留底层const)

// auto与引用
int &ri = i;
auto d = ri;                   // d是int(引用被忽略)

// auto在循环中的应用(C++11)
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";   // auto推导为std::vector<int>::iterator
}
for (auto &elem : vec) {       // 范围for循环,auto&避免拷贝
    elem *= 2;                 // 修改原vector元素
}

// decltype类型推导:保留所有类型信息(包括引用和顶层const)
const int ci2 = 0, &cj = ci2;
decltype(ci2) x = 0;           // x是const int
decltype(cj) y = x;            // y是const int&,必须初始化

// decltype与表达式
int i2 = 42, *p4 = &i2, &r4 = i2;
decltype(r4) e = i2;           // e是int&
decltype(r4 + 0) f;            // f是int(表达式结果是右值)
decltype(*p4) g = i2;          // g是int&(解引用操作产生引用)
decltype((i2)) h = i2;         // h是int&(双层括号总是产生引用)

6. 自定义数据结构与头文件组织

结构体和类是组织相关数据的核心机制,头文件是组织代码的关键。

cpp 复制代码
// Sales_data.h 头文件
#ifndef SALES_DATA_H  // 头文件保护,防止重复包含
#define SALES_DATA_H

#include <string>

// 销售数据类定义
struct Sales_data {
    // 数据成员
    std::string bookNo;          // 书籍ISBN号
    unsigned units_sold = 0;     // 销售数量(类内初始值)
    double revenue = 0.0;        // 总收入(类内初始值)
    
    // 成员函数声明
    std::string isbn() const { return bookNo; }  // 返回ISBN
    Sales_data& combine(const Sales_data&);      // 合并销售记录
    double avg_price() const;                    // 计算平均价格
};

// 非成员函数声明
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);

#endif // SALES_DATA_H
cpp 复制代码
// Sales_data.cpp 实现文件
#include "Sales_data.h"

// 成员函数定义
double Sales_data::avg_price() const {
    if (units_sold)
        return revenue / units_sold;
    else
        return 0.0;
}

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;  // 返回调用对象的引用
}

// 非成员函数定义
Sales_data add(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

std::ostream &print(std::ostream &os, const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

std::istream &read(std::istream &is, Sales_data &item) {
    double price = 0.0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = item.units_sold * price;
    return is;
}
cpp 复制代码
// main.cpp 使用示例
#include <iostream>
#include "Sales_data.h"

int main() {
    Sales_data total;
    double price;
    
    if (std::cin >> total.bookNo >> total.units_sold >> price) {
        total.revenue = total.units_sold * price;
        
        Sales_data trans;
        while (std::cin >> trans.bookNo >> trans.units_sold >> price) {
            trans.revenue = trans.units_sold * price;
            
            if (total.isbn() == trans.isbn()) {
                total.combine(trans);  // 合并相同ISBN的记录
            } else {
                print(std::cout, total) << std::endl;
                total = trans;  // 处理下一本书
            }
        }
        print(std::cout, total) << std::endl;  // 打印最后一本书
    } else {
        std::cerr << "No data?!" << std::endl;
        return -1;
    }
    
    return 0;
}

本章的关键在于理解:类型决定了数据的含义和操作 。正确选择和使用类型是编写正确、高效C++程序的基础。复合类型(特别是指针和引用)是C++灵活性的核心,const提供了安全性保障,而类型别名、autodecltype则提高了代码的可读性和可维护性。自定义数据结构将相关数据组织在一起,是面向对象编程的起点。


参考来源

相关推荐
想唱rap1 小时前
UDP套接字编程
服务器·网络·c++·网络协议·ubuntu·udp
喜欢吃鱿鱼2 小时前
DES加解密(附带解决转义问题)-VUE
开发语言·前端·javascript
来日可期13142 小时前
计算机存储视角下的有符号数:不止是“正负”那么简单
c++
愚者游世2 小时前
variadic templates(可变参数模板)各版本异同
开发语言·c++·程序人生·面试
书到用时方恨少!2 小时前
Python 面向对象进阶:多态——同一个接口,千种面孔
开发语言·python·多态·面向对象
徐新帅2 小时前
4181:【GESP2603七级】拆分
c++·学习·算法·信奥赛
无忧.芙桃2 小时前
现代C++精讲之处理类型
开发语言·c++
黎梨梨梨_2 小时前
C++入门基础(下)(重载,引用,inline,nullptr)
开发语言·c++·算法
谁刺我心2 小时前
[QML]Functional功能型控件-虚拟键盘
开发语言·qml·虚拟键盘