《C++ Primer》导学系列:第 2 章 - 变量和基本类型

news2024/11/25 21:34:56

2.1 基本内置类型

概述

本小节介绍C++中的基本内置类型。基本内置类型是构成C++程序的基础,它们用于表示数据的不同形式,如整数、浮点数、字符等。理解和使用这些基本类型是编写C++程序的起点。

2.1.1 算术类型

C++的算术类型分为整型和浮点型。整型用于表示整数,浮点型用于表示带小数点的数值。

整型

整型包括各种不同的类型,以满足不同范围和精度的需求。

  • 基本整型类型
bool flag = true;            // 布尔类型
char ch = 'a';               // 字符类型
signed char sch = -1;        // 有符号字符类型
unsigned char uch = 255;     // 无符号字符类型
short s = -32768;            // 短整型
unsigned short us = 65535;   // 无符号短整型
int i = -2147483648;         // 整型
unsigned int ui = 4294967295;// 无符号整型
long l = -9223372036854775808L;       // 长整型
unsigned long ul = 18446744073709551615UL; // 无符号长整型
long long ll = -9223372036854775807LL;     // 长长整型
unsigned long long ull = 18446744073709551615ULL; // 无符号长长整型

浮点型

浮点型用于表示带小数点的数值,分为floatdoublelong double三种类型,分别表示单精度、双精度和扩展精度浮点数。

  • 基本浮点类型
float f = 3.14f;           // 单精度浮点型
double d = 3.14;           // 双精度浮点型
long double ld = 3.14L;    // 扩展精度浮点型

2.1.2 类型转换

类型转换用于在不同类型之间进行转换。C++支持隐式转换和显式转换。

隐式转换

隐式转换在需要时自动进行,不需要显式指定。

  • 示例
int i = 42;
double d = i;  // int隐式转换为double

显式转换

显式转换需要使用强制转换运算符。

  • C风格的显式转换
double d = 3.14;
int i = (int)d;  // double显式转换为int

  • C++风格的显式转换
double d = 3.14;
int i = static_cast<int>(d);  // 使用static_cast进行显式转换

2.1.3 字面值常量

字面值常量是程序中直接使用的常量值,它们在程序运行时是固定不变的。字面值常量可以是整数、浮点数、字符、字符串等。理解字面值常量有助于编写更清晰、可读性更高的代码。

整数字面值

整数字面值表示整数,可以用不同的进制表示,如十进制、八进制和十六进制。

  • 十进制:不带前缀的整数。
int dec = 42;  // 十进制

  • 八进制:以0开头的整数。
int oct = 052;  // 八进制

  • 十六进制:以0x0X开头的整数。
int hex = 0x2A;  // 十六进制

浮点字面值

浮点字面值表示带小数点的数值,可以用科学计数法表示。默认情况下,浮点字面值是double类型,可以通过后缀fF指定为float类型,通过后缀lL指定为long double类型。

  • 普通表示
double d = 3.14;

  • 科学计数法表示
double d2 = 3.14e2;  // 3.14 × 10^2

  • 指定类型
float f = 3.14f;           // float类型
long double ld = 3.14l;    // long double类型

字符和字符串字面值

字符和字符串字面值用于表示字符和字符串。

  • 字符字面值:用单引号括起来的单个字符。
char c = 'a';
char newline = '\n';  // 转义字符

  • 字符串字面值:用双引号括起来的一串字符。
const char* str = "Hello, World!";

布尔字面值和指针字面值

C++中还包括布尔字面值和指针字面值。

  • 布尔字面值truefalse
bool flag = true;

  • 指针字面值nullptr表示空指针。
int* p = nullptr;

重点与难点分析

重点

  1. 基本数据类型:理解整型和浮点型的不同类型及其适用范围。
  2. 类型转换:掌握隐式转换和显式转换的用法,特别是C++风格的显式转换。

难点

  1. 类型转换:初学者容易混淆隐式和显式转换的用法,需要通过练习掌握。

总结与提高

本节总结

  1. 了解了C++中的基本内置类型,包括整型和浮点型。
  2. 学习了类型转换的概念和具体用法,包括隐式转换和显式转换。

提高建议

  1. 多练习基本类型的使用:通过编写各种小程序,熟悉不同基本类型的定义和操作。
  2. 深入理解类型转换:特别是C++风格的显式转换,通过实际应用加深理解。
  3. 在算术运算中不要使用chat或者bool类型,因为chat有可能是signed chat,也可能是unsigned chat,具体由编译器来决定,而bool没有规定存储的大小。
  4. 执行浮点运算时选用double,这时因为float通常精度不够而且双精度浮点运算和单精度浮点运算的计算代价差不多,甚至在某些平台上可能双精度还更快。

2.2 变量

概述

本小节介绍C++中的变量,包括变量的定义、初始化、作用域和生命周期等。变量是程序中用于存储和操作数据的基本单元,理解变量的概念和使用方法是编写C++程序的基础。

2.2.1 变量定义

在C++中,变量的定义包括变量类型和变量名,可以在定义时进行初始化。

  • 基本语法
类型 变量名 = 初始值;

  • 示例
int i = 0;  // 定义一个整型变量并初始化为0
double d = 3.14;  // 定义一个双精度浮点型变量并初始化为3.14

变量初始化是为变量赋初值,可以在定义时进行。C++支持多种初始化方式,包括拷贝初始化、直接初始化和列表初始化。

拷贝初始化

拷贝初始化使用赋值运算符将初始值赋给变量。

  • 语法
int i = 0;
double d = 3.14;

直接初始化

直接初始化使用括号将初始值赋给变量。

  • 语法
int i(0);
double d(3.14);

