1,什么是C++
C++ 是一种通用的、面向对象的编程语言,是 C 语言的一个超集,也就是说,任何有效的 C 程序都是有效的 C++ 程序。C++ 通过添加诸如类和对象、继承和多态等概念,扩展了 C 语言的功能,使其更适用于大型软件项目和复杂系统的开发。
以下是 C++ 的一些重要特性和概念:
1.面向对象编程(OOP):C++ 是一种面向对象的编程语言,支持类和对象的概念。通过封装、继承和多态,可以实现更清晰、模块化的代码结构,并提高代码的可重用性和可维护性。
2.泛型编程:C++ 支持泛型编程,通过模板(template)实现。模板允许编写通用的数据类型和算法,使得代码更加灵活和可扩展。
3.标准模板库(STL):STL 是 C++ 标准库的一部分,提供了丰富的数据结构和算法,如向量(vector)、链表(list)、队列(queue)、堆栈(stack)、映射(map)等,以及各种常用算法,如排序、查找、迭代等。
4.强类型检查:C++ 是一种静态类型语言,具有强类型检查,即在编译时检查类型错误,避免了许多在运行时可能出现的错误。
5.跨平台性:C++ 是一种可移植的语言,可以在各种操作系统和硬件平台上进行开发和运行,包括 Windows、Linux、macOS 等。
6.高性能:C++ 是一种高性能的语言,提供了直接的内存访问和更好的控制性能的能力,使得它适用于开发对性能要求较高的应用程序,如游戏、图形处理、嵌入式系统等。
7.多范式编程:C++ 支持多种编程范式,包括面向过程编程、面向对象编程、泛型编程等,可以根据需要选择合适的编程范式来解决问题。
总的来说,C++ 是一种功能强大、灵活多样的编程语言,广泛应用于各种领域,包括系统软件、应用软件、游戏开发、嵌入式系统等。
2,c++的发展史
C++ 的发展历史可以追溯到 1979 年,当时 Bjarne Stroustrup 在贝尔实验室开始了 C++ 的开发,最初被称为 "C with Classes"。以下是 C++ 的主要发展历程:
1.初期阶段(1980s):
2.在 1983 年发布了 C++ 的第一个正式版本。
3.在这个阶段,C++ 主要是建立在 C 语言的基础上,并添加了面向对象编程的特性,如类、继承、多态等。
4.标准化阶段(1990s):
5.在 1998 年发布了 C++ 的第一个国际标准,称为 C++98 或 C++03,这个标准基本上固定了 C++ 的语言特性和标准库。
6.在这个阶段,C++ 的标准化使得它更加稳定和可靠,也促进了 C++ 的广泛应用。
7.C++11(2011年):
8.在 2011 年发布了 C++11 标准,引入了许多重要的新特性,如自动类型推断、lambda 表达式、智能指针、右值引用等,极大地改进了 C++ 的编程体验和性能。
9.C++14 和 C++17:
10.C++14 在 2014 年发布,是对 C++11 的一些小幅修改和增强。
11.C++17 在 2017 年发布,引入了一些新的语言特性和标准库改进,如并行算法、文件系统库、结构化绑定等。
12.C++20 和未来发展:
13.C++20 在 2020 年发布,带来了一系列重要的新特性,包括概念、模块、协程、范围和初始化语法等,以及许多标准库的改进。
14.未来的 C++ 标准还将继续发展,预计会持续增加新的功能和改进,以满足不断变化的编程需求和技术趋势。
在整个发展过程中,C++ 逐渐成为一种功能丰富、灵活多样的编程语言,广泛应用于各种领域,包括系统软件、应用软件、游戏开发、嵌入式系统等。同时,C++ 的标准化和开放性也促进了社区的积极参与和贡献,推动了语言的不断发展和完善。
3,c++与java推荐
很多人可能存在这样的误区,什么误区呢,语言误区。有些人觉的java岗位多,社区完善,每半年优化一次就觉得java比c++好,但是在一些底层开发中c++更容易实现,如服务器开发等。语言只是编程的工具,并没有好于坏的区分,而且没人规定只可以学习一门语言不是。
每种编程语言都有其自身的优势和适用场景,没有一种语言能够在所有方面都胜过其他语言。以下是一些常见的语言误区:
-
语言偏好误区:有些人可能过分偏爱某种编程语言,认为它是最好的语言,而忽视其他语言的优势。这种偏好可能是基于个人经验、所处行业或社区影响等因素形成的。
-
语言适用范围误区:有些人可能错误地认为某种语言只适用于特定领域或场景,而不考虑其在其他领域的潜在用途。实际上,大多数编程语言都具有广泛的适用范围,可以应用于不同类型的项目和问题解决方案。
-
语言性能误区:有些人可能过分关注语言的性能,认为性能更高的语言就一定更好。然而,性能并非唯一的考量因素,开发效率、可维护性、生态系统等因素同样重要。
-
语言趋势误区:有些人可能过分追求某种语言的热门度或流行趋势,而忽视其实际的适用性和价值。流行的语言不一定适合每个项目或每个开发者。
走出语言误区意味着要客观看待每种编程语言的优劣,并根据具体的项目需求和个人偏好选择合适的工具。学习多种编程语言可以拓宽视野,增强解决问题的能力,并丰富个人的编程技能。所以,并不需要纠结该学习那种语言,行动起来即可。
4,c++的境界划分
学习 C++ 可以说是一场持续的探索之旅,它的境界可以根据个人的学习目标和需求而有所不同。不过,以下是一些通常被认为是 C++ 学习路线上的不同境界:
1.入门者(初学者):
2.对基本语法和概念有基本了解,如变量、数据类型、循环、条件语句等。
3.能够编写简单的程序解决基本问题,如打印输出、数学运算等。
4.初级程序员:
5.熟悉更多的 C++ 特性,如函数、数组、指针等。
6.能够编写结构化程序,并能够理解面向对象编程的基本概念。
7.开始学习面向对象编程的概念,如类、对象、继承、多态等。
8.中级程序员:
9.熟练掌握面向对象编程,能够设计和实现复杂的类和对象关系。
10.熟悉常用的标准库,能够使用标准库中的容器、算法等进行开发。
11.开始学习更高级的 C++ 特性,如模板、异常处理、多线程编程等。
12.高级程序员:
13.深入理解 C++ 的底层机制和高级特性,如内存管理、移动语义、模板元编程等。
14.能够进行性能优化和调试,理解代码的运行时性能特性。
15.精通现代 C++ 开发技术和最佳实践,能够应对复杂的开发场景和挑战。
16.专家级程序员:
17.对 C++ 标准库和语言规范有深入的理解和掌握。
18.能够进行 C++ 核心语言和编程范式的扩展和定制。
19.在特定领域(如游戏开发、系统编程、嵌入式开发等)有深厚的经验和造诣。
达到每个境界都需要不断的学习和实践,同时也需要参与到各种项目中去,积累经验和技能。无论你的目标是成为一名优秀的软件工程师、游戏开发者还是系统架构师,C++ 都是一门不可或缺的重要技能。博主跟你们一样是个初学者。
5,c++的语法
5.1 关键字
c++总计63个关键字,其中红色圈中的关键字为C语言关键字,在c++中延用。
5.2 命名空间
我们接下来就看看命名空间是如何解决这个问题的
#include <iostream>
#include <stdlib.h>
/* 定义命名空间 */
namespace test
{
/* 命名空间中不仅可以定义整形等变量*/
int rand = 10;
int print = 90;
/* 亦可定义函数以及结构体等 */
int add(int right, int left)
{
return right + left;
}
typedef struct Tree {
struct Tree* node;
int data;
}Tree;
}
/* 就算我们在外部重新写一个重命名的函数或者其他,都不会有什么影响 */
int add(int right, int left)
{
return (right + left) * 10;
}
/* 展开命名空间中的某个值 */
using test::print;
int main()
{
//第一种访问命名空间的数据的方式
printf("%d\n", test::add(10, 20));
printf("%d\n", add(10, 20));
printf("%d\n", test::rand);
//第二种展开命名空间中的某个值
printf("%d\n", print);
}
这里,我们了解了命名空间,以及它的用途。接下来,我们学习两个关键字。
5.3 cout和cin
cout
(流插入) 是 C++ 标准库中的输出流对象,位于 iostream
头文件中。它是面向对象的,使用 <<
运算符将数据插入到输出流中,然后将其输出到标准输出设备(通常是控制台)。cout
提供了对输出的更多控制,如格式化输出、控制输出精度等。
示例:
#include <iostream>
int main()
{
int num = 42;
std::cout << "The value of num is: " << num << std::endl; return 0;
}
printf:
printf
是 C 标准库中的函数,位于 cstdio
头文件中。它是面向过程的,使用格式化字符串和可变参数列表来进行输出。printf
的格式化字符串指定了输出的格式,然后根据参数列表将数据输出到标准输出设备。
示例:
#include <stdio.h>
int main()
{
int num = 42;
printf("The value of num is: %d\n", num);
return 0;
}
相似之处:
- 都用于输出数据到标准输出设备。
- 都可以输出各种数据类型,如整数、浮点数、字符串等。
不同之处:
cout
是 C++ 的一部分,是 C++ 标准库中的输出流对象;而printf
是 C 标准库中的函数。cout
是面向对象的,使用<<
运算符插入数据;而printf
是面向过程的,使用格式化字符串和参数列表。cout
提供了更多的控制选项,如控制输出精度、使用流控制符等;而printf
的格式化字符串控制输出格式。cout
在编译时会进行类型检查,能够更好地避免类型不匹配的错误;而printf
的参数列表是一个可变参数列表,类型不匹配时可能会导致运行时错误。
在 C++ 中,通常推荐优先使用 cout
,因为它更符合 C++ 的面向对象特性,而且更安全。但在一些特殊情况下,如需要使用复杂的格式化输出时,printf
也是一个很好的选择。
cin
(流提取)是 C++ 标准库中的输入流对象,位于 iostream
头文件中。它是面向对象的,使用 >>
运算符从输入流中提取数据,并将其存储到相应的变量中。cin
提供了对输入的更多控制,如数据类型检查、输入错误处理等。
示例:
#include <iostream>
int main()
{
int num; std::cout << "Enter a number: ";
std::cin >> num; std::cout << "You entered: " << num << std::endl;
return 0;
}
scanf
是 C 标准库中的函数,位于 cstdio
头文件中。它是面向过程的,使用格式化字符串和地址传递的方式来接收输入的数据,并将其存储到相应的变量中。scanf
的格式化字符串指定了输入的格式,然后根据参数列表将数据从标准输入设备中读取。
示例:
#include <stdio.h>
int main()
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
printf("You entered: %d\n", num);
return 0;
}
相似之处:
- 都用于从标准输入设备(通常是键盘)读取数据。
- 都可以读取各种数据类型,如整数、浮点数等。
不同之处:
cin
是 C++ 的一部分,是 C++ 标准库中的输入流对象;而scanf
是 C 标准库中的函数。cin
是面向对象的,使用>>
运算符从输入流中提取数据;而scanf
是面向过程的,使用格式化字符串和地址传递方式。cin
在编译时会进行类型检查,能够更好地避免类型不匹配的错误;而scanf
的参数列表是一个可变参数列表,类型不匹配时可能会导致运行时错误。cin
可以处理输入错误,并提供了异常处理机制;而scanf
对输入错误的处理相对较弱,需要额外的代码来进行检查和处理。
在 C++ 中,通常推荐优先使用 cin
,因为它更符合 C++ 的面向对象特性,而且更安全可靠。但在一些特殊情况下,如需要使用复杂的格式化输入时,scanf
也是一个很好的选择。
5.4 缺省(默认)参数
C++ 中的缺省(默认)参数允许在函数声明中为参数提供默认值。这意味着在调用函数时,如果没有提供该参数的值,则将使用其默认值。默认参数在函数的定义中进行指定,并且通常在函数的声明中也会声明默认参数。
/* 全缺省 */
void Fun2(int para1 = 11, int para2 = 12, int para3 = 13)
{
cout << "Fun2:" << para1 << endl;
cout << "Fun2:" << para2 << endl;
cout << "Fun2:" << para3 << endl << endl;
}
/* 半缺省参数 */
void Fun3(int para1, int para2, int para3 = 20)
{
cout << "Fun3:" << para1 << endl;
cout << "Fun3:" << para2 << endl;
cout << "Fun3:" << para3 << endl << endl;
}
int main()
{
/* 半缺省(全缺省)传参必须从左往右依次给出,不能间隔着给。Fun2(,2,3)--错误做法 */
Fun2();
Fun2(1);
Fun2(1, 2);
Fun2(1, 2, 3);
Fun3(10, 20);
Fun3(10, 20, 40);
return 0;
}
5.5 函数重载
5.5.1 函数重载定义
C++ 中的函数重载指的是在同一个作用域内定义多个具有相同名称但参数列表不同的函数。这使得可以使用相同的函数名来实现不同的功能,根据参数的类型、数量或顺序来调用不同版本的函数。
函数重载的定义遵循以下规则:
-
函数名相同:函数重载的函数必须具有相同的名称。
-
参数列表不同:函数重载的函数必须具有不同的参数列表,包括参数的类型、数量或顺序。
-
返回类型不同:函数重载的函数可以具有相同的名称和参数列表,但返回类型不同。然而,仅根据返回类型的不同来重载函数并不是合法的重载,因为函数重载的决定是基于函数的调用时可见的信息,而不是函数的返回类型。
-
函数重载与默认参数:函数重载可以与默认参数一起使用。在这种情况下,函数的参数列表可能是不同的,但函数重载的决定仍然基于调用时提供的参数。
下面是一个简单的示例:
#include <iostream>
// 函数重载示例
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void print(double num) {
std::cout << "Double: " << num << std::endl;
}
void print(char ch) {
std::cout << "Character: " << ch << std::endl;
}
int main() {
print(5); // 调用第一个 print 函数,输出: Integer: 5
print(3.14); // 调用第二个 print 函数,输出: Double: 3.14
print('A'); // 调用第三个 print 函数,输出: Character: A
return 0;
}
5.5.2 不构成函数重载情况
在 C++ 中,有一些情况下并不构成函数重载,包括:
- 仅返回类型不同:函数重载的决定不基于函数的返回类型,因此仅仅因为函数的返回类型不同而定义的函数不构成重载。例如:
// 这不是重载,因为函数参数相同,只有返回类型不同
int add(int a, int b) {
return a + b;
}
double add(int a, int b) {
return a + b;
}
- 函数名不同:只有函数名相同才能构成重载,如果函数名不同,则不构成重载。例如:
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void display(int num) {
std::cout << "Integer: " << num << std::endl;
}
这两个函数虽然参数相同,但函数名不同,因此不构成重载。
- 参数列表相同:如果参数列表相同,即使函数名不同,也不构成重载。例如:
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void print(int value) {
std::cout << "Value: " << value << std::endl;
}
这两个函数的参数列表都是 (int)
,即使函数名不同,也不构成重载。
- 仅默认参数不同:如果函数仅在默认参数上有所不同,也不构成重载。例如:
void print(int num, int width = 10) {
std::cout << "Number: " << num << ", Width: " << width << std::endl;
}
void print(int num, int height = 5) {
std::cout << "Number: " << num << ", Height: " << height << std::endl;
}
这两个函数虽然有不同的默认参数,但函数签名相同,因此不构成重载。
总之,只有在函数名相同且参数列表不同的情况下才构成函数重载。
当然还有一种情况也不构成重载
namespace test {
// 命名空间中的函数重名不构成重载
void print(int num) {
std::cout << "test Integer: " << num << std::endl;
}
}
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
int main() {
print(5); // 调用第一个 print 函数,输出: Integer: 5
test::print(10);
return 0;
}
5.5.3 函数重载原理
6,extern "C"
在C++中,extern "C" 是一种用来指定函数按照C语言的调用约定进行编译和链接的语法。C语言和C++语言在函数调用约定上有所不同,主要表现在函数名的修饰(name mangling)和参数的传递方式上。
通常情况下,C++编译器会对函数名进行修饰以支持函数重载、命名空间等特性,这会导致生成的函数符号与C语言不兼容,从而使得C++代码无法直接与C代码进行链接。但是,在C++中可以使用extern "C"来告诉编译器将函数的名字按照C语言的规则来处理,从而使得C++函数能够在C语言中进行调用。
具体而言,当你在C++中使用 extern "C" 声明一个函数时,编译器会将该函数的名字按照C语言的方式进行处理,不会进行C++特有的名称修饰。这意味着函数的名称和参数类型会被保持原样,不会进行符号重载和名称修饰。
例如:
// C++中的声明,函数名会被修饰
extern "C" void func(int arg);
// C++中的定义,函数名会被修饰
extern "C" {
void func(int arg) {
// 函数体
}
}
上述代码中,func 函数的名字在C++中被声明和定义,但由于使用了 extern "C",因此编译器会按照C语言的方式处理函数名,使得在外部可以直接调用 func 而不需要考虑C++的名称修饰。
这种机制常用于C++代码需要与C代码进行链接的情况,比如在C++中使用C语言编写的库或者接口。
7,“&”引用
7.1 引用的概念
在C++中,引用是一种重要的概念,它提供了对变量的别名或引用。引用允许我们使用一个名称来访问另一个变量的值,而不是创建该变量的副本。引用通常用于以下几个方面:
1. 别名
引用提供了对变量的别名,通过引用,可以使用一个名称来操作另一个变量的值。
int x = 5;
int &ref = x; // ref 是 x 的引用
现在,ref 是 x 的别名,对 ref 的操作实际上就是对 x 的操作。
2. 传递函数参数
引用可以用作函数的参数,这样可以避免将大型对象传递给函数时发生的复制。
void increment(int &num) {
num++;
}
int main() {
int x = 5;
increment(x); // 传递 x 的引用
std::cout << x; // 输出 6
return 0;
}
在这个例子中,increment 函数接受一个整数的引用,通过引用修改了 x 的值。
3. 返回引用
函数还可以返回引用,允许通过函数来修改变量的值。
int &getLargest(int &a, int &b) {
return (a > b) ? a : b;
}
int main() {
int x = 5, y = 10;
getLargest(x, y) = 20; // 修改了 x 或 y 的值
std::cout << x << " " << y; // 输出 20 10
return 0;
}
在这个例子中,getLargest 函数返回两个整数中较大的那个的引用,可以通过返回的引用来修改 x 或 y 的值。
4. 引用作为成员变量
引用也可以作为类的成员变量,它可以在类的构造函数中初始化为另一个对象的引用。
class MyClass {
public:
int &ref;
MyClass(int &r) : ref(r) {}
};
int main() {
int x = 5;
MyClass obj(x);
obj.ref = 10;
std::cout << x; // 输出 10
return 0;
}
在这个例子中,MyClass 类有一个整数的引用成员变量 ref,它可以在对象创建时初始化为另一个整数的引用。
总之,引用是C++中的重要概念,它提供了对变量的别名或引用,并允许我们通过引用来修改变量的值,同时也可以用于函数参数传递和返回值等方面。
7.2 常引用
在C++中,常引用(const reference)是一种引用,它允许我们以只读方式访问所引用的对象,并且在引用的生命周期内,不能通过该引用来修改所引用的对象。常引用通过在引用类型前添加 const 关键字来声明。
常引用的定义语法如下:
const type &refName = existingVariable;
其中:
1.type 是引用的类型,可以是任何合法的C++数据类型,包括基本类型、自定义类型、指针类型等。
2.refName 是引用的名称,可以是任何有效的变量名。
3.existingVariable 是引用所引用的已存在的对象或变量。
下面是一个示例:
int x = 10;
const int &
ref = x; // ref 是对 x 的只读引用
在这个例子中,ref 是对 x 的只读引用,即通过 ref 可以读取 x 的值,但不能修改 x 的值。
常引用通常用于以下几个方面:
4.函数参数传递: 常引用可以用于函数参数传递,通过常引用可以避免在函数调用过程中创建变量的副本,从而提高性能。
void print(const int &num) {
std::cout << num;
}
5.函数返回值: 函数可以返回常引用,允许通过函数来访问对象的值,但不允许修改它。
const int &getNumber() {
static int num = 10;
return num;
}
6.避免不必要的修改: 常引用可以确保在函数调用过程中不会意外修改所引用的对象,从而提高程序的可靠性和安全性。
需要注意的是,常引用虽然不能修改所引用的对象,但所引用的对象本身并不一定是常量。因此,即使常引用引用的是一个非常量对象,也不能通过常引用来修改它。
8,引用和指针
引用和指针是C++中两种不同的机制,它们都用于间接访问内存中的对象,但在语法、语义和使用方式上有一些重要的区别:
1.语法和声明方式:
指针使用 * 符号来声明和操作,而引用使用 & 符号来声明。
声明指针时需要指定指针的类型,而声明引用时需要指定引用所引用对象的类型。
2.初始化和赋值:
指针可以在声明时不进行初始化,也可以在之后赋值给其他对象,甚至可以为 nullptr。而引用在声明时必须进行初始化,并且一旦初始化之后,就无法再绑定到其他对象。
指针可以多次赋值,指向不同的对象,而引用一旦初始化之后,就不能再改变其引用的对象。
3.空值处理:
指针可以是空指针(nullptr),即指向空地址,表示不指向任何有效对象。而引用必须始终指向一个有效的对象,不能为null。
4.操作符和语法:
通过指针可以使用解引用操作符 * 来访问所指向的对象,也可以使用地址操作符 & 来获取变量的地址。而引用本身就是所引用对象的别名,不需要使用额外的操作符来访问所引用的对象。
对指针进行解引用可能会导致空指针异常(如果指针指向空地址),而对引用进行操作则不会出现这种情况,因为引用必须在初始化时绑定到有效的对象。
5.用途和约束:
引用通常用于函数参数传递、函数返回值、避免副本创建等场景,而指针则更灵活,可以用于动态内存分配、数据结构的实现、跨函数修改对象等情况。
使用引用时,不需要考虑空指针问题,因为引用必须始终引用有效的对象。而使用指针时,需要注意空指针异常的处理。
引用更容易阅读和理解,因为它提供了一种更直观的方式来操作对象,而指针则需要更多的语法和操作符来完成同样的任务。
总的来说,引用提供了一种更安全、更简洁的方式来访问和操作对象,而指针则更灵活,但也更容易出错。在选择使用引用还是指针时,需要根据具体的需求和场景来决定。
9,内联函数
9.1 内联函数的定义
内联函数是C++中的一种特殊函数,通过关键字 inline 来声明。内联函数的主要目的是提高程序的执行效率,减少函数调用的开销。
在编译器处理内联函数时,它会将函数的定义体直接插入到每个调用该函数的地方,而不是生成一个函数调用。这样可以减少函数调用的开销,因为不需要保存和恢复调用现场,也不需要进行参数传递和返回值处理。
内联函数的定义语法如下:
inline returnType functionName(parameters) {
// 函数定义体
}
其中:
1.returnType 是函数返回类型。
2.functionName 是函数名称。
3.parameters 是函数参数列表。
例如:
inline int add(int a, int b) {
return a + b;
}
9.2 内联函数的使用场景
在使用内联函数时,编译器会根据实际情况决定是否将函数内联展开。通常情况下,内联函数适用于以下情况:
4.函数体简单:内联函数通常用于函数体比较简单的情况,因为函数体过大时,内联展开可能会导致代码体积增大,影响程序的性能。
5.频繁调用:内联函数适用于频繁调用的函数,通过内联展开可以减少函数调用的开销。
6.常用的小型辅助函数:内联函数通常用于一些常用的小型辅助函数,比如一些简单的数学运算、访问器函数等。
需要注意的是,虽然使用内联函数可以提高程序的执行效率,但过度使用内联函数可能会导致代码体积增大,从而影响程序的性能和可维护性。因此,应该根据实际情况谨慎地选择使用内联函数。此外,编译器不一定会完全遵循 inline 关键字,它可能会根据实际情况进行优化和调整。
10,auto关键字(c++11)
10.1 auto的定义
auto 关键字是 C++11 中引入的新特性之一,用于自动推导变量的类型。通过 auto 关键字,编译器可以根据变量的初始化表达式推导出其类型,从而简化代码并提高可读性。
使用 auto 关键字声明变量的语法如下:
auto variableName = initializer;
其中:
1.variableName 是变量的名称。
2.initializer 是变量的初始化表达式。
例如:
auto x = 10; // x 被推导为 int 类型
auto y = 3.14; // y 被推导为 double 类型
auto z = "Hello"; // z 被推导为 const char* 类型
10.2 auto 关键字的使用场景
3.简化代码: 当变量的类型可以从初始化表达式中明显推导出来时,使用 auto 可以省略冗长的类型声明,使代码更加简洁清晰。
4.复杂类型简化: 对于模板和复杂类型,使用 auto 可以避免手动指定类型,减少编码错误和维护成本。
5.迭代器和范围循环: 在使用迭代器或范围循环时,auto 可以更方便地声明循环变量,避免手动指定迭代器的类型。
6.泛型编程: 在泛型编程中,auto 可以用于推导模板参数的类型,简化代码并提高灵活性。
需要注意的是,auto 推导出的类型通常是变量的实际类型的副本,而不是引用或 const 限定符。如果希望推导出引用类型或 const 限定符,可以使用 auto& 或 const auto&。
此外,auto 关键字也可以与 decltype 结合使用,用于从表达式中推导出类型,这在泛型编程中非常有用。
10.3 auto无法推导的场景
虽然 auto 关键字可以自动推导变量的类型,但并不是所有情况下都能成功推导,存在一些特殊的场景,auto 无法进行类型推导。以下是一些 auto 无法推导的情况:
1.初始化表达式中包含多个不同类型的子表达式: 如果初始化表达式中包含多个不同类型的子表达式,编译器无法确定应该推导出哪种类型。
auto x = 10, y = 3.14; // x 和 y 都是不同类型,无法推导
2.初始化表达式为函数调用时,函数有多个重载: 如果初始化表达式是一个函数调用,并且该函数有多个重载版本,编译器无法确定应该选择哪个重载版本。
auto result = max(a, b); // 如果 max 有多个重载版本,无法推导
3.初始化表达式为 lambda 表达式且没有显式返回类型: 如果初始化表达式为 lambda 表达式,并且 lambda 表达式没有显式指定返回类型,编译器无法确定 lambda 表达式的返回类型。
auto func = [](int a, int b) { return a + b; }; // lambda 表达式没有显式返回类型
4.初始化表达式为模板类型参数: 如果初始化表达式是一个模板类型参数,编译器无法确定模板类型参数的具体类型。
template<typename T>
void foo(T t) {
auto x = t; // 无法确定 T 的具体类型
}
5.使用 auto&& 推导右值引用时,右值不是纯右值: 当使用 auto&& 推导右值引用时,如果右值不是纯右值,编译器可能会推导出错误的类型。
auto&& ref = getX(); // 如果 getX() 返回一个左值,则可能推导出错误的类型
在这些情况下,编译器无法确定变量的具体类型,因此无法进行类型推导,此时就需要显式指定变量的类型,而不能使用 auto 关键字。
11,基于范围的for循环(C++11)
11.1 范围的for循环定义
基于范围的 for 循环(Range-based for loop)是 C++11 中引入的一种循环语法,用于遍历容器(如数组、标准库容器、C-style 数组等)中的元素,使得遍历更加简洁和直观。
基于范围的 for 循环的语法形式如下:
for (element_declaration : range_expression) {
// 循环体
}
其中:
1.element_declaration:循环体中每次迭代时,当前元素的声明。
2.range_expression:要遍历的范围,可以是容器、数组或者其他支持迭代器的数据结构。例如,遍历一个 std::vector<int>:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
11.2 基于范围的 for 循环的特点和用法
3.简洁明了: 相比传统的迭代器或者下标循环,基于范围的 for 循环语法更加简洁明了,代码更易读。
4.自动推导: 循环体中的 element_declaration 可以使用 auto 关键字进行类型推导,避免手动指定类型。
5.不需要迭代器或下标: 基于范围的 for 循环隐藏了迭代器或者下标的细节,使得代码更加简洁,减少了出错的可能性。
6.遍历过程中不可修改元素: 基于范围的 for 循环遍历容器时,不能在循环体内修改容器的元素值,因为 element_declaration 是元素的副本。
7.支持自定义类型: 对于用户自定义类型,只要实现了适当的迭代器或者 begin()、end() 函数,就可以使用基于范围的 for 循环进行遍历。
基于范围的 for 循环使得 C++ 中遍历容器更加简洁和直观,是 C++11 中的一个重要特性,被广泛应用于实际的编程中。
11.3 范围for的使用条件
基于范围的 for 循环适用于遍历能够提供迭代器的数据结构,包括但不限于:
1.标准库容器: 诸如 std::vector、std::list、std::deque、std::set、std::map 等标准库提供的容器都支持基于范围的 for 循环。
2.数组: 包括 C-style 数组(如 int arr[5])、std::array 等数组容器。
3.自定义类型: 如果自定义类型实现了适当的迭代器或者提供了 begin() 和 end() 函数,则可以使用基于范围的 for 循环。
使用基于范围的 for 循环的条件和限制如下:
4.提供迭代器支持: 数据结构必须提供迭代器,以便基于范围的 for 循环能够正确地迭代元素。通常,这意味着数据结构要么实现了标准库的迭代器接口,要么提供了 begin() 和 end() 函数。
5.可迭代性: 数据结构必须是可迭代的,即能够按顺序访问其元素。这通常包括顺序容器和关联容器,但也可能包括其他自定义类型,只要它们能够提供按顺序访问元素的方式。
6.元素类型可复制: 基于范围的 for 循环中的元素声明通常是变量的副本,因此元素类型必须是可复制的。如果元素类型不可复制(例如,std::unique_ptr),则需要使用 auto& 或 const auto& 以引用方式进行遍历。
7.不修改容器: 在基于范围的 for 循环中,循环体内不能修改容器的大小或结构,因为这可能会导致迭代器失效或者产生未定义的行为。
总之,只要数据结构提供了迭代器支持并且是可迭代的,就可以使用基于范围的 for 循环来遍历其元素。这种循环简洁明了,易于理解,并且在 C++11 中成为了标准编程实践的一部分。
12,c++nullptr
nullptr 是 C++11 中引入的空指针常量,用于代表空指针。在 C++ 中,以前通常使用 NULL 或 0 来表示空指针,但这样做存在一些问题,因为 NULL 和 0 可能被解释为整数而不是指针。为了解决这个问题,并明确表示空指针,C++11 引入了 nullptr。
nullptr 是一个关键字,它是一个字面常量,表示空指针。使用 nullptr 可以避免一些与类型安全相关的问题,并且更加清晰明了。
以下是 nullptr 的一些特点和用法:
1.类型安全: nullptr 是一个特殊的空指针常量,它没有特定的类型,可以隐式转换为任何指针类型,但不能隐式转换为整数类型,从而提供了更好的类型安全性。
2.清晰明了: 使用 nullptr 可以明确地表示空指针,使代码更加清晰易懂,避免了使用 NULL 或 0 可能带来的歧义。
3.替代NULL: 在 C++11 中,推荐使用 nullptr 来代替 NULL 或 0,以表示空指针。
4.可用于条件判断: nullptr 可以用于条件判断,例如在 if 语句中判断指针是否为空。
5.类型推导: 当使用 auto 关键字声明变量时,编译器会自动推导出 nullptr 的类型为 nullptr_t。
6.避免重载二义性: 使用 nullptr 可以避免函数重载时产生的二义性,因为它没有具体的类型。
示例用法:
int* ptr = nullptr; // 使用 nullptr 初始化指针
if (ptr == nullptr) {
// 检查指针是否为空
// 或者可以写成 if (!ptr)
// nullptr 在条件判断中会被隐式转换为 false
}
总的来说,nullptr 是 C++11 中一个重要的语言特性,它提高了代码的可读性和安全性,并且在现代 C++ 编程中被广泛应用。
13,每期一问
上期答案
bool _isSymmetric(struct TreeNode* p, struct TreeNode* q)
{
//对称二叉树的特点,root节点的左右子树相同
//当左子树和右子树同时走到空时,就表示递归遍历该路径途中的节点相同
if(p == NULL && q == NULL)
return true;
if(p == NULL || q == NULL)
return false;
//对比值是否相等
if(p->val == q->val)
return _isSymmetric(p->left, q->right) && _isSymmetric(p->right, q->left);
else
return false;
}
bool isSymmetric(struct TreeNode* root){
return _isSymmetric(root->left, root->right);
}
本期问题:. - 力扣(LeetCode)
到此我们就已经对c++有了初步的了解,接下来的学习,我们一起进步!!!