二、从C语言到C++(二)

news/2024/7/7 18:29:42

二、从C语言到C++(二)

  • `bool` 类型
    • 怎么打印 `bool` 类型的值
  • 强弱类型
    • C语言的类型系统
    • C++的类型系统
    • 总结
  • `NULL` 和 `nullptr`
    • `NULL`
    • `nullptr`
    • 示例
  • 起别名
    • 使用 `typedef`
    • 使用 `using` 关键字(C++11及以后)
    • 注意
  • ` void*` 万能指针
    • C语言中的 `void*`
    • C++中的 `void*`
    • 注意事项
  • `const`
    • C语言中的 `const`(冒牌货)
    • C++中的 `const`(真货)
    • `const` 的区别
    • 总结

bool 类型

在C语言中,bool 类型并不是内置的数据类型,直到C99标准才引入了 _Bool 类型作为整数类型的一个扩展,并提供了宏 bool 作为 _Bool 的别名,以及 truefalse 作为宏定义,通常用于 <stdbool.h> 头文件中。但是,由于C语言对 _Bool 的处理本质上还是基于整型的,所以其使用并不像C++中的 bool 类型那样直观和严格。

在C++中,bool 是一个内置的数据类型,它只有两个可能的值:truefalsebool 类型常用于条件语句、循环语句以及逻辑运算中。C++的 bool 类型是一个真正的布尔类型,它不能隐式地转换为其他类型(除了 int 和可以接受 int 的其他类型),除非进行了显式的类型转换。

以下是一个C++中使用 bool 类型的简单示例:

#include <iostream>

bool isEven(int number) {
    return number % 2 == 0;
}

int main() {
    int num = 10;
    bool result = isEven(num);

    if (result) {
        std::cout << num << " is even." << std::endl;
    } else {
        std::cout << num << " is odd." << std::endl;
    }

    return 0;
}

在这个示例中,isEven 函数接受一个整数作为参数,并返回一个 bool 类型的值来表示该数是否是偶数。在 main 函数中,我们调用 isEven 函数,并将结果存储在 bool 类型的变量 result 中。然后,我们使用 if 语句根据 result 的值来输出相应的消息。

需要注意的是,在C++中,truefalsebool 类型的常量,它们的值分别为1和0(或者可以视为“真”和“假”)。但是,你不能将 bool 类型的变量直接与整数进行算术运算,因为它们是不同的类型。如果你需要这样做,你需要进行显式的类型转换。

怎么打印 bool 类型的值

在C++中,如果你想以文本形式(即 “true” 或 “false”)而不是整数形式(即 1 或 0)打印 bool 类型的值,你可以使用 std::boolalpha 操纵符。这个操纵符是定义在 <iomanip> 头文件中的,它可以改变流(通常是 std::cout)对于 bool 类型的输出方式。

以下是一个例子,展示了如何使用 std::boolalpha 来打印 bool 类型的值:

#include <iostream>
#include <iomanip> // 包含 boolalpha 操纵符的头文件

int main() {
    bool flag = true;

    // 使用 std::boolalpha 操纵符
    std::cout << std::boolalpha << flag << std::endl; // 输出 "true"

    // 如果你之后想恢复默认的整数输出方式
    std::cout << std::noboolalpha << flag << std::endl; // 输出 1

    return 0;
}

在这个例子中,std::boolalpha 使得 std::coutbool 类型的值作为 “true” 或 “false” 输出。如果你想恢复到默认的整数输出方式(即 true 输出为 1,false 输出为 0),你可以使用 std::noboolalpha 操纵符。不过,请注意,这些操纵符只影响它们被设置之后的输出,不会影响之前的输出。所以,如果你只想改变一个 bool 值的输出方式,而不需要后续恢复,你可以只使用 std::boolalpha

强弱类型

从C语言到C++,关于强弱类型的概念,首先需要明确的是,C和C++在类型系统方面都是静态类型语言,这意味着在编译时就需要确定变量的类型,并且这个类型在程序运行过程中通常是不能改变的。然而,这里的“强弱类型”通常指的是类型系统对程序员施加的约束程度和防止错误的能力。