列表初始化

列表初始化使用花括号将初始值赋给变量,C++11引入的这种方式可以防止窄化转换(narrowing conversion)。

  • 语法
int i{0};
double d{3.14};

2.2.2 变量声明和定义的关系

在C++中,声明和定义是两个不同的概念。理解变量的声明和定义之间的关系有助于编写更清晰和模块化的代码。

变量声明

变量声明告诉编译器变量的类型和名字,但不分配存储空间。声明通常出现在头文件中,以便在多个文件中共享变量。

  • 示例
extern int i;  // 声明一个整型变量i
extern double pi;  // 声明一个双精度浮点型变量pi
  • 注意extern关键字表示声明而非定义。声明告诉编译器变量已经定义在其他地方。

变量定义

变量定义不仅告诉编译器变量的类型和名字,还分配存储空间并可以初始化变量。定义通常出现在源文件中。

  • 示例
int i = 42;  // 定义一个整型变量i并初始化为42
double pi = 3.14159;  // 定义一个双精度浮点型变量pi并初始化为3.14159

声明和定义的区别
  • 声明
    • 告诉编译器变量的存在及其类型。
    • 不分配存储空间。
    • 使用extern关键字。
  • 定义
    • 告诉编译器变量的存在及其类型。
    • 分配存储空间。
    • 可以进行初始化。
  • 示例
// 声明
extern int x;  // 告诉编译器变量x存在且类型为int

// 定义
int x = 10;  // 告诉编译器变量x存在且类型为int,并分配存储空间和初始化

多文件程序中的声明和定义

在多文件程序中,变量的声明和定义通常在不同的文件中。通过声明和定义的分离,可以实现代码的模块化和重用。

  • 头文件(declarations.h)
extern int global_variable;  // 声明全局变量

  • 源文件1(file1.cpp)
#include "declarations.h"

int global_variable = 100;  // 定义全局变量

int main() {
    // 使用global_variable
    return 0;
}

  • 源文件2(file2.cpp)
#include "declarations.h"

void some_function() {
    // 使用global_variable
}

2.2.3 变量的作用域

变量的作用域是指变量在程序中可见并且可以被访问的区域。C++中的变量作用域分为全局作用域、局部作用域和块作用域。

全局作用域

在所有函数之外定义的变量具有全局作用域,可以在整个程序中访问。

  • 示例
int global_var = 100;  // 全局变量

int main() {
    std::cout << global_var << std::endl;  // 访问全局变量
    return 0;
}

局部作用域

在函数内部定义的变量具有局部作用域,只能在定义它的函数内部访问。

  • 示例
int main() {
    int local_var = 10;  // 局部变量
    std::cout << local_var << std::endl;  // 访问局部变量
    return 0;
}

块作用域

在复合语句(如if、for、while语句的花括号内)中定义的变量具有块作用域,只能在定义它的块内部访问。

  • 示例
int main() {
    if (true) {
        int block_var = 5;  // 块作用域变量
        std::cout << block_var << std::endl;  // 访问块作用域变量
    }
    // std::cout << block_var << std::endl;  // 错误,超出块作用域
    return 0;
}

2.2.4 变量的生命周期

变量的生命周期是指变量在内存中存在的时间段。变量的生命周期与它的作用域相关。

  • 全局变量:在程序开始时分配内存,程序结束时释放内存。
  • 局部变量:在函数调用时分配内存,函数返回时释放内存。
  • 块作用域变量:在块进入时分配内存,块退出时释放内存。

重点与难点分析

重点

  1. 变量的定义和初始化:掌握变量的定义和多种初始化方式,包括拷贝初始化、直接初始化和列表初始化。
  2. 理解声明和定义的区别:掌握声明和定义的不同之处,特别是在多文件程序中的作用。
  3. extern关键字:理解extern关键字的作用,用于声明变量而不定义。
  4. 变量的作用域:理解全局作用域、局部作用域和块作用域的概念和应用。

难点

  1. 列表初始化:理解列表初始化的优势,特别是防止窄化转换。
  2. 链接错误的排查:初学者容易在多文件程序中遇到链接错误,需要理解声明和定义的关系来解决。
  3. 模块化编程:通过声明和定义的分离,实现代码的模块化和重用。
  4. 变量的作用域和生命周期:初学者容易混淆不同作用域和生命周期的概念,需要通过实际编程练习加深理解。

练习题解析
  1. 练习2.7:定义一个局部变量和一个全局变量,并在程序中分别访问它们。
    • 示例代码:
#include <iostream>

int global_var = 100;  // 全局变量

int main() {
    int local_var = 10;  // 局部变量
    std::cout << "Global variable: " << global_var << std::endl;
    std::cout << "Local variable: " << local_var << std::endl;
    return 0;
}

  1. 练习2.8:定义一个块作用域变量,并尝试在块外部访问它,观察编译器报错。
    • 示例代码:
#include <iostream>

int main() {
    if (true) {
        int block_var = 5;  // 块作用域变量
        std::cout << "Block variable inside block: " << block_var << std::endl;
    }
    // std::cout << "Block variable outside block: " << block_var << std::endl;  // 错误,超出块作用域
    return 0;
}

  1. 练习2.9:定义一个const变量,并尝试修改它,观察编译器报错。
    • 示例代码:
#include <iostream>

int main() {
    const int max_size = 100;  // 常量变量
    // max_size = 200;  // 错误,常量不能被修改
    std::cout << "Constant variable: " << max_size << std::endl;
    return 0;
}

总结与提高

