目录
学校这半年开了 C++的课程,有一些实验报告要写,刚好借实验的机会,对部分语法进行一个复习总结叭,常复习常新~
详细代码可以前文查看:
1.C++入门(上)
1.命名空间
namespace A
{
namespace B
{
namespace C
{
int a = 2;
}
}
}
int main()
{
printf("%d", A::B::C::a);
return 0;
}
- 以上代码中,嵌套了三层命名空间域,在访问a时,从外层到内层按照
A::B::C::a
访问。 - 所以可以按照如下方式解决不同文件变量可能存在冲突的问题:每个
.cpp
文件最外层,用一个命名空间域包含起来,后续引入文件时,每个人编写的文件独自享有一个域,就不会发生冲突问题了。
那如果这样的话,在main函数中想要访问其它文件内的内容是不是很冗余,几乎大部分变量都要加上::
前缀,这就太麻烦了。于是又产生了展开命名空间域这一功能。
展开命名空间域
- 所谓展开命名空间域,就是对某个空间域进行展开,将其内部的变量放到全局中。
using namespace (名称);
有时候我们并不是需要一个命名空间域中的所有内容,如果将整个空间域展开有些没必要。此时可以使用部分展开:
using (名称)::(变量名)
- 示例代码
//展开全命名空间
namespace user1
{
int a = 0;
int b = 1;
}
using namespace user1;
int main()
{
printf("%d", a);
printf("%d", b);
return 0;
}
//
//展开部分命名空间
namespace user1
{
int a = 0;
int b = 1;
int Add(int x, int y)
{
return x + y;
}
}
using user1::Add;
using user1::a;
int main()
{
printf("%d",a);
Add(3, 5);
return 0;
}
匿名命名空间
- C++中的命名空间可以匿名,此时这个命名空间无法从外部找到。
namespace
{
int localVar = 42;
}
- 假如在一个文件的最外层,嵌套一层匿名命名空间,那么相当于给所有变量添加了static属性,文件外部无法访问文件内部的变量。
2.输入输出
- 相比于C语言的输入输出,需要使用
%d
,%s
,%f
这样的占位符来控制输入类型。 - C++的输入输出操作明显的优势就是:自动识别类型。
- 其中
cout
可以拆分为c + out
,所以用于输出;cin
可以拆分为c + in
,所以用于输入
3.缺少参数
全缺省参数
- 缺省参数是值可以为函数的参数设置初始值,如果调用时没有传入参数,则此参数以初始值调用函数。
int Add(int x = 5, int y = 10)
{
return x + y;
}
int main()
{
Add(1, 2); //1+2
Add(1); //1+10
Add(); //5+10
return 0;
}
- 这种参数缺省叫做全缺省参数,即所有的参数都赋予了初始值,哪怕一个参数都不传,也可以调用函数。
- 注意:传入参数必须从左往右传入,不能有空缺。
int Add(int x = 5, int y = 10, int z = 20)
{
return x + y + z;
}
int main()
{
Add(1, ,6);
return 0;
}
- 此代码中
Add(1, ,6);
的意图是让x = 5
,z = 20
,让y
取初始值。但是这是不允许的,调用函数时,必须从左向右连续传入,不能间断地缺省参数。
半缺省参数
- 半缺省参数是指,缺省参数时,有一些值不赋予初始值,必须传入值。
int Add(int x = 5, int y, int z = 20)
{
return x + y + z;
}
此代码中,x
是被缺省的,那么其右边的y
和z
也必须被缺省,不能跳过y
直接缺省z
。
最后还有一个注意点:不能在声明和定义时同时缺省参数。
//.h
void func(int a = 10); // 函数声明
//
//.cpp
//重定义的错误写法
// void func(int a = 10) // 函数定义
// {
// cout << a * 5 << endl;
// }
void func(int a)
{
cout << a * 5 << endl;
}
- 将函数声明在了
test.h
文件中,又 定义在了test.cpp
文件中。 - 这样会造成重定义的错误,程序无法运行
如果想要将缺省参数声明在.h
文件中,那么在定义时就不要写出缺省参数了。
4.函数重载
- 函数重载是指C++允许在同一作用域中声明的同名函数
- 但是其必须遵守一项规则:保证同名函数的形参列表不同。
形参列表不同要求满足以下三者之一:
- 函数的参数 个数不同
- 函数的参数 类型不同
- 函数的参数 类型的顺序不同
- 原理就是编译器会根据识别到的形参,来选择调用哪个函数~
参数类型不同
void Add(int left, int right)
{
cout << "I am int Add" << endl;
}
void Add(double left, double right)
{
cout << "I am double Add" << endl;
}
参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
void f(int a, int b)
{
cout << "f(int a, int b)" << endl;
}
参数类型的顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
底层:去年文章中有详细解释过
5.引用
C++的引用是一种特殊的变量类型,用于给已经存在的变量起一个别名。
- 通过引用,可以通过一个已存在的变量名来访问和操作另一个变量的值。
- 引用可以被看作是一个已存在变量的别名
- 引用和被引用的变量始终指向同一块内存空间,对引用的操作实际上就是对被引用变量的操作。
type& 别名 = 变量名;
其中,type
是被引用变量的类型。
int main() {
int num = 10;
int& ref = num; // 创建一个引用ref,指向num
cout << "num的值为:" << num << endl; // 输出:num的值为:10
cout << "ref的值为:" << ref << endl; // 输出:ref的值为:10
// 通过引用修改num的值
ref = 20;
cout << "num的新值为:" << num << endl; // 输出:num的新值为:20
cout << "ref的新值为:" << ref << endl; // 输出:ref的新值为:20
return 0;
}
相当于:
int num = 10;
int* ref = #
*ref = 20;
需要注意的是,引用不同于指针,它不能指向空值或者没有初始化的变量。
因此,在定义引用时必须保证所引用的变量已经存在,并且在定义引用时必须进行初始化。
也就是说下面的语句是非法的:
int& a;
///
//指针
int* a;
int* b = NULL;
- a是一个引用,但是它没有初始化,此时编译器会报错。
- 但是在指针中,空指针与未初始化的指针是合法的。
引用其实不单单只是代替指针这么简单,其还可以作为返回值,参数等。
按引用传递
C++中的按引用传递是一种参数传递方式,它允许函数通过引用来操作调用者提供的实参。
- 按引用传递是将实参的引用传递给形参。
- 按引用传递的语法:在函数的参数前加上&符号。
void Swap(int& a, int& b) //采用引用,直接就可以 拿着用啦
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 1;
int y = 3;
Swap(x, y);
return 0;
}
//
//对比C
void Swap(int* a, int* b) //传指针
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int x = 1;
int y = 3;
Swap(&x, &y); //取地址
return 0;
}
按引用传递有以下几个作用:
1. 通过引用传递参数可以避免对大型对象的复制。
- 当传递一个大型对象时,按值传递会进行一次复制操作
- 而按引用传递只需要传递对象的引用而不需进行复制,从而提高了程序的效率。
2. 通过引用传递参数可以实现函数对实参的修改。
- 在函数内部,通过引用可以直接操作实参,对实参的修改会在函数外部产生影响。
- 而按值传递只能修改函数内部的形参副本,对实参没有影响。
返回引用
在C++中,返回引用是指从函数中返回一个引用类型的值。
- 返回引用的主要目的是允许函数返回一个对于某个变量的引用,从而允许在函数外部对该变量进行修改。
返回引用的主要用途有以下几个:
- 允许函数直接修改函数外部的变量。
- 允许在函数调用中连续进行操作,类似于链式操作。
- 优化性能,避免创建临时对象。
下面通过案例来分别说明这几个功能:
1. 允许函数返回值,直接被函数外部修改
int& increment(int& num)
{
num++;
return num;
}
int main()
{
int num = 5;
increment(num) = 10;
cout << num << endl; // 输出为 10
return 0;
}
2. 允许函数返回值,被连续进行操作:
int& add(int& num, int value)
{
num += value;
return num;
}
int main()
{
int num = 5;
add(add(num, 3), 2);
cout << num << endl; // (5+3)+2 ==10
return 0;
}
//第一次的返回值
//又被当作第二次的第一个参数进行了一个传入
3. 优化性能,避免创建临时对象:
string& concatenate(string& str1, const string& str2)
{
str1 += str2;
return str1;
}
int main()
{
string str1 = "Hello";
string str2 = " World";
concatenate(str1, str2) += "!";
//通过返回引用,可以直接对str1进行修改,避免了创建临时对象
cout << str1 << endl; // 输出为 "Hello World!"
return 0;
}
vs 指针
汇编对比
对比特性 |
引用 (Reference) |
指针 (Pointer) |
定义本质 |
变量的别名,与实体共享内存地址 |
存储变量地址的独立变量 |
初始化要求 |
必须初始化,且绑定后不可更改绑定实体 |
允许不初始化(可能成为野指针) |
指向可变性 |
始终指向初始化的实体,不可变更 |
可随时指向同类型的不同实体或置为 |
空值允许性 |
不存在空引用( 引用非法) |
允许指向 或空地址 |
|
返回引用实体类型的大小 (如 |
返回指针变量本身占用的地址空间大小 (如 |
自增操作语义 |
对实体值自增(如 修改实际变量值) |
对地址偏移自增(如 按类型大小移动地址) |
多级间接访问 |
仅支持单级引用(如 |
支持多级指针(如 |
访问实体方式 |
隐式解引用(编译器自动处理) |
需显式使用 解引用(如 |
安全性 |
更高(无空引用风险,绑定后不可变更) |
更低(可能空指针、野指针、内存泄漏风险) |
sum:
- 语义差异:引用是别名,指针是地址容器。
- 安全性:引用天然规避空引用和悬垂引用(需注意作用域问题),指针需手动管理有效性。
- 灵活性:指针支持更复杂的操作(如地址运算、多级间接访问),引用提供更简洁的语法。
- 使用场景:引用常用于函数参数传递和返回值优化,指针用于动态内存管理或底层操作。