C++ Primer Fifth Edition notes(4)
- 1C++ Primer Fifth Edition notes(1)
- 2C++ Primer Fifth Edition notes(2)
- 3C++ Primer Fifth Edition notes(3)
- 4C++ Primer Fifth Edition notes(4)本文

2.4 const
1 const限定符的初始化
- 初始化后不能再赋值
例如:
const int bufSize = 512;int const bufSize = 512; // 等价写法bufSize = 512; // 错误
- 定义时必须初始化
例如:
const int i = get_size(); // 可以,运行时取得初始值const int j = 42; // 可以const int k; // 错误,缺少初始值
2 如何用 const 修饰对象?
顶层 const:限制对象本身。
底层 const:限制通过指针或引用访问的对象。
- 修饰指针
例如:
int x = 1, y = 2;const int* p1 = &x; // 指向常量的指针:不能通过 p1 改 x,但 p1 可指向别处int const* p2 = &x; // 同上,等价int* const p3 = &x; // 常量指针:p3 不能指向别处,但可以通过 p3 改 xconst int* const p4 = &x; // 既不能改所指对象,也不能改指针本身// *p1 = 3; // 错误p1 = &y; // 正确*p3 = 3; // 正确// p3 = &y; // 错误
const int* p:指向常量的指针,底层const,p可变,*p不可变;
int* const p:常量指针,顶层const,p不可变,*p可变;
const int* const p:都不可变。
- 修饰引用
例如:
int x = 1;const int& r = x;// r = 2; // 错误const int& r2 = 42; // 可以绑定临时量,生命周期被延长void print(const std::string& s); // 函数参数
3 constexpr与const的区别是什么?
定义:constexpr 是一个关键字,用它声明变量,就是要求这个变量在编译时确定初始值(常量表达式),并且以后不能修改。
这里的常量表达式,就是值不会改变,而且编译时就能算出的表达式。
例如:
constexpr int max_files = 20; // 合法,编译时知道是 20constexpr int limit = max_files + 1; // 合法,编译时能算出 21int x;std::cin >> x;constexpr int sz = x * 2; // 错误,运行时才知道结果const int sz = x * 2; // 合法
| 声明方式 | 初始化后能否修改 | 初始值是否必须在编译时确定 |
|---|---|---|
int | 可以 | 不必 |
const int | 不可以 | 不必 |
constexpr int | 不可以 | 必须 |
const 只保证只读,不保证编译期;constexpr 保证可用于编译期常量表达式。
在指针的声明中:
| 声明 | 指针能否改指向 | 能否通过指针修改所指对象 |
|---|---|---|
const int *p | 可以 | 不可以 |
constexpr int *q | 不可以 | 类型允许,但必须先指向有效对象 |
例如:
constexpr int i = 42;int j = 0;constexpr const int *p = &i;constexpr int *p1 = &j;p1 = nullptr; // 错误,修改的是指针保存的地址*p1 = 10; // 合法,修改的是 j 的值
constexpr让指针自身成为常量,不会自动给所指对象加上const。
p1的地址必须在编译时确定,但j的值仍然可以改变。
2.5 处理类型
1 什么是类型别名?
定义:类型别名给已有类型起一个新名字,不会创建新类型,只是让代码更可读、更好维护。
基本语法:
// C 风格,C++ 也支持typedef 原类型 别名;
// C++11 起推荐using 别名 = 原类型;- 用
typedef定义别名
例如:
typedef double wages;typedef wages base, *p;第一行使
wages成为double的别名。第二行中的
base也是double的别名,p则是double*的别名。
- 用
using定义别名
例如:
using SI = Sales_item;SI item; // 一个 Sales_item 变量
const修饰指针别名时
例如:
typedef char *pstring;const pstring cstr = 0; // 等同于 char* const cstr = 0const pstring *ps; // 等同于 char* const* ps;const char *cstr = 0; // 等同于 const char* cstr = 0
pstring代表的类型是char*,所以const pstring是带有顶层const的指针类型。
2 auto 类型说明符
定义:auto 让编译器根据初始化式推导变量类型,变量必须初始化。
- 处理指针
例如:
const int ci = i, &cr = ci;auto b = ci; // intauto c = cr; // intauto d = &i; // int*auto e = &ci; // const int*
e可以改指向,但不能通过e修改它指向的const int类型的变量。如果希望新变量本身也是常量:
const auto f = ci;
- 处理引用
例如:在
auto后写&,定义的就是引用。auto &g = ci; // g 是 const int&auto &h = 42; // 错误:普通引用不能绑定到字面值const auto &j = 42; // 正确:常量引用可以绑定到字面值
3 什么是值类别?
定义:值类别是 C++ 用来描述一个表达式是否代表一个具有明确身份的对象,以及它的资源是否可以被移动。
| 值类别 | 有身份? | 可移动? | 含义 |
|---|---|---|---|
| lvalue 左值 | 有 | 否 | 有名字、有固定位置的对象 |
| xvalue 亡值 | 有 | 是 | 有身份,但即将被移动/销毁 |
| prvalue 纯右值 | 无 | 是 | 临时计算结果,没有持久身份 |
组合成:
expression / \ glvalue rvalue / \ / \ lvalue xvalue xvalue prvalue例如:
int x = 1;int* p = &x;int& lr = x;// 左值x; // lvalue* p; // lvaluelr; // lvalue++x; // lvalue// 亡值std::move(x); // xvaluestatic_cast<int&&>(x); // xvalue// 纯右值42; // prvaluex + 1; // prvaluex++; // prvalue&x; // prvalue
4 decltype 类型说明符
定义:decltype 是 C++11 引入的类型说明符,作用是在编译期推导一个表达式的精确类型,但不会真正求值这个表达式。
基本语法:
decltype(表达式)decltype(auto) // C++14如果表达式是 左值 lvalue,decltype(e) 得到 T&
如果表达式是 亡值 xvalue,decltype(e) 得到 T&&
如果表达式是 纯右值 prvalue,decltype(e) 得到 T
例如:
int x = 0;const int cx = 1;int& rx = x;decltype(x) a; // intdecltype(cx) b = 2; // const intdecltype(rx) c = x; // int&,必须初始化decltype((x)) d = x; // int&,注意多了一层括号decltype(x + 1) e = 3; // int,x+1 是纯右值decltype(++x) f = x; // int&,前置 ++ 返回左值decltype(x++) g = 4; // int,后置 ++ 返回纯右值因为
x是未加括号的变量名,得到声明类型int;而(x)是一个左值表达式,所以得到int&。例如:声明变量类型
int a = 1;double b = 2.0;decltype(a + b) c = 3.0; // c 是 double例如:
decltype(auto)int x = 0;decltype(auto) r = (x); // int&decltype(auto) v = x; // int
decltype(auto)会按decltype的规则推导返回类型,能保留引用和const。例如:不求值获取表达式类型
int func();decltype(func()) x; // x 是 int,但不会调用 func()
与auto的不同:
| 特性 | auto | decltype |
|---|---|---|
| 推导规则 | 类似模板参数推导 | 精确保留类型和值类别 |
顶层const | 通常忽略 | 保留 |
| 引用 | 通常忽略 | 保留 |
| 是否需要初始化 | 变量必须初始化 | 变量可默认初始化,若类型允许 |
| 典型例子 | auto a = rx;得到int | decltype(rx) b = x;得到int& |
2.6 自定义数据结构
1 如何定义类和数据成员?
定义:在 C++ 中,类用 class 或 struct 定义,数据成员就是写在类里面的变量。
基本语法:
class 类名 {访问修饰符: 数据类型 成员名; 成员函数;}; // 注意最后有分号例如:定义
Sales_data类#include <string>struct Sales_data {std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};
struct后面是类名Sales_data。花括号内是类体。其中的三个变量称为数据成员,它们决定每个Sales_data对象包含哪些数据。
或使用class来定义:
例如:
#include <iostream>#include <string>class Student {private: // 私有数据成员,类外不能直接访问int id_;std::string name_;double score_;public: // 公有成员函数,类外可以调用Student(int id, std::string name, double score): id_(id), name_(std::move(name)), score_(score) {}void print() const {std::cout << id_ << " " << name_ << " " << score_ << std::endl;}};访问控制:
public:类外可访问
private:只有类内和友元可访问
protected:类内和派生类可访问
class默认访问权限是private,struct默认是public。class A {int x; // 默认 private};struct B {int y; // 默认 public};
2 如何使用类和数据成员?
创建对象
例如:
Sales_data accum, trans;
accum和trans各有自己的bookNo、units_sold和revenue。修改一个对象的成员,不会改变另一个对象对应的成员。Sales_data *salesptr;这行只定义了一个指针,没有创建新的
Sales_data对象。此时salesptr还没有初始化,不能用它访问对象。Student s1(1, "Tom", 90.5); // 栈上创建对象,自动调用构造函数Student* s2 = new Student(2, "Jerry", 88.0); // 堆上创建对象Student arr[2] = {Student(3, "Alice", 80.0),Student(4, "Bob", 85.0)}; // 对象数组创建并初始化对象。
访问成员
- 访问普通成员:
对象.成员
例如:
struct Point {int x;int y;};int main() {Point p;p.x = 10;p.y = 20;std::cout << p.x << "," << p.y << "\n";}
struct默认成员是public,所以可以直接赋值。
- 访问指针成员:
指针->成员
例如:
s2->print(); // 指针用 ->s2->setScore(91.0);delete s2; // 手动释放
- 访问私有成员:
对象.公有函数()
例如:
s1.setScore(95.0);std::cout << "新成绩: " << s1.getScore() << "\n";
- 访问静态成员:
类名::静态成员
定义:静态成员是用 static 关键字修饰的类成员,它不属于某个具体的对象,而属于整个类。也就是说,不管创建了多少个对象,静态成员只有一份,所有对象共享它。
静态成员包括:静态数据成员、静态成员函数。
例如:访问静态数据成员
#include <iostream>class Counter {public:static int count; // 类内声明Counter() { ++count; }~Counter() { --count; }};int Counter::count = 0; // 类外定义并初始化int main() {Counter a, b;std::cout << Counter::count << "\n"; // 2return 0;}这里
count被所有Counter对象共享。创建两个对象后,count变成 2。class Counter {public:inline static int count = 0; // C++17};
例如:访问静态成员函数
class Counter {public:static int count;Counter() { ++count; }static int getCount() { // 静态成员函数return count;}};int Counter::count = 0;int main() {Counter a, b;std::cout << Counter::getCount() << "\n"; // 2}静态成员函数没有
this,可以通过类名直接调用。
调用成员函数
- 通过对象调用:
对象.成员函数()
例如:
Student s1(1, "Tom", 90.5);s1.print(); // 调用成员函数
- 通过指针调用:
指针->成员函数()
例如:
Student* s2 = new Student(2, "Jerry", 88.0);s2->print(); // 指针用 ->
3 如何编写自己的头文件?
定义:头文件本质是一种数据结构的接口声明,类通常定义在头文件中。
头文件.h的基本写法:
#ifndef MY_MATH_H#define MY_MATH_H
// 头文件内容
#endif上述三行为头文件保护符的写法,或用#pragma once,用来防止同一个源文件多次包含这个头文件,上述是更标准可移植的写法。
例如:
Sales_data.h#ifndef SALES_DATA_H#define SALES_DATA_H#include <string>struct Sales_data {std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};#endif
4 为什么需要头文件保护符?
定义:头文件保护符是用来防止同一个头文件在同一个翻译单元中被多次包含的一组预处理指令。
例如:一个头文件可能被多次包含。
#include <string>#include "Sales_data.h"而
Sales_data.h自己又包含<string>。其他头文件也可能间接包含
Sales_data.h。如果类定义被重复放入同一个源文件,编译器会看到多个相同的类定义并报错。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!