本节总结

  1. 学习了C++中的变量定义和初始化方法,包括拷贝初始化、直接初始化和列表初始化。
  2. 理解了变量的声明和定义的区别。
  3. 学习了extern关键字的作用,用于声明变量而不定义。
  4. 了解了在多文件程序中如何通过声明和定义分离来实现模块化编程。
  5. 理解了变量的作用域,包括全局作用域、局部作用域和块作用域。

提高建议

  1. 多练习变量的定义和初始化:通过编写各种变量定义和初始化的小程序,熟悉不同初始化方法的用法。
  2. 多练习声明和定义的使用:通过编写多文件程序,练习变量的声明和定义,熟悉extern关键字的用法。
  3. 理解链接错误的排查:通过故意制造和解决链接错误,深入理解声明和定义的关系。
  4. 模块化编程:通过声明和定义的分离,实现代码的模块化和重用,提高编程能力和代码质量。
  5. 深入理解作用域和生命周期:通过实际编程练习,理解不同作用域和生命周期的概念及其对变量的影响。

2.3 复合类型

概述

复合类型是基于基本类型构建的类型,包括引用和指针。复合类型的使用可以更灵活地操作数据和内存,掌握复合类型是编写C++程序的重要技能。

2.3.1 引用

引用是某个已存在变量的别名,通过引用可以访问或修改该变量的值。引用在定义时必须初始化,一旦绑定某个变量后不能再绑定其他变量。

定义引用
  • 语法
类型 &引用名 = 变量名;

  • 示例
int i = 42;
int &ref = i;  // 定义引用ref,绑定变量i

引用的使用
  • 修改引用所指向的变量
int i = 42;
int &ref = i;  // ref是i的引用
ref = 100;     // 修改ref即修改i
std::cout << i << std::endl;  // 输出100

  • 引用的特性
    • 引用在初始化时必须绑定变量,不能绑定字面值或常量。
    • 引用一旦绑定变量后,不能再绑定其他变量。

2.3.2 指针

指针是存储另一个变量地址的变量,通过指针可以间接访问或修改变量。指针在定义时可以不初始化,指向有效的内存地址后才能使用。

定义指针
  • 语法
类型 *指针名;

  • 示例
int i = 42;
int *p = &i;  // 定义指针p,指向变量i的地址

指针的使用
  • 访问指针所指向的变量
int i = 42;
int *p = &i;  // p是指向i的指针
*p = 100;     // 修改*p即修改i
std::cout << i << std::endl;  // 输出100

  • 指针的特性
    • 指针可以指向任何类型的变量,包括基本类型和复合类型。
    • 指针可以有多个级别,如指向指针的指针。

空指针和NULL指针
  • 空指针:空指针不指向任何对象,用于初始化指针或表示指针不指向有效地址。
int *p = nullptr;  // C++11引入的空指针表示法
int *p1 = 0;       // 传统的空指针表示法
int *p2 = NULL;    // 使用宏NULL定义的空指针

指针的运算

指针可以进行算术运算,如递增、递减和相减。

  • 指针递增递减
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;  // 指向数组的第一个元素
++p;           // p指向数组的第二个元素
std::cout << *p << std::endl;  // 输出2

  • 指针相减
int arr[] = {1, 2, 3, 4, 5};
int *p1 = arr;
int *p2 = arr + 4;
std::cout << p2 - p1 << std::endl;  // 输出4,表示p2和p1之间相隔4个元素

2.3.3 const限定符与指针和引用

const限定符可以用来修饰指针和引用,使其所指向的对象不可修改。

指向常量的指针

指向常量的指针不能修改其指向的对象。

  • 定义指向常量的指针
const int *p = &i;  // p是指向常量的指针,不能通过p修改i的值

  • 示例
int i = 42;
const int *p = &i;
// *p = 100;  // 错误,不能通过p修改i的值
i = 100;    // 可以直接修改i的值
std::cout << *p << std::endl;  // 输出100

常量指针

常量指针本身是常量,不能修改其指向的地址。

  • 定义常量指针
int *const p = &i;  // p是常量指针,必须初始化,且不能修改指向

  • 示例
int i = 42;
int *const p = &i;  // 常量指针
*p = 100;           // 可以通过p修改i的值
// int j = 10;
// p = &j;  // 错误,不能修改常量指针p的指向

指向常量的常量指针

指向常量的常量指针既不能修改指向的对象,也不能修改指针本身的指向。

  • 定义指向常量的常量指针
const int *const p = &i;  // p是指向常量的常量指针

  • 示例
int i = 42;
const int *const p = &i;
// *p = 100;  // 错误,不能通过p修改i的值
// p = &j;    // 错误,不能修改常量指针p的指向

重点与难点分析

重点

  1. 引用的定义和使用:理解引用的概念及其用途,掌握引用的定义和使用方法。
  2. 指针的定义和使用:掌握指针的定义和基本操作,特别是指针的运算和空指针的使用。
  3. const限定符的应用:理解const限定符与指针和引用的组合使用方法,掌握其在保护数据中的作用。

难点

  1. 指针运算:初学者容易混淆指针的递增、递减和相减操作,需要通过练习掌握。
  2. const限定符的组合使用:理解不同组合的const限定符与指针和引用的作用及其实际应用。

