【系统学C++】二、从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 的别名,以及 true 和 false 作为宏定义,通常用于 <stdbool.h> 头文件中。但是,由于C语言对 _Bool 的处理本质上还是基于整型的,所以其使用并不像C++中的 bool 类型那样直观和严格。
在C++中,bool 是一个内置的数据类型,它只有两个可能的值:true 和 false。bool 类型常用于条件语句、循环语句以及逻辑运算中。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++中,true 和 false 是 bool 类型的常量,它们的值分别为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; // 输出 1return 0;
}
在这个例子中,std::boolalpha 使得 std::cout 将 bool 类型的值作为 “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_cast、dynamic_cast、const_cast和reinterpret_cast。这些转换函数要求程序员明确指定转换的意图,并在可能的情况下进行运行时检查。
- 减少隐式转换:C++尝试减少隐式类型转换的数量,以减少错误的可能性。然而,由于C++需要与C语言兼容,因此仍然存在一些隐式转换。
- 面向对象编程:C++支持面向对象编程,这包括类、继承、多态等概念。这些特性使得C++的类型系统更加复杂,但也更加灵活和强大。通过封装、继承和多态,C++可以提供更好的代码重用性和可维护性。
- 模板和STL:C++的模板和STL(Standard Template Library)提供了类型安全的容器和数据结构,这些容器和数据结构在编译时进行类型检查,从而减少了运行时错误的可能性。
总结
- 从C语言到C++,类型系统的“强弱”主要体现在对程序员施加的约束程度和防止错误的能力上。
- C语言的类型系统相对较弱,允许更多的灵活性和隐式转换,但也增加了出错的可能性。
- C++通过引入更严格的类型检查和转换函数、减少隐式转换、支持面向对象编程以及提供类型安全的容器和数据结构等方式,增强了其类型系统的“强度”,从而提高了代码的安全性和可维护性。
NULL 和 nullptr
 
在C和C++中,NULL 和 nullptr 都用于表示指针不指向任何有效的内存地址(空指针)。然而,它们在定义和使用上存在一些区别。
NULL
 
- 在C语言中:NULL是一个宏,通常被定义为(void*)0或0。它用于表示指针不指向任何有效的内存地址。
- 在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++中,有几种方式可以实现这一点,但最常用的可能是使用typedef或using关键字(从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* 常用于以下情况:
- 动态内存分配:与 malloc和free函数一起使用,它们返回和接受void*类型的指针。
int *p = (int*)malloc(sizeof(int));
free(p);
- 函数指针:当函数需要处理多种类型的指针时,可以使用 void*作为参数类型。但是,在函数内部,你需要将其转换为正确的类型才能使用。
void print_int(void *ptr) {int *int_ptr = (int*)ptr;printf("%d\n", *int_ptr);
}
C++中的 void*
 
在C++中,虽然 void* 仍然可用,但由于C++提供了更强大的类型系统和模板,void* 的使用频率相对较低。然而,在以下情况下,你仍然可能会看到 void*:
- 与C语言的接口:当你需要与C语言代码交互时,可能会使用 void*。
- 某些特定的库和API:某些C++库或API可能为了保持与C的兼容性而使用 void*。
- 动态内存分配:虽然C++推荐使用 new和delete运算符进行动态内存分配,但malloc和free仍然可用,并且它们返回和接受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*)#
*pt = 19;
printf("%d %d\n", num,*pt);		//output:19 19
可以看到常量it的值已经通过指针被间接改变
C++中的 const(真货)
 
在C++中,const 的使用更加灵活和强大。C++不仅保留了C语言中 const 的基本功能,还增加了一些额外的特性和保护机制。
- 类型检查:C++编译器在编译时会进行更严格的类型检查,确保 const变量不会被误修改。
- const 成员函数:在C++中,你可以声明一个成员函数为 const,这意味着该函数不会修改其所属对象的任何成员变量(除非这些变量也被声明为mutable)。这有助于维护类的封装性和数据完整性。
- const_cast:C++提供了一个 const_cast运算符,用于在必要时去除const性质。然而,这种操作应该谨慎使用,因为它可能会破坏数据的完整性。
- const 引用参数:在C++中,你可以将函数参数声明为 const引用,这样可以确保在函数内部不会修改传入的参数。
- 运行时保护:虽然C++本身并不提供直接的运行时保护来防止 const变量的修改(像某些更高级别的语言那样),但由于其类型系统和编译器的严格检查,使得在大多数情况下都能确保const变量的不可变性。
const 的区别
 
在C语言和C++中,const关键字都被用来声明一个常量,但这两个语言在处理const时有一些细微的差别。以下是一些主要的区别:
-  作用域: - 在C语言中,const变量默认具有文件作用域(除非在函数内部声明),并且如果在一个头文件中声明了const变量,那么在包含该头文件的多个源文件中会出现重复定义的错误。为了解决这个问题,C语言通常使用extern const来声明变量,并在一个源文件中定义它。
- 在C++中,const变量默认具有块作用域(即它们只在声明它们的代码块内可见),但如果在全局或命名空间作用域中声明,则它们具有全局或命名空间作用域。此外,C++允许在头文件中声明和定义const变量(只要它们是常量表达式并且已经初始化),这被称为常量表达式内联初始化(in-class initialization of static const integral types)。
 
- 在C语言中,
-  类型检查: - C++对const的类型检查更为严格。例如,在C++中,你不能将一个非const指针赋值给一个const指针,除非该非const指针指向的对象是const的。但在C语言中,这种转换是允许的。
 
- C++对
-  常量表达式: - C++支持常量表达式(constexpr),这是一种特殊的const变量,它在编译时就可以确定其值。常量表达式可以用于数组大小、模板参数等需要常量值的地方。C语言没有直接支持常量表达式的概念。
 
- C++支持常量表达式(constexpr),这是一种特殊的
-  类的常量成员: - 在C++中,你可以使用const来声明类的常量成员。这些成员必须在构造函数初始化列表中初始化,并且之后不能被修改。C语言没有类的概念,因此不支持类的常量成员。
 
- 在C++中,你可以使用
-  const函数: - 在C++中,你可以使用const来修饰成员函数,表示该函数不会修改调用它的对象的任何成员变量(除了mutable成员)。这有助于保证对象的封装性和不变性。C语言没有成员函数的概念,因此不支持const函数。
 
- 在C++中,你可以使用
-  指针和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++中,这些组合方式更为常见和有用。
 
- 在C和C++中,
-  const_cast: - C++提供了const_cast运算符,用于在编译时安全地去除指针或引用的常量性。这在某些情况下可能是有用的,但应该谨慎使用以避免意外的副作用。C语言没有提供类似的运算符。
 
- C++提供了
总结
- 在C语言中,const主要是一个编译时的概念,用于声明常量并帮助编译器进行类型检查。但在运行时,它并不提供额外的保护来防止const变量的修改。
- 在C++中,const的使用更加灵活和强大。除了编译时的类型检查外,C++还提供了更多的特性和保护机制来确保const变量的不可变性。这使得C++中的const可以被视为更“真实”或更“强大”的版本。
因此,从某种意义上说,C++中的 const 可以被视为“真货”,而C语言中的 const 则可以被视为“冒牌货”。但需要注意的是,这种说法主要是为了强调两者之间的差异和C++中 const 的优势,并不意味着C语言中的 const 没有用或不可靠。在实际编程中,我们应该根据具体需求选择合适的语言和特性。