【系统学C++】二、从C语言到C++(二)
- [`bool` 类型](#
bool
类型) -
- [怎么打印 `bool` 类型的值](#怎么打印
bool
类型的值)
- [怎么打印 `bool` 类型的值](#怎么打印
- 强弱类型
- [`NULL` 和 `nullptr`](#
NULL
和nullptr
) - 起别名
-
- [使用 `typedef`](#使用
typedef
) - [使用 `using` 关键字(C++11及以后)](#使用
using
关键字(C++11及以后)) - 注意
- [使用 `typedef`](#使用
- [` void*` 万能指针](#
void*
万能指针) -
- [C语言中的 `void*`](#C语言中的
void*
) - [C++中的 `void*`](#C++中的
void*
) - 注意事项
- [C语言中的 `void*`](#C语言中的
- `const`
-
- [C语言中的 `const`(冒牌货)](#C语言中的
const
(冒牌货)) - [C++中的 `const`(真货)](#C++中的
const
(真货)) - [`const` 的区别](#
const
的区别) - 总结
- [C语言中的 `const`(冒牌货)](#C语言中的
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
类型的简单示例:
cpp
#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
类型的值:
cpp
#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::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
:
c
int *ptr = NULL;
在C++中,你可以继续使用 NULL
(尽管它可能已经被定义为 0
),但更好的做法是使用 nullptr
:
cpp
int *ptr = nullptr;
注意:在C++中,如果你尝试将 nullptr
赋值给一个非指针类型的变量,编译器会报错,这有助于在编译时捕获潜在的错误。而如果你使用 NULL
并将其定义为 0
,这种错误可能不会被捕获。
起别名
在C++中,起别名(aliasing)通常指的是为一个类型或对象创建另一个名称,这样你就可以通过不同的名称来引用相同的类型或对象。在C++中,有几种方式可以实现这一点,但最常用的可能是使用typedef
或using
关键字(从C++11开始)。
使用 typedef
在C和C++中,typedef
关键字被用来为现有类型定义一个新的名称。这在处理复杂的数据类型时特别有用,比如结构体、联合体、函数指针等。
cpp
// 定义一个结构体
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
关键字被引入作为一种更简洁、更灵活的别名机制。它可以用于类型、模板、命名空间等。
cpp
// 使用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*
类型的指针。
c
int *p = (int*)malloc(sizeof(int));
free(p);
- 函数指针 :当函数需要处理多种类型的指针时,可以使用
void*
作为参数类型。但是,在函数内部,你需要将其转换为正确的类型才能使用。
c
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
修饰的变量为只读。
c
const int num = 18;
//num = 19 //error:不能修改const 对象
//int arr[num] //error:数组大小必须是常量
通过指针间接修改只读变量的值:
c
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
没有用或不可靠。在实际编程中,我们应该根据具体需求选择合适的语言和特性。