练习题解析
  1. 练习2.12:定义一个引用和一个指针,分别修改它们所指向的变量的值。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    int &ref = i;  // 定义引用
    int *p = &i;   // 定义指针

    ref = 100;     // 修改引用所指向的变量
    std::cout << "i after ref: " << i << std::endl;

    *p = 200;      // 修改指针所指向的变量
    std::cout << "i after p: " << i << std::endl;

    return 0;
}

  1. 练习2.13:定义一个指向常量的指针和一个常量指针,尝试修改它们的指向和指向的值。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    const int *p1 = &i;  // 指向常量的指针
    int *const p2 = &i;  // 常量指针

    // *p1 = 100;  // 错误,不能通过p1修改i的值
    i = 100;     // 可以直接修改i的值
    std::cout << "i after p1: " << *p1 << std::endl;

    *p2 = 200;   // 可以通过p2修改i的值
    // int j = 300;
    // p2 = &j;  // 错误,不能修改常量指针p2的指向
    std::cout << "i after p2: " << *p2 << std::endl;

    return 0;
}

  1. 练习2.14:定义一个指向常量的常量指针,并尝试修改其指向和指向的值。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    const int *const p = &i;  // 指向常量的常量指针

    // *p = 100;  // 错误,不能通过p修改i的值
    // int j = 200;
    // p = &j;    // 错误,不能修改常量指针p的指向

    std::cout << "i: " << *p << std::endl;
    return 0;
}

总结与提高

本节总结

  1. 了解了引用的定义和使用方法,理解引用的基本概念。
  2. 学习了指针的定义和使用方法,掌握指针的基本操作和空指针的使用。
  3. 掌握了const限定符与指针和引用的组合使用方法,理解其在数据保护中的作用。

提高建议

  1. 多练习引用和指针的使用:通过编写各种引用和指针的小程序,熟悉它们的定义和操作。
  2. 深入理解指针运算:通过实际编程练习,掌握指针的递增、递减和相减操作。
  3. 应用const限定符:在程序中尽量使用const限定符保护数据,提高程序的安全性和可读性。

2.4 const限定符

概述

const关键字用于定义常量,它可以修饰变量、指针和函数,表示这些元素的值在程序运行期间不可改变。const限定符的合理使用可以提高程序的安全性和可读性。

2.4.1 定义常量

常量是值不能改变的变量,用const关键字修饰。

  • 基本语法
const 类型 变量名 = 初始值;

  • 示例
const int max_value = 100;  // 定义一个整型常量

常量初始化

常量在定义时必须进行初始化,因为之后不能修改其值。

  • 示例
const double pi = 3.14159;  // 定义并初始化常量pi

2.4.2 const引用

const引用是引用常量值的引用,不能通过const引用修改其绑定的对象。

  • 定义const引用
const 类型 &引用名 = 变量名;

  • 示例
int i = 42;
const int &ref = i;  // 定义一个const引用
// ref = 100;  // 错误,不能通过const引用修改i的值
i = 100;  // 可以直接修改i的值
std::cout << ref << std::endl;  // 输出100

const引用绑定字面值

const引用可以绑定到字面值、常量和不同类型的对象上。

  • 示例
const int &ref = 42;  // const引用绑定字面值
const double &ref2 = 3.14;  // const引用绑定浮点字面值

2.4.3 const指针

指针与const结合可以有不同的含义,取决于const在声明中的位置。

指向常量的指针

指向常量的指针不能通过该指针修改指向的对象。

  • 定义指向常量的指针
const 类型 *指针名;

  • 示例
int i = 42;
const int *p = &i;  // 定义指向常量的指针
// *p = 100;  // 错误,不能通过p修改i的值
i = 100;  // 可以直接修改i的值

常量指针

常量指针本身是常量,不能修改其指向的地址。

  • 定义常量指针
类型 *const 指针名;

  • 示例
int i = 42;
int *const p = &i;  // 定义常量指针
*p = 100;  // 可以通过p修改i的值
// int j = 10;
// p = &j;  // 错误,不能修改常量指针的指向

指向常量的常量指针

指向常量的常量指针既不能修改指向的对象,也不能修改指针本身的指向。

  • 定义指向常量的常量指针
const 类型 *const 指针名;

  • 示例
int i = 42;
const int *const p = &i;  // 定义指向常量的常量指针
// *p = 100;  // 错误,不能通过p修改i的值
// int j = 10;
// p = &j;  // 错误,不能修改指针p的指向

2.4.4 顶层const与底层const

const在C++中有顶层const和底层const的区别。

顶层const

顶层const表示指针本身是常量,或者是常量对象。

  • 示例
const int ci = 42;  // ci是顶层const
int *const p = &i;  // p是顶层const

底层const

底层const表示指针或引用所指向的对象是常量。

  • 示例
const int *p = &ci;  // p是底层const
const int &ref = ci;  // ref是底层const

2.4.5 constexpr和常量表达式

C++11引入了constexpr关键字,用于表示常量表达式。常量表达式是在编译时就能求值的表达式,使用constexpr可以提高程序的执行效率和安全性。

常量表达式

常量表达式是在编译时就能求值的表达式。常量表达式可以是字面值、常量、以及由常量表达式构成的表达式。

  • 示例
constexpr int size = 100;  // 常量表达式

constexpr和函数

constexpr可以用于函数声明,表示该函数是一个常量表达式函数,返回值可以在编译时求值。constexpr函数必须满足以下条件:

  • 函数体中只能包含单一的return语句或一系列能在编译时求值的表达式。
  • 所有参数和返回类型必须是字面值类型。
  • 示例
constexpr int square(int x) {
    return x * x;
}

constexpr int result = square(10);  // result在编译时计算

constexpr和const的区别
  • const:定义在运行时不变的常量,不能修改其值。
  • constexpr:定义在编译时求值的常量表达式,常用于编译时常量计算和优化。
  • 示例
const int const_var = 100;  // 运行时常量
constexpr int constexpr_var = 200;  // 编译时常量

重点与难点分析

