《C++ Primer》第2章(变量和基本类型)核心内容详解
本章是C++编程的基石,系统地讲解了构成程序的基本数据单元及其操作方式。以下通过表格和代码示例,详细解析各核心知识点。
1. 基本内置类型与类型转换
C++的基本内置类型包括算术类型(整型、浮点型、字符型)和特殊类型void。理解它们的尺寸和表示范围是正确选择数据类型的前提。
| 类别 | 类型 | 含义 | 典型尺寸/范围 |
|---|---|---|---|
| 布尔型 | bool |
真/假值 | 通常1字节,true或false |
| 字符型 | 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 = # // 底层const:指向常量的指针(指针指向的值不能改)
// *p1 = 20; // 错误:不能通过p1修改num
p1 = nullptr; // 正确:p1本身可以改变指向
int *const p2 = # // 顶层const:常量指针(指针本身不能改)
*p2 = 20; // 正确:可以通过p2修改num
// p2 = nullptr; // 错误:p2本身是常量
const int *const p3 = #// 既是顶层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提供了安全性保障,而类型别名、auto和decltype则提高了代码的可读性和可维护性。自定义数据结构将相关数据组织在一起,是面向对象编程的起点。