C语言的类型系统

  • 弱类型检查:C语言的类型系统可以被认为是相对“弱”的,因为它允许程序员进行某些可能导致问题的类型转换。例如,C风格的强制类型转换 (Type) expression 可以几乎无限制地将任何类型的表达式转换为任何类型,而不进行太多的运行时检查。
  • 隐式转换:C语言中存在许多隐式类型转换,这些转换可能在程序员不注意的情况下发生,并导致难以察觉的错误。
  • 指针操作:C语言中的指针可以指向任何类型的数据,这增加了灵活性但也带来了风险。例如,将一个整数指针错误地解释为字符指针可能会导致未定义行为。

C++的类型系统

  • 强类型检查:C++在C语言的基础上增加了更严格的类型检查。虽然C++也允许强制类型转换,但它提供了更安全的替代方案,如static_castdynamic_castconst_castreinterpret_cast。这些转换函数要求程序员明确指定转换的意图,并在可能的情况下进行运行时检查。
  • 减少隐式转换:C++尝试减少隐式类型转换的数量,以减少错误的可能性。然而,由于C++需要与C语言兼容,因此仍然存在一些隐式转换。
  • 面向对象编程:C++支持面向对象编程,这包括类、继承、多态等概念。这些特性使得C++的类型系统更加复杂,但也更加灵活和强大。通过封装、继承和多态,C++可以提供更好的代码重用性和可维护性。
  • 模板和STL:C++的模板和STL(Standard Template Library)提供了类型安全的容器和数据结构,这些容器和数据结构在编译时进行类型检查,从而减少了运行时错误的可能性。

总结

  • 从C语言到C++,类型系统的“强弱”主要体现在对程序员施加的约束程度和防止错误的能力上。
  • C语言的类型系统相对较弱,允许更多的灵活性和隐式转换,但也增加了出错的可能性。
  • C++通过引入更严格的类型检查和转换函数、减少隐式转换、支持面向对象编程以及提供类型安全的容器和数据结构等方式,增强了其类型系统的“强度”,从而提高了代码的安全性和可维护性。

NULLnullptr

在C和C++中,NULLnullptr 都用于表示指针不指向任何有效的内存地址(空指针)。然而,它们在定义和使用上存在一些区别。

NULL

  • 在C语言中NULL 是一个宏,通常被定义为 (void*)00。它用于表示指针不指向任何有效的内存地址。
  • 在C++中:虽然C++是从C发展而来的,但它也支持 NULL。然而,在C++中,直接将 NULL 定义为 (void*)0 可能会导致类型安全的问题,因为当你尝试将一个 void* 类型的值赋给一个非 void* 类型的指针时,编译器可能会发出警告或错误。因此,C++标准库通常将 NULL 定义为 0((void*)0) 的一个类型安全的替代品,比如 #define NULL 0

nullptr

  • 在C++11及以后nullptr 是一个关键字,用于替换 NULL。它是C++11标准中引入的一个新特性,用于表示空指针。与 NULL 相比,nullptr 具有更好的类型安全性,因为它不是宏,而是一个真正的类型(std::nullptr_t),可以自动转换为任何指针类型或指针到成员的类型,但不能转换为整数类型。
  • 优点:使用 nullptr 可以避免由于 NULL 被错误地定义为 (void*)0 而导致的类型不匹配问题。此外,由于 nullptr 是一个关键字,它在代码中的使用也更清晰,更易于阅读和理解。

示例

在C中,你可能会这样使用 NULL

int *ptr = NULL;

在C++中,你可以继续使用 NULL(尽管它可能已经被定义为 0),但更好的做法是使用 nullptr

int *ptr = nullptr;

注意:在C++中,如果你尝试将 nullptr 赋值给一个非指针类型的变量,编译器会报错,这有助于在编译时捕获潜在的错误。而如果你使用 NULL 并将其定义为 0,这种错误可能不会被捕获。

起别名

在C++中,起别名(aliasing)通常指的是为一个类型或对象创建另一个名称,这样你就可以通过不同的名称来引用相同的类型或对象。在C++中,有几种方式可以实现这一点,但最常用的可能是使用typedefusing关键字(从C++11开始)。