重点

  1. 定义和使用常量:掌握const关键字的基本用法,理解常量的定义和初始化方法。
  2. const引用和指针:了解const引用和指针的定义及使用,理解其在保护数据中的作用。
  3. 顶层const与底层const:区分顶层const和底层const的概念及其应用。
  4. 理解constexpr的用法:掌握如何定义constexpr变量和constexpr函数,理解它们在编译时求值的特性。
  5. 使用constexpr提高性能:通过使用constexpr关键字,可以在编译时计算常量表达式,提高程序的执行效率。

难点

  1. const指针的组合使用:初学者容易混淆指向常量的指针、常量指针和指向常量的常量指针,需要通过练习掌握。
  2. 顶层const与底层const的区分:理解顶层const和底层const的区别及其在实际编程中的应用。
  3. constexpr函数的定义:确保constexpr函数的所有参数和返回类型都是字面值类型,并且函数体中只能包含能在编译时求值的表达式。
  4. constexprconst的区别:理解constexprconst的不同应用场景和各自的优缺点。

练习题解析
  1. 练习2.15:定义一个const引用和一个const指针,尝试修改它们所指向的变量的值。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    const int &ref = i;  // 定义const引用
    const int *p = &i;  // 定义指向常量的指针

    // ref = 100;  // 错误,不能通过const引用修改i的值
    // *p = 100;  // 错误,不能通过指向常量的指针修改i的值
    i = 100;  // 可以直接修改i的值

    std::cout << "ref: " << ref << std::endl;
    std::cout << "*p: " << *p << std::endl;
    return 0;
}

  1. 练习2.16:定义一个常量指针和一个指向常量的常量指针,尝试修改它们的指向和指向的值。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    int j = 10;
    int *const p1 = &i;  // 定义常量指针
    const int *const p2 = &i;  // 定义指向常量的常量指针

    *p1 = 100;  // 可以通过常量指针修改i的值
    // p1 = &j;  // 错误,不能修改常量指针的指向
    
    // *p2 = 200;  // 错误,不能通过指向常量的常量指针修改i的值
    // p2 = &j;  // 错误,不能修改指向常量的常量指针的指向

    std::cout << "i: " << i << std::endl;
    std::cout << "*p1: " << *p1 << std::endl;
    std::cout << "*p2: " << *p2 << std::endl;
    return 0;
}

  1. 练习2.17:定义一个顶层const变量和一个底层const指针,理解其用法。
    • 示例代码:
#include <iostream>

int main() {
    const int ci = 42;  // 顶层const
    const int *p = &ci;  // 底层const指针

    // ci = 100;  // 错误,不能修改顶层const变量的值
    // *p = 100;  // 错误,不能通过底层const指针修改ci的值
    
    std::cout << "ci: " << ci << std::endl;
    std::cout << "*p: " << *p << std::endl;
    return 0;
}

  1. 练习2.18:定义一个constexpr函数,计算两个整数的和,并在编译时求值。
    • 示例代码:
#include <iostream>

constexpr int add(int a, int b) {
    return a + b;
}

int main() {
    constexpr int result = add(10, 20);  // result在编译时计算
    std::cout << "Result: " << result << std::endl;
    return 0;
}

  1. 练习2.19:比较constconstexpr的用法,定义运行时常量和编译时常量。
    • 示例代码:
#include <iostream>

const int run_time_const = 100;  // 运行时常量
constexpr int compile_time_const = 200;  // 编译时常量

int main() {
    std::cout << "Runtime const: " << run_time_const << std::endl;
    std::cout << "Compiletime const: " << compile_time_const << std::endl;
    return 0;
}

总结与提高

本节总结

  1. 学习了const关键字的用法,理解了常量的定义和初始化方法。
  2. 掌握了const引用和指针的定义及其在保护数据中的作用。
  3. 了解了顶层const和底层const的区别及其应用。
  4. 学习了constexpr关键字的用法,理解了常量表达式的概念及其在编译时求值的特性。
  5. 掌握了如何定义constexpr变量和constexpr函数,理解它们在提高程序性能中的作用。

提高建议

  1. 多练习const的使用:通过编写各种const引用和指针的小程序,熟悉它们的定义和操作。
  2. 深入理解顶层const与底层const:通过实际编程练习,掌握顶层const和底层const的区别及其在实际应用中的作用。
  3. 应用const保护数据:在程序中尽量使用const关键字保护数据,提高程序的安全性和可读性。
  4. 多练习constexpr的使用:通过编写各种constexpr变量和函数的小程序,熟悉它们的定义和操作。
  5. 理解编译时常量和运行时常量的区别:通过实际编程练习,掌握constexprconst的不同应用场景及其优缺点。
  6. 优化程序性能:在实际编程中,尽量使用constexpr关键字进行编译时计算和优化,提高程序的执行效率和安全性。

2.5 处理类型

概述

本小节介绍C++中的类型处理方法,包括类型别名、autodecltype关键字。通过这些工具,可以简化代码,提高可读性,并在编译时确定变量类型。

2.5.1 类型别名

类型别名用于为现有类型创建新的名称,可以使用typedefusing关键字。

使用typedef定义类型别名

typedef关键字用于定义类型别名,使代码更易读和维护。

  • 语法
typedef 原类型 新类型;

  • 示例
typedef double wages;  // 定义wages为double的别名
typedef wages base, *p;  // base是double的别名,p是指向double的指针类型

使用using定义类型别名

C++11引入了using关键字,可以替代typedef,更直观易读。

  • 语法
using 新类型 = 原类型;

  • 示例
using wages = double;  // 定义wages为double的别名
using base = wages;    // base是wages的别名,即double的别名
using p = base*;       // p是指向base的指针类型

2.5.2 auto类型说明符

