1.c++关键字
2.命明空间
3.c++输入输出
在C++中,输入输出操作主要通过输入输出流来实现,以下是相关介绍:
1. 头文件
需包含 <iostream> 头文件,它提供了用于输入输出的类和对象等相关定义。
2. 标准输出流(cout)
- 基本用法:
std::cout 是标准输出流对象,用于向标准输出设备(通常是屏幕)输出数据。使用 << 运算符来连接要输出的数据,示例如下:
上述代码先输出字符串 "The number is: " ,接着输出变量 num 的值, std::endl 表示换行,相当于 \n ,但它还会刷新输出缓冲区。
- 输出不同类型数据:
可以输出多种类型的数据,如 char 、 int 、 double 、 string (需包含 <string> 头文件)等,例如:
3. 标准输入流(cin)
- 基本用法:
std::cin 是标准输入流对象,用于从标准输入设备(通常是键盘)获取数据。使用 >> 运算符来接收输入的数据并赋给相应变量,示例如下:
上述代码先提示用户输入一个整数,然后通过 std::cin 接收输入并存储到变量 num 中,最后输出用户输入的值。
- 连续输入:
可以连续使用 >> 运算符来接收多个不同类型变量的数据,例如:
此外,C++还提供了如 cerr (标准错误输出流,用于输出错误信息)、 clog (标准日志输出流)等不同用途的输出流,方便在不同场景下进行输入输出操作。
4.缺省参数
在C++中,缺省参数是一项很实用的特性,以下是关于它的详细介绍:
定义
缺省参数指的是在声明或定义函数时,为函数的参数指定一个默认值。这样在调用该函数时,如果没有为这个带有缺省值的参数传递实参,那么函数就会自动采用预先设定的默认值来进行运算。
分类
- 全缺省参数:函数的所有参数都设置了默认值。例如:
int add(int a = 1, int b = 2)
{
return a + b;
}
在调用 add 函数时,可以不传参数,像 int result = add(); ,此时函数内部就会按照默认值,即 a 为1、 b 为2来计算,结果为3;当然也可以传部分或全部参数,比如 int result2 = add(3); ,这时 a 的值为3, b 还是采用默认值2来参与运算。
- 半缺省参数:函数的部分参数设置了默认值。需要注意的是,设置了默认值的参数必须从右往左连续设置,例如:
int sub(int a, int b = 5)
{
return a - b;
}
调用时,可以像 int res = sub(8); 这样只传一个参数,函数会将传入的参数作为 a 的值, b 使用默认值5进行运算;但不能只给 b 传参而不给 a 传参,这种传参顺序不符合半缺省参数的规则。
使用规则与注意事项
- 声明与定义一致性:如果函数在声明和定义处都设置了缺省参数,那么两处设置的默认值要保持一致,否则可能导致编译错误或者结果不符合预期。
- 函数重载影响:缺省参数可能会和函数重载产生关联和影响,有时候设置了缺省参数的函数和另一个同名但参数列表稍有不同的重载函数,在调用时需要仔细区分,避免出现意外的调用结果。
总之,合理运用缺省参数可以让函数调用更加灵活方便,增强代码的复用性和可读性。
5.函数重载
函数重载是C++中一个重要的特性,以下是关于它的详细内容:
概念
函数重载指的是在同一个作用域内,可以有多个同名函数存在,但这些函数的参数列表必须不同。通过参数列表的差异来区分不同的函数,让编译器能够在调用时准确地知道该调用哪一个具体的函数。
构成函数重载的条件
- 参数个数不同:例如:
int add(int a, int b)
{
return a + b;
}
int add(int a, int b, int c)
{
return a + b + c;
}
这里有两个 add 函数,第一个接受两个整型参数,第二个接受三个整型参数,参数个数不同,构成了函数重载。
- 参数类型不同:比如:
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
这两个 add 函数虽然函数名相同,但参数类型不一样,一个是整型参数,一个是双精度浮点型参数,也构成了函数重载。
- 参数顺序不同:像这样:
void func(int a, char b)
{
// 函数实现内容
}
void func(char b, int a)
{
// 函数实现内容
}
两个 func 函数参数顺序不同,同样能构成函数重载。
注意事项
- 返回值类型不能用于区分重载函数:仅仅是返回值类型不同,而参数列表相同的函数,不能构成函数重载。例如,下面这样是错误的:
int add(int a, int b)
{
return a + b;
}
double add(int a, int b)
{
return (double)(a + b);
}
虽然返回值类型分别为整型和双精度浮点型,但参数列表一致,不符合函数重载的定义,编译器会报错。
- 函数重载在不同的调用场景中要保证调用的准确性:尤其在存在类型转换等复杂情况时,要留意编译器根据参数匹配的规则来准确调用相应的重载函数,避免出现意外的调用结果。
6.引用
在C++中,引用(Reference)是一种很重要的概念,以下是关于它的详细介绍:
概念
引用可以看作是一个已存在变量的别名,通过这个别名能对其所关联的变量进行操作,就好像操作引用本身就是直接操作对应的那个变量一样。
定义和初始化
引用在定义时必须进行初始化,指定它所引用的对象,并且一旦初始化绑定了某个变量后,就不能再改变去引用其他变量了。定义引用的语法形式为: 类型& 引用名 = 变量名; ,例如:
int num = 10;
int& ref = num; // ref就是num的引用,是num的别名
这里 ref 就是对 num 的引用,后续对 ref 进行的操作,如 ref = 20; ,实际上就是对 num 进行操作,此时 num 的值也会变为20。
常引用
- 常量引用可以绑定常量:当我们不希望通过引用去修改所关联的对象时,可以使用常引用。常引用可以用 const 关键字来定义,比如:
const int num2 = 30;
const int& ref2 = num2; // 正确,常引用能绑定常量
常引用还能绑定非常量对象,但通过这个常引用不能对对象进行修改操作,例如:
int num3 = 40;
const int& ref3 = num3; // 正确,常引用绑定非常量对象
// ref3 = 50; // 错误,不能通过常引用修改对象的值
引用作为函数参数
引用常被用作函数参数,对比传值调用,它可以避免在函数调用时进行大量的数据复制,提高效率,同时在函数内部能直接修改传入的实参变量的值。例如:
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
在调用 swap 函数时,像 int x = 5, y = 10; swap(x, y); ,就能直接交换 x 和 y 的值了。
引用作为函数返回值
函数也可以返回引用,但要确保返回的引用所指向的对象在函数结束后依然存在,否则会出现悬空引用等错误情况。例如:
int& getNum()
{
static int num = 100; // 使用静态变量,函数结束后其依然存在
return num;
}
7.内联函数
内联函数是C++中用于优化程序性能的一种机制,以下是其相关介绍:
概念
内联函数在编译阶段,编译器通常会尝试将函数的代码直接嵌入到调用它的地方,而不是像普通函数那样进行函数调用的流程(如保存现场、跳转等开销),以此来减少函数调用的时间成本,提高程序执行效率,尤其适用于函数体比较短小且频繁被调用的情况。
定义方式
可以通过在函数声明或定义前加上 inline 关键字来将其标记为内联函数,例如:
inline int add(int a, int b)
{
return a + b;
}
这里的 add 函数就是一个内联函数,编译器在处理代码中调用 add 函数的地方时,大概率会把函数体代码直接展开在调用处。
使用注意事项
- 建议函数体简短:内联函数的初衷是减少函数调用开销,如果函数体过长,比如包含大量复杂逻辑、循环语句等,编译器可能不会将其真正内联展开,即便加了 inline 关键字也不一定能达到预期效果,甚至可能因代码膨胀导致程序性能下降。
- 定义位置:内联函数一般建议将其定义放在头文件中。因为内联函数在编译阶段就需要展开,不同的源文件如果调用了该内联函数,编译器要能看到完整的函数定义才能正确展开,所以常把内联函数定义放在头文件里,然后在需要的源文件中包含这个头文件。
- 与宏的区别:虽然内联函数和宏( #define 定义的宏)在某些方面有相似之处,比如都可以让代码看起来像是直接嵌入的,但内联函数是真正的函数,遵循函数的语法规则,有类型检查等机制,而宏只是简单的文本替换,容易出现一些意想不到的错误(如没有类型检查、宏展开可能导致的优先级问题等),内联函数相对更安全、规范。
内联函数和宏定义在C++中有以下区别:
1. 语法与本质
- 内联函数:是真正的函数,需要遵循函数的语法规则来定义,有返回值类型、参数列表等,在编译阶段由编译器处理,会进行类型检查、语法检查等操作,例如
inline int add(int a, int b)
{
return a + b;
}
- 宏定义:通过 #define 预处理指令来定义,本质上是一种简单的文本替换机制,在预处理阶段进行替换,不存在类型、语法等检查,比如:
#define ADD(a,b) (a + b)
2. 安全性与错误检查
- 内联函数:由于是函数形式,编译器会对其进行严格的类型等方面的检查,能发现诸如参数类型不匹配等错误,代码安全性较高。
- 宏定义:只是单纯的文本替换,如果定义或使用不当,很容易出现错误。例如宏展开后的运算优先级问题,像 #define MUL(a, b) a * b ,当使用 MUL(3 + 2, 4) 时,展开后是 3 + 2 * 4 ,结果并非期望的 (3 + 2) * 4 ,而且不会有报错提示,所以安全性低。
3. 参数处理
- 内联函数:参数传递像普通函数一样有实际的传参过程,遵循函数调用的参数传递机制,每个参数都是独立的实体,有自己的类型和值。
- 宏定义:在替换时是直接把参数对应的文本进行替换,没有真正意义上的参数传递概念,更像是简单的文本嵌入。
4. 作用域和可见性
- 内联函数:有明确的作用域规则,和普通函数类似,遵循C++语言中关于函数在不同代码块、文件等场景下的作用域及可见性要求。
- 宏定义:一旦定义,从定义处开始到文件末尾(除非被 #undef 取消定义)都有效,作用范围相对更宽泛、灵活,但也容易出现命名冲突等问题。
5. 调试便利性
- 内联函数:可以像普通函数一样在调试器中进行调试,方便追踪执行流程、查看变量值等。
- 宏定义:因为只是文本替换,调试起来难度较大,很难像函数那样清晰地查看其执行情况和中间状态。
8.auto关键字(c++11)
在C++11中引入的 auto 关键字有很重要的作用,以下是关于它的介绍:
1. 基本概念
auto 关键字能让编译器根据变量初始化表达式的类型自动推断出变量的类型。这样在声明变量时,就无需显式地写出变量的具体类型了,尤其适用于类型名称比较长或者类型难以明确写出的情况,能使代码更加简洁。
2. 用法示例
- 基本类型推断:
例如,对于一个 std::vector 容器,使用迭代器时,传统写法可能比较繁琐:
std::vector<int> vec = { 1, 2, 3 };
std::vector<int>::iterator it = vec.begin();
而使用 auto 关键字就简单多了:
std::vector<int> vec = { 1, 2, 3 };
auto it = vec.begin();
编译器会根据 vec.begin() 返回值的类型自动推断出 it 的类型,这里 it 就是 std::vector<int>::iterator 类型。
- 复杂类型推断:
当函数返回一个比较复杂的类型时,利用 auto 也能方便地接收返回值,比如:
auto func()
{
std::map<std::string, std::vector<int>> myMap;
// 对myMap进行一些操作
return myMap;
}
auto result = func();
编译器会依据 func 函数实际返回的 std::map<std::string, std::vector<int>> 类型来确定 result 的类型。
3. 注意事项
- 不能用于函数参数类型推断: auto 关键字目前只能用于声明变量时进行类型推断,不能用在函数的参数列表中来推断参数的类型,例如这样是错误的:
void myFunction(auto param)
{ // 错误,不能这样用
// 函数实现
}
- 初始化必须明确:使用 auto 声明变量时,变量必须有初始化表达式,编译器才能据此推断类型,像 auto num; 这样没有初始化的写法是不允许的。
总之, auto 关键字在C++11及后续版本中给编程带来了便利,让代码书写更加简洁高效,不过也要遵循相应的使用规则。
9.基于范围的for循环(c+11)
基于范围的for循环是C++11引入的一个很实用的特性,以下是它的详细介绍:
概念
基于范围的for循环提供了一种简洁方便的方式,用于遍历容器(如 std::vector 、 std::array 等)、数组以及其他可迭代对象中的元素,无需像传统循环那样显式地操作迭代器或者指定循环的起止范围,让代码更加清晰易读。
语法形式
其基本语法格式为:
for (元素类型 变量名 : 可迭代对象)
{
// 循环体,对变量名代表的元素进行操作
}
例如,遍历一个 std::vector 容器:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec = { 1, 2, 3, 4, 5 };
for (int element : vec)
{
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
在上述代码中, int element 声明了一个变量用来接收容器 vec 中的每个元素,每次循环时, element 就代表了 vec 中的一个元素,循环依次取出元素并输出,最终会输出 1 2 3 4 5 。
注意事项
- 修改元素问题:默认情况下,循环变量是元素的副本(对于基本类型等情况),在循环体中修改循环变量,并不会改变原可迭代对象中的对应元素。如果想要修改原对象中的元素,对于像 std::vector 这样的容器,需要将循环变量声明为引用类型,例如:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec = { 1, 2, 3, 4, 5 };
for (int& elem : vec)
{
elem++; // 此时修改elem会改变vec中对应的元素
}
for (int element : vec)
{
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
上述代码中,第二次输出时会发现 vec 中的元素都被加1了,变成了 2 3 4 5 6 。
- 适用对象范围:它主要适用于实现了合适的迭代器相关功能、支持范围遍历的对象,像普通的内置数组、标准库中的多数容器等都可以使用,但一些自定义的、未按照规范实现迭代器功能的对象可能无法直接使用基于范围的for循环来遍历。
基于范围的for循环简化了对可迭代对象的遍历操作,让代码更简洁、直观。
10.指针空值-nullptr(c++)
在C++中, nullptr 用于表示指针空值,以下是关于它的详细介绍:
1. 引入背景
在早期C++版本中,常使用 NULL 来表示空指针,而 NULL 实际上是一个宏,它一般被定义为整数0(在C语言中这样定义没问题,因为C语言里指针和整数在一定程度上可以隐式转换)。但在C++里,这样的定义容易引发一些类型不匹配等混淆情况,例如在函数重载场景中,如果有一个函数接受指针参数,另一个接受整型参数,传入 NULL 时就可能出现意外的调用结果。所以C++11引入了 nullptr 来专门明确地表示指针空值。
2. 特点与用法
- 类型安全: nullptr 是一种特殊的字面值,它的类型为 std::nullptr_t ,在涉及指针操作时能确保类型安全。比如在初始化指针变量或者给指针赋值为空时,可以这样使用:
int* ptr = nullptr; // 正确地将指针初始化为空值
-函数重载区分:在有函数重载且参数涉及指针和其他类型(如整型等)区分的情况下,使用 nullptr 能保证调用的准确性。例如:
void func(int* p)
{
std::cout << "Called func with pointer parameter" << std::endl;
}
void func(int num)
{
std::cout << "Called func with integer parameter" << std::endl;
}
int main()
{
func(nullptr); // 会准确调用接受指针参数的func函数
return 0;
}
如果这里用 NULL (其本质是0),可能就会错误地调用接受整型参数的函数了,而使用 nullptr 则能正确匹配到接受指针参数的 func 函数。
3. 与其他表示空值方式对比
和用 0 或者 (void*)0 来表示空指针相比, nullptr 更符合C++的类型安全要求,避免了因隐式类型转换带来的潜在错误,使代码在处理指针空值情况时更加清晰、准确。
总之, nullptr 在C++中是表示指针空值的规范且安全的方式,在指针相关操作以及避免一些因空指针表示引发的混淆场景中起着重要作用。
看到这里的话,能不能给我点赞,收藏,关注呢