使用 typedef

在C和C++中,typedef关键字被用来为现有类型定义一个新的名称。这在处理复杂的数据类型时特别有用,比如结构体、联合体、函数指针等。

// 定义一个结构体
struct Point {
    int x;
    int y;
};

// 使用typedef为结构体起别名
typedef struct Point PointAlias;

// 现在Point和PointAlias是等价的
PointAlias p;
p.x = 10;
p.y = 20;

使用 using 关键字(C++11及以后)

在C++11及以后的版本中,using关键字被引入作为一种更简洁、更灵活的别名机制。它可以用于类型、模板、命名空间等。

// 使用using为结构体起别名
using PointAlias = Point;

// 现在Point和PointAlias是等价的
PointAlias p;
p.x = 10;
p.y = 20;

// 对于模板类型,using也特别有用
template<typename T>
using Vector = std::vector<T>;

// 现在可以这样使用
Vector<int> int_vector;

注意

  • 别名并不创建新的类型,它只是为现有类型提供了一个新的名称。
  • 在使用别名时,要注意作用域和链接规则,以避免名称冲突。
  • 在C++中,推荐使用using关键字作为别名机制,因为它更加灵活和简洁。但在处理C语言代码或需要与C语言交互时,可能仍然需要使用typedef

void* 万能指针

在C和C++中,void* 被称为“万能指针”或“通用指针”,因为它可以指向任何数据类型的对象。这种灵活性在编写泛型代码(尤其是在C语言中,因为C++提供了更强大的模板系统)时非常有用。但是,void* 的使用也需要额外的注意,因为当你尝试解引用一个 void* 时,编译器不知道你要访问的数据类型,所以你需要显式地进行类型转换。

C语言中的 void*

在C语言中,void* 常用于以下情况:

  1. 动态内存分配:与 mallocfree 函数一起使用,它们返回和接受 void* 类型的指针。
int *p = (int*)malloc(sizeof(int));
free(p);
  1. 函数指针:当函数需要处理多种类型的指针时,可以使用 void* 作为参数类型。但是,在函数内部,你需要将其转换为正确的类型才能使用。
void print_int(void *ptr) {
    int *int_ptr = (int*)ptr;
    printf("%d\n", *int_ptr);
}

C++中的 void*

在C++中,虽然 void* 仍然可用,但由于C++提供了更强大的类型系统和模板,void* 的使用频率相对较低。然而,在以下情况下,你仍然可能会看到 void*

  1. 与C语言的接口:当你需要与C语言代码交互时,可能会使用 void*
  2. 某些特定的库和API:某些C++库或API可能为了保持与C的兼容性而使用 void*
  3. 动态内存分配:虽然C++推荐使用 newdelete 运算符进行动态内存分配,但 mallocfree 仍然可用,并且它们返回和接受 void* 类型的指针。

注意事项

  • 使用 void* 时需要特别小心,因为编译器不会为你检查类型安全性。如果你错误地将一个 void* 转换为错误的类型并解引用它,可能会导致未定义的行为。
  • 在C++中,尽量使用模板、智能指针和类型安全的容器来替代 void*,以提高代码的可读性、可维护性和安全性。
  • 当从 void* 转换到其他类型的指针时,务必确保转换是安全的,并且转换后的指针确实指向了正确类型的数据。
  • C语言的 void* 万能指针能和其他任意类型的指针相互转换
  • C++ 的 void* 万能指针能和其他任意类型的指针相互转换,任意类型能转为 void*,但是 void* 不能转为任意其他类型的指针

const

在C语言和C++中,const 关键字都被用来声明一个变量或对象是不可变的,即其值在初始化之后不能被修改。然而,虽然这两个语言都支持 const,但它们在处理 const 的方式和提供的特性上存在一些差异,这导致了所谓的“冒牌货”与“真货”的说法。

C语言中的 const(冒牌货)