auto关键字用于自动推导变量的类型,编译器根据初始值的类型推断变量的类型。

使用auto定义变量
  • 语法
auto 变量名 = 初始值;

  • 示例
auto i = 42;  // i被推断为int类型
auto d = 3.14;  // d被推断为double类型

使用auto简化代码

auto可以简化复杂类型的定义,特别是在使用模板和迭代器时。

  • 示例
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << std::endl;
}

2.5.3 decltype类型指示符

decltype关键字用于查询表达式的类型,编译器分析表达式并推断其类型。

使用decltype获取类型
  • 语法
decltype(表达式) 变量名 = 初始值;

  • 示例
int i = 42;
decltype(i) j = i;  // j的类型被推断为int

使用decltype获取函数返回类型

decltype可以用来声明函数的返回类型,特别是在返回类型依赖于参数类型时。

  • 示例
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
    return a + b;
}

重点与难点分析

重点

  1. 类型别名的定义和使用:掌握typedefusing的用法,理解它们在代码简化和可读性提高中的作用。
  2. auto关键字:学习如何使用auto自动推导变量类型,简化代码。
  3. decltype关键字:理解decltype的用法,掌握如何获取表达式的类型。

难点

  1. autodecltype的实际应用:初学者需要通过实际编程练习,理解和掌握autodecltype的使用场景和技巧。

练习题解析
  1. 练习2.21:使用typedefusing分别定义类型别名,并使用这些别名定义变量。
    • 示例代码:
#include <iostream>

typedef double wages_t;  // 使用typedef定义类型别名
using salary_t = double;  // 使用using定义类型别名

int main() {
    wages_t hourly_wage = 20.5;
    salary_t annual_salary = 50000.0;

    std::cout << "Hourly Wage: " << hourly_wage << std::endl;
    std::cout << "Annual Salary: " << annual_salary << std::endl;
    return 0;
}

  1. 练习2.22:编写一个使用auto关键字推导变量类型的示例程序。
    • 示例代码:
#include <iostream>
#include <vector>

int main() {
    auto i = 42;  // 自动推导类型为int
    auto d = 3.14;  // 自动推导类型为double
    std::vector<int> vec = {1, 2, 3, 4, 5};

    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

  1. 练习2.23:编写一个使用decltype关键字推导表达式类型的示例程序。
    • 示例代码:
#include <iostream>

int main() {
    int i = 42;
    decltype(i) j = i;  // j的类型被推导为int

    auto add = [](decltype(i) a, decltype(i) b) -> decltype(a + b) {
        return a + b;
    };

    std::cout << "j: " << j << std::endl;
    std::cout << "add(i, j): " << add(i, j) << std::endl;

    return 0;
}

总结与提高

本节总结

  1. 了解了类型别名的定义和使用方法,掌握了typedefusing关键字。
  2. 学习了auto关键字的用法,理解了自动推导变量类型的优势。
  3. 掌握了decltype关键字的用法,能够获取表达式的类型。

提高建议

  1. 多练习类型别名的使用:通过编写各种类型别名的小程序,熟悉typedefusing的定义和应用。
  2. 深入理解autodecltype:通过实际编程练习,掌握autodecltype在不同场景中的应用和优

2.6 自定义数据结构

概述

本小节介绍如何在C++中定义和使用自定义数据结构。自定义数据结构可以帮助我们更有效地组织和管理复杂的数据。C++中常用的自定义数据结构包括结构体(struct)和联合体(union)。

2.6.1 结构体(struct)

结构体是一种将不同类型的数据组合在一起的复合类型。结构体成员可以是基本类型、数组、指针、甚至是其他结构体。

定义和使用结构体
  • 定义结构体
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

  • 使用结构体
int main() {
    Sales_data book1;
    book1.bookNo = "978-0590353403";
    book1.units_sold = 25;
    book1.revenue = book1.units_sold * 20.5;

    std::cout << "Book No: " << book1.bookNo << std::endl;
    std::cout << "Units Sold: " << book1.units_sold << std::endl;
    std::cout << "Revenue: " << book1.revenue << std::endl;

    return 0;
}

结构体的初始化

可以在定义结构体变量时进行初始化。

  • 列表初始化
Sales_data book2 = {"978-0590353403", 30, 615.0};

  • 默认初始化
Sales_data book3;  // book3的成员会被默认初始化

结构体的成员访问

结构体的成员通过点操作符(.)访问和修改。

  • 访问和修改成员
book1.units_sold = 50;
double avg_price = book1.revenue / book1.units_sold;

2.6.2 联合体(union)

联合体是一种节省空间的类类型,它允许其成员共享同一块内存。联合体一次只能存储一个成员的值。

定义和使用联合体
  • 定义联合体
union Token {
    char cval;
    int ival;
    double dval;
};

  • 使用联合体
int main() {
    Token t;
    t.cval = 'c';
    std::cout << "Token as char: " << t.cval << std::endl;

    t.ival = 42;
    std::cout << "Token as int: " << t.ival << std::endl;

    t.dval = 3.14;
    std::cout << "Token as double: " << t.dval << std::endl;

    return 0;
}

联合体的成员访问

联合体的成员通过点操作符访问和修改。需要注意的是,联合体的不同成员共享同一块内存,因此同时访问多个成员是不安全的。

  • 访问和修改成员
t.ival = 10;
t.dval = 20.5;  // t.ival的值可能会被覆盖

2.6.3 枚举类型

枚举类型是用户定义的一种类型,它由一组命名的整型常量组成。枚举类型提高了代码的可读性和可维护性。

定义和使用枚举类型
  • 定义枚举类型
enum class Color { Red, Green, Blue };

  • 使用枚举类型
int main() {
    Color color = Color::Red;

    switch (color) {
    case Color::Red:
        std::cout << "Red" << std::endl;
        break;
    case Color::Green:
        std::cout << "Green" << std::endl;
        break;
    case Color::Blue:
        std::cout << "Blue" << std::endl;
        break;
    }

    return 0;
}

枚举类型的作用域

C++11引入了强作用域枚举(enum class),它们的枚举值不会被提升到外层作用域。

  • 示例
enum class Color { Red, Green, Blue };
Color c = Color::Red;
// int n = Color::Red;  // 错误,Color::Red没有提升到外层作用域

重点与难点分析

重点

  1. 结构体的定义和使用:掌握结构体的基本概念和用法,包括定义、初始化和成员访问。
  2. 联合体的定义和使用:了解联合体的特点和用法,理解其节省空间的优势。
  3. 枚举类型的定义和使用:掌握枚举类型的基本概念和用法,特别是强作用域枚举(enum class)。

难点

  1. 联合体的内存共享:理解联合体成员共享同一块内存的特性,并注意避免同时访问多个成员。
  2. 强作用域枚举的使用:掌握enum class的定义和使用,理解其作用域规则。
练习题解析
  1. 练习2.24:定义一个结构体表示书籍信息,并编写程序输出书籍的详细信息。
    • 示例代码:
#include <iostream>
#include <string>

struct Book {
    std::string title;
    std::string author;
    double price;
};

int main() {
    Book book = {"The C++ Programming Language", "Bjarne Stroustrup", 59.99};

    std::cout << "Title: " << book.title << std::endl;
    std::cout << "Author: " << book.author << std::endl;
    std::cout << "Price: $" << book.price << std::endl;

    return 0;
}

  1. 练习2.25:定义一个联合体存储不同类型的数据,并编写程序分别输出这些数据。
    • 示例代码:
#include <iostream>

union Data {
    int ival;
    float fval;
    char cval;
};

int main() {
    Data data;
    data.ival = 42;
    std::cout << "Data as int: " << data.ival << std::endl;

    data.fval = 3.14f;
    std::cout << "Data as float: " << data.fval << std::endl;

    data.cval = 'c';
    std::cout << "Data as char: " << data.cval << std::endl;

    return 0;
}

  1. 练习2.26:定义一个枚举类型表示颜色,并编写程序输出对应的颜色名称。
    • 示例代码:
#include <iostream>

enum class Color { Red, Green, Blue };

int main() {
    Color color = Color::Green;

    switch (color) {
    case Color::Red:
        std::cout << "Red" << std::endl;
        break;
    case Color::Green:
        std::cout << "Green" << std::endl;
        break;
    case Color::Blue:
        std::cout << "Blue" << std::endl;
        break;
    }

    return 0;
}

总结与提高

本节总结

  1. 学习了结构体的定义和使用方法,理解了结构体在组织和管理复杂数据中的作用。
  2. 掌握了联合体的特点和用法,理解了其节省空间的优势。
  3. 了解了枚举类型的定义和使用方法,特别是强作用域枚举(enum class)的优势。

提高建议

  1. 多练习结构体和联合体的使用:通过编写各种结构体和联合体的小程序,熟悉它们的定义和操作。
  2. 深入理解联合体的内存共享特性:通过实际编程练习,掌握联合体成员共享内存的特性,注意避免同时访问多个成员。
  3. 应用枚举类型提高代码可读性:在程序中使用枚举类型表示有限的状态或选项,提高代码的可读性和可维护性。

本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVS三种负载均衡模式:NAT、Tunneling和DR的技术对比

1. LVS-NAT 模式的特性 IP使用&#xff1a;RS&#xff08;Real Server&#xff09;应使用私有地址&#xff0c;RS的网关必须指向DIP&#xff08;Director IP&#xff09;。网络范围&#xff1a;DIP和RIP必须在同一个网段内。数据包处理&#xff1a;请求和响应报文都需要经过Di…

【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常

【问题分类】驱动使用 【关键字】OCI、驱动使用、PHP 【问题描述】 PHP使用OCI8连接yashan数据库&#xff0c;使用绑定参数获取数据时&#xff0c;出现报错 如果使用PDO_OCI接口连接数据库&#xff0c;未弹出异常&#xff0c;但是无法正确获取数据 【问题原因分析】 开启O…

远程桌面失败:你的凭据不工作

远程桌面失败&#xff1a;你的凭据不工作 远程桌面失败&#xff1a;你的凭据不工作_您的凭据不工作-CSDN博客https://blog.csdn.net/weixin_38004638/article/details/82290796

java设计模式和面向对象编程思想

Java设计模式和面向对象编程思想是软件开发中的核心概念&#xff0c;对于构建可维护、可扩展的软件系统至关重要。下面是对这两个主题的知识点总结&#xff1a; 面向对象编程&#xff08;OOP&#xff09;思想 封装&#xff1a;将数据&#xff08;属性&#xff09;和操作这些数据…

Linux C编译器从零开发一

基础程序汇编 test.c int main() {return 42; } 查看反汇编 cc -o test test.c objdump -d -M intel test 0000000000001129 <main>:1129: f3 0f 1e fa endbr64 112d: 55 push rbp112e: 48 89 e5 mov rbp,rsp1131: b…

攻防世界-fakebook题目__详解

1.打开题目先用dirsearch工具扫描一波&#xff0c;扫出来了robots.php目录&#xff0c;然后访问robots.txt 目录&#xff0c;发现了有一个备份文件 &#xff0c;访问备份文件&#xff0c;下载内容 文件的大致内容如下 里面有一个curl_exec这个函数容易造成ssrf攻击的漏洞 我…

运行时类型识别RTTI(typeid dynamic_cast)和虚函数机制的关系

1.typeid 2.dynamic_cast 指针类型决定了可以操作的内存范围大小 子类指针转化为父类类型的指针的一般是合法的&#xff1a; 父类的指针类型转化为子类类型指针&#xff0c;超过合法操作范围&#xff0c;不安全 两种转换&#xff1a;编译期的转换&#xff0c;运行时的转化 编译…

四川汇聚荣聚荣科技有限公司是干什么的,拼多多运营如何做?

四川汇聚荣聚荣科技有限公司是干什么的&#xff0c;拼多多运营如何做?随着电商行业的快速发展&#xff0c;越来越多的企业开始涉足这一领域。其中&#xff0c;四川汇聚荣聚荣科技有限公司便是其中的一员。那么&#xff0c;这家公司究竟是做什么的呢?简单来说&#xff0c;它是…

【代码随想录】【算法训练营】【第35天】[134]加油站 [135]分发糖果 [860]柠檬水找零 [406]根据身高重建队列

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 35&#xff0c;连休两天~ 题目详情 [134] 加油站 题目描述 134 加油站 解题思路 前提&#xff1a;数组 思路&#xff1a;全局贪心算法&#xff1a;最小累加剩余汽油为负数&#xff0c;说明…

短视频矩阵系统源码搭建--如何基于各平台原生态坏境做开发

短视频矩阵系统源码搭建是一个涉及多个技术层面的复杂过程&#xff0c;它要求开发者能够理解并利用不同平台的原生环境来开发和部署应用程序。以下是一些基于不同平台原生环境开发短视频矩阵系统的一般步骤和考虑因素&#xff1a; 1.需求分析&#xff1a;首先明确系统需要实现的…

Python学习笔记9:入门知识(九)

缩进 什么是缩进&#xff1f; 缩进&#xff0c;简单的理解为本行的首字符相比上一行的首字符位置相对靠后。目前笔者接触的编程语言缩进一般是4字符&#xff0c;直接可以按tab键就行。 为什么突然讲缩进&#xff1f; Python这门语言&#xff0c;是依靠缩进来判断当前行与上…

ISP图像算法面试准备(1)

ISP图像算法面试准备 ISP图像算法面试准备(1) 文章目录 ISP图像算法面试准备前言一、ISP流程二、重点关注1. AWB必须在Demosaic之后进行。2. Gamma矫正通常在CCM之前进行 三、如何实现ISP参数自动化调试四、AE&#xff0c;即自动曝光&#xff08;Auto Exposure&#xff09;总结…

【深度学习】基于EANet模型的图像识别和分类技术

1.引言 1.1.EANet模型简介 EANet&#xff08;External Attention Transformer&#xff09;是一种深度学习模型&#xff0c;它结合了Transformer架构和外部注意力机制&#xff0c;特别适用于图像分类等计算机视觉任务。以下是关于EANet的详细解释&#xff1a; 1.1.1 定义与背…

Qt项目天气预报(3) - qt的http编程获取天气数据

概念 Qt中的HTTP编程主要涉及使用Qt的网络模块来进行HTTP请求和处理HTTP响应。Qt提供了一系列类来处理网络通信&#xff0c;其中最常用的类是 QNetworkAccessManager 、 QNetworkRequest 、 QNetworkReply 以及相关的支持类。 编程实例 以下是一个基本的HTTP编程示例&#xff0…

优雅谈大模型12:一文读懂LoRA/DoRA/MoRA

Microsoft于2021年推出的LoRA是一种经济型微调模型参数的方法。现在大模型的参数规模动不动都在10亿级别以上&#xff0c;微调大模型&#xff08;微调这里代表着SFT&#xff0c;例如读者将某个大模型拿到自身领域&#xff0c;想使用自身领域的知识再次训练和精校大模型&#xf…

Gradle实现类似Maven的profiles功能

版本说明 GraalVM JDK 21.0.3Gradle 8.7Spring Boot 3.2.5 目录结构 指定环境打包 application.yml/yaml/properties 执行 bootJar 打包命令前要先执行 clean【其它和 processResources 相关的命令也要先执行 clean】&#xff0c;否则 active 值不会变&#xff01; spring…

计算机网络:网络层 - IP数据报的转发

计算机网络&#xff1a;网络层 - IP数据报的转发 基于终点转发最长前缀匹配二叉线索树路由表特殊路由特定主机路由默认路由 IP多播 基于终点转发 路由器转发报文时&#xff0c;是通过报文中的目的地址字段来转发的&#xff0c;也即是说路由器只知道终点的IP地址&#xff0c;根…

一种新的一维时间序列信号盲解卷积算法(以旋转机械故障诊断为例,MATLAB环境)

一种新的一维时间序列信号盲解卷积算法&#xff08;以旋转机械故障诊断为例&#xff0c;MATLAB环境&#xff09;&#xff0c;可作为深度学习信号前处理过程&#xff0c;水个SCI不是问题。 机械设备的状态信号中往往蕴含着大量的设备异常信息。如何从繁多的机械状态信号中提取足…

每日一练:攻防世界:ewm

这道题我尝试了使用montagegaps解题&#xff0c;但是没有解出来&#xff0c;图片数量不是很多&#xff0c;可以尝试用PS直接拼图&#xff0c;但是这样学不到东西&#xff0c;我也就没尝试&#xff0c;直接看的官方WP 这段代码应该是改变工作目录到small&#xff0c;并且变量当…

第九届星华杯网络邀请赛

T1喵星人的身高 T2犇犇碑 T3嘤嘤词典 T4三角区间和