在C语言中,const 的使用相对简单。你可以用它来声明一个常量,但这个常量主要是编译时的概念。编译器会在编译时检查代码,确保没有尝试修改 const 变量的值。然而,C语言中的 const 并不提供运行时保护,也就是说,如果你在程序运行时通过某种方式(比如指针操作)绕过编译器的检查去修改 const 变量的值,编译器是无法阻止的。这种修改可能导致未定义的行为。
C语言中的 const 并不是真正的常量,只是表示 const 修饰的变量为只读。

const int num = 18;
//num = 19			//error:不能修改const 对象
//int arr[num]		//error:数组大小必须是常量

通过指针间接修改只读变量的值:

int* pt = (int*)&num;
*pt = 19;
printf("%d %d\n", num,*pt);		//output:19 19

可以看到常量it的值已经通过指针被间接改变

C++中的 const(真货)

在C++中,const 的使用更加灵活和强大。C++不仅保留了C语言中 const 的基本功能,还增加了一些额外的特性和保护机制。

  1. 类型检查:C++编译器在编译时会进行更严格的类型检查,确保 const 变量不会被误修改。
  2. const 成员函数:在C++中,你可以声明一个成员函数为 const,这意味着该函数不会修改其所属对象的任何成员变量(除非这些变量也被声明为 mutable)。这有助于维护类的封装性和数据完整性。
  3. const_cast:C++提供了一个 const_cast 运算符,用于在必要时去除 const 性质。然而,这种操作应该谨慎使用,因为它可能会破坏数据的完整性。
  4. const 引用参数:在C++中,你可以将函数参数声明为 const 引用,这样可以确保在函数内部不会修改传入的参数。
  5. 运行时保护:虽然C++本身并不提供直接的运行时保护来防止 const 变量的修改(像某些更高级别的语言那样),但由于其类型系统和编译器的严格检查,使得在大多数情况下都能确保 const 变量的不可变性。

const 的区别

在C语言和C++中,const关键字都被用来声明一个常量,但这两个语言在处理const时有一些细微的差别。以下是一些主要的区别:

  1. 作用域

    • 在C语言中,const变量默认具有文件作用域(除非在函数内部声明),并且如果在一个头文件中声明了const变量,那么在包含该头文件的多个源文件中会出现重复定义的错误。为了解决这个问题,C语言通常使用extern const来声明变量,并在一个源文件中定义它。
    • 在C++中,const变量默认具有块作用域(即它们只在声明它们的代码块内可见),但如果在全局或命名空间作用域中声明,则它们具有全局或命名空间作用域。此外,C++允许在头文件中声明和定义const变量(只要它们是常量表达式并且已经初始化),这被称为常量表达式内联初始化(in-class initialization of static const integral types)。
  2. 类型检查

    • C++对const的类型检查更为严格。例如,在C++中,你不能将一个非const指针赋值给一个const指针,除非该非const指针指向的对象是const的。但在C语言中,这种转换是允许的。
  3. 常量表达式

    • C++支持常量表达式(constexpr),这是一种特殊的const变量,它在编译时就可以确定其值。常量表达式可以用于数组大小、模板参数等需要常量值的地方。C语言没有直接支持常量表达式的概念。
  4. 类的常量成员

    • 在C++中,你可以使用const来声明类的常量成员。这些成员必须在构造函数初始化列表中初始化,并且之后不能被修改。C语言没有类的概念,因此不支持类的常量成员。
  5. const函数

    • 在C++中,你可以使用const来修饰成员函数,表示该函数不会修改调用它的对象的任何成员变量(除了mutable成员)。这有助于保证对象的封装性和不变性。C语言没有成员函数的概念,因此不支持const函数。
  6. 指针和const

    • 在C和C++中,const与指针的结合方式可以产生不同的效果。例如,const int *p表示p指向一个const int(即不能通过p修改该int的值),而int const *p具有相同的意义。但是,int * const p表示p是一个指向int的const指针(即不能修改p的值,但可以修改p指向的int的值)。在C++中,这些组合方式更为常见和有用。
  7. const_cast

    • C++提供了const_cast运算符,用于在编译时安全地去除指针或引用的常量性。这在某些情况下可能是有用的,但应该谨慎使用以避免意外的副作用。C语言没有提供类似的运算符。

总结

  • 在C语言中,const 主要是一个编译时的概念,用于声明常量并帮助编译器进行类型检查。但在运行时,它并不提供额外的保护来防止 const 变量的修改。
  • 在C++中,const 的使用更加灵活和强大。除了编译时的类型检查外,C++还提供了更多的特性和保护机制来确保 const 变量的不可变性。这使得C++中的 const 可以被视为更“真实”或更“强大”的版本。

因此,从某种意义上说,C++中的 const 可以被视为“真货”,而C语言中的 const 则可以被视为“冒牌货”。但需要注意的是,这种说法主要是为了强调两者之间的差异和C++中 const 的优势,并不意味着C语言中的 const 没有用或不可靠。在实际编程中,我们应该根据具体需求选择合适的语言和特性。


http://lihuaxi.xjx100.cn/news/2215467.html

相关文章

python成员属性特性

python成员属性特性 python成员属性内存特性成员属性的默认值python成员属性的内存会不会导致数据出问题除了基本类型以外其他数据类型没有这个特性在构造函数中给成员属性赋值内存会是怎么样的处理python成员属性特性原因是什么呢&#xff1f;python这种处理有什么坏处呢&…

ByteTrack

1. 论文中伪代码表示的流程图 2. 简要版 此图源自&#xff1a; ByteTrack多目标跟踪原理&#xff0c;白老师人工智能学堂 3. 详细版 根据ByteTrack-CPP-ncnn代码的数据流画的较为详细的流程图&#xff1a; 4. ByteTrack-CPP-ncnn的UML类图 Reference ByteTrack多目标跟踪原…

JS中的延时操作setTimeout()和setInterval()

JS中&#xff0c;给我们提供两种延时操作的内置方法setTimeout()和setInterval()。setTimeout和setInterval方法都是挂载在javascript的window对象下&#xff0c;通过两个参数控制&#xff0c;第一个参数控制运行的表达式或方法&#xff0c;第二个参数表示延时的时间&#xff0…

​​Vitis HLS 学习笔记--添加 RTL 黑盒函数

目录 1. 简介 2. 用法详解 2.1 需要的文件 2.1.1 RTL 函数签名 2.1.2 黑盒 JSON 描述文件 2.1.3 RTL IP 文件 2.2 操作步骤 3. 总结 1. 简介 Vitis HLS 工具可以将现有的 Verilog RTL IP&#xff08;即硬件描述语言编写的模块&#xff09;集成到 C/C HLS 项目中。通过…

【CT】LeetCode手撕—20. 有效的括号

题目 原题连接&#xff1a;20. 有效的括号 1- 思路 模式识别 模式1&#xff1a;括号左右匹配 ——> 借助栈来实现 ——> Deque<Character> deque new LinkedList<>()模式2&#xff1a;顺序匹配 ——> 用 if 判断 具体思路 1.遇到左括号 直接入栈相应…

基于Matlab的车牌识别停车场出入库计时计费管理系统(含GUI界面)【W6】

简介&#xff1a; 在当今城市化进程加快的环境下&#xff0c;停车管理成为了一个日益重要和复杂的问题。城市中的停车资源有限&#xff0c;如何高效利用和管理这些资源&#xff0c;不仅关乎市民出行便利性&#xff0c;也涉及到城市交通拥堵、环境污染等诸多问题的解决。 传统的…

【2024亲测无坑】Oracle--19C在Centos7上的静默安装(rpm版)

一、Oracle 19c Linux安装&#xff08;Centos 7&#xff09; 1.查看磁盘可用空间及配置ip地址 [rootlocalhost /]# df -h 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 1.4G 0 1.4G 0% /dev tmpfs 1.4G …

模拟电子技术基础(二)--PN结

PN结的本质 芯片都是由硅晶体制成&#xff0c;单个硅原子最外层有带有4个电子 在纯硅当中这些电子会两两形成共价键&#xff0c;此时周围形成非常稳定的八电子结构 在一个回路中&#xff0c;灯泡不亮&#xff0c;不导通&#xff0c;因为电池无法吸引其中的电子离开&#xff0c…