目录
一、命名空间
命名空间是为了防止出现一些函数和变量出现命名冲突的问题,比如:
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
printf("%d\n",rand);
return 0;
}
这里运行程序会报错,因为 stdlib.h 库中包含有 rand() 函数,与定义的 rand 变量会冲突
为了避免出现类似这类情况的命名冲突,需要应用到 namespace 关键字去定义命名空间
1、namespace
- 定义命名空间,需要在 namespace 关键字后面跟命名空间的名字(自己取的),再接上 { } 就可以,在命名空间中可以定义 变量 / 函数 / 类型 等
namespace myname
{
}
- namespace 的本质是定义一个域,域与全局域独立,不同的域可以定义同名变量
namespace myname
{
int a = 10;
}
namespace yourname
{
int a = 10;
}
- C++中有全局域、局部域、命名空间域、类域;域影响的是编译时语法查找一个 函数 / 变量 / 类型 出处(声明或定义)的逻辑,所以有了域的隔离,就可以解决命名冲突的问题。局部域和全局域除了会影响编辑查找逻辑,还会影响生命周期,命名空间域和类域不影响生命周期
//全局域 int a = 10; //命名空间域 namespace myname { int a = 10; } int main() { //局部域 int a = 10; return 0 }
- namespace 只能定义在全局,但可以嵌套
namespace myname { int a = 10; namespace yourname { int b = 20; } }
- 多文件中的同名 namespace 会合并,不会冲突
namespace myname { int a = 10; } namespace myname { int b = 20; }
- C++标准库都放在义个叫 std 的命名空间中
2、命名空间的使用
命名空间可以通过 " :: " 符号来访问
1)指定访问命名空间的变量
#include<stdio.h>
#include<stdlib.h>
namespace myname
{
int rand = 10;
}
int main()
{
printf("%d\n",myname::rand);
return 0;
}
这样,rand() 函数,与定义的 rand 变量就不会冲突
//全局域
int a = 10;
//命名空间域
namespace myname
{
int a = 10;
}
int main()
{
//局部域
int a = 10;
//访问全局域
printf("%d\n",::a);
//访问局部域
printf("%d\n",a);
//访问命名空间域
printf("%d\n",myname::a);
return 0
}
2)using 展开
- using 关键字可以指定展开命名空间中某个成员
#include<stdio.h>
namespace myname
{
int Add(int a, int b)
{
return 10 * (a + b);
}
int Sub(int a, int b)
{
return 10 * (a - b);
}
}
//指定展开命名空间某个成员
using myname::Add;
int main()
{
printf("Add:%d\n", Add(10, 20));
return 0;
}
- using 关键字可以展开指定命名空间所有成员
#include<stdio.h>
namespace myname
{
int Add(int a, int b)
{
return 10 * (a + b);
}
int Sub(int a, int b)
{
return 10 * (a - b);
}
}
//展开指定命名空间所有成员
using namespace myname;
int main()
{
printf("Add:%d\n", Add(20, 10));
printf("Sub:%d\n", Sub(20, 10));
return 0;
}
所以 using 还可以展开C++标准库
#include<iostream>
using namespace std;
二、C++的输入和输出
cout / cin / endl 都属于C++ 标准库,要通过命名空间的使用方式去使用
1、cout 输出
在C语言中," hello world " 的输出
#include<stdio.h>
int main()
{
printf("hello world!!!");
return 0;
}
而在C++中的输出
#include<iostream>
using namespace std;
int main()
{
cout << "hello world!!!" ;
return 0;
}
- << 表示的是流插入运算符,与之相反的 >> 表示的是流提取运算符,即类似于河流的流向方向,<< 即流向输出,>> 即流向变量
- 与 printf 不同的是,cout 可以主动识别变量类型,不需要通过 " %d,%s,%c,%f,%lf " 等方式输出
#include<iostream>
using namespace std;//std命名空间包含所有的C++标准库
int main()
{
int a = 10;
double b = 0.1;
char c = 'x';
cout << "int:" << a << " \n ";
cout << "double:" << b << "\n";
cout << "char:" << c << "\n";
return 0;
}
2、cin 输入
cin 与 scanf 类似,但不需要用到取地址符号 " & ",且与 cout 相同,不需要通过 " %d,%s,%c,%f,%lf " 等方式输入,可以自主地识别变量类型
#include<iostream>
using namespace std;//std命名空间包含所有的C++标准库
int main()
{
int a = 10;
double b = 0.1;
char c = 'x';
cout << "int:" << a << "\n";
cout << "double:" << b << "\n";
cout << "char:" << c << "\n";
cin >> a >> b >> c;
cout << "int:" << a << "\n";
cout << "double:" << b << "\n";
cout << "char:" << c << "\n";
return 0;
}
3、endl
endl 相当于 " \n " ,但不同的是 endl 是一个函数
#include<iostream>
using namespace std;//std命名空间包含所有的C++标准库
int main()
{
int a = 10;
double b = 0.1;
char c = 'x';
cout << "int:" << a << endl;
cout << "double:" << b << endl;
cout << "char:" << c << endl;
cin >> a >> b >> c;
cout << "int:" << a << endl;
cout << "double:" << b << endl;
cout << "char:" << c << endl;
return 0;
}
三、缺省参数
- 缺省参数是声明或定义一个函数是为函数的参数指定一个缺省值,在调用该函数时,如果没有指定实参,则采用该形参的缺省值,否则使用指定的实参。缺省参数分为全缺省和半缺省
- 全缺省即全部形参给缺省值,半缺省即部分形参给缺省值,C++规定半缺省参数必须从左往右依此给缺省值,不能间隔跳跃给缺省值
- 带缺省参数的函数调用,C++规定必须从左到右依此给实参,不能跳跃给实参
- 函数声明和定义分离时,缺省参数不能在声明和定义中同时出现,规定必须函数声明给缺省值
#include<iostream>
using namespace std;
//全缺省
void Add1(int a = 0, int b = 0, int c = 0)
{
cout << "全:" << a + b + c << endl;
}
//半缺省
void Add2(int a, int b = 0, int c = 0)
{
cout << "半:" << a + b + c << endl;
}
int main()
{
Add1();
Add1(10);
Add1(10,20);
Add1(10,20,30);
Add2(0);
Add2(10,20);
Add2(10,20,30);
return 0;
}
四、函数重载
C++支持在同一作用域出现同名函数,但需要这些同名函数的形参不同,可以是参数个数不同、参数类型不同,或者参数类型相同但类型顺序不同,因为这样可以通过形参明显分辨出需要调用的是哪个函数
#include<iostream>
using namespace std;
//参数个数不同
int Add(int a, int b)
{
return a + b;
}
int Add(int a)
{
return (a + b) * 5;
}
//参数类型不同
int Add(double a, double b)
{
return (a + b) * 10;
}
//参数类型相同但类型顺序不同
double Add(double a, int b)
{
return 20*(a + b);
}
double Add(int a, double b)
{
return 30*(a + b);
}
int main()
{
cout << Add(10, 10) << endl;
cout << Add(10) << endl;
cout << Add(10.0, 10.0) << endl;
cout << Add(10.0, 10) << endl;
cout << Add(10, 10.0) << endl;
return 0;
}
要注意的是不能出现要调用的函数无法分辨的情况
#include<iostream>
using namespace std;
void Add()
{
return 10+20;
}
Void Add(int a = 20)
{
return a + 20;
}
int main()
{
cout << Add() << endl;
return 0;
}
像这种情况,就会因为不知道要调用哪个函数而报错
五、引用
1、引用的概念和定义
引用不是新定义一个变量,而是给已经存在的变量取一个别名,编译器不会为引用变量和开辟内存空间,而是和引用的变量共用同一个内存空间,就比如秦始皇和嬴政都指向同一个人。
引用的底层代码也是通过指针实现的
类型& 引用别名 = 引用对象;
#include<iostream>
using namespace std;
int main()
{
int a = 100;
int& b = a;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;//可以看出 a、b 的地址是相同的,a 、b共用同一个空间
++b;
cout << "a:" << a << endl;//
int c = b;//这里不是新建引用别名,而是给 c 赋值
cout << "c:" << c << endl;
cout << "&c:" << &c << endl;
return 0;
}
2、引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,不能再引用其他实体(指针可以指向某一个地址,但可以修改,指向另一个地址)
3、const 引用
可以引用一个 const 对象,但是必须用 const 引用,引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大
#include<iostream>
using namespace std;
int main()
{
int a = 100;
int& b = a;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;
++b;
cout << "a:" << a << endl;
int c = b;
cout << "c:" << c << endl;
cout << "&c:" << &c << endl;
const int d = 100;
const int& e = d;
cout << "d:" << d << endl;
cout << "e:" << e << endl;
int f = 100;
const int& g = f;
cout << "f:" << f << endl;
cout << "g:" << g << endl;
return 0;
}
4、指针与引用的关系
- 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8字节)
- 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。
六、inline
-
用 inline 修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧,可以提高效率
-
编译器只会选择性展开内联函数,使用于频繁调用且短小函数,对于递归函数或代码多的函数,加上 inline 也会被编译器忽略,因为会增加代码量( 比如一个代码量为 4 的内联函数 和一个代码量为 20 的内联函数,在10000条代码中展开后 20*10000 远大于 4*10000 ,代码量大大增加,反而影响程序运行效率)
-
C语言实现宏函数也会在预处理时展开,但实现宏函数非常复杂且容易出错,且不方便调试,所以C++设计了内联函数就是为了替代C的宏函数
-
inline 不建议声明和定义分离到两个文件,分离会导致链接错误,因为 inline 被展开,没有函数地址,链接时会报错
-
vs编译器 debug 版本下默认不展开 inline ,方便调试
#include<iostream>
using namespace std;
inline int Add(int a, int b)
{
a++;
a++;
a++;
return a + b;
}
int main()
{
cout << Add(10 - 1, 20 + 22) * 10 << endl;
return 0;
}
七、nullptr
- NULL实际是一个宏,在C头文件(stddef.h)中,可以看到
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
- C++中 NULL 可能会被定义为字面常量 0 ,或者在C中被定义为无类型指针(void*)的常量,这样会出现以下情况
#include<iostream>
using namespace std;
void i(int a)
{
cout << "int a" << endl;
}
void i(int* a)
{
cout << "int* a" << endl;
}
int main()
{
i(NULL);
return 0;
}
调用时会出现只能调用第一个 i() 函数的情况,或者
i((void*)NULL);
会报错
- C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
#include<iostream>
using namespace std;
void i(int a)
{
cout << "int a" << endl;
}
void i(int* a)
{
cout << "int* a" << endl;
}
void d(double e)
{
cout << "double e" << endl;
}
void d(double* e)
{
cout << "double* e" << endl;
}
int main()
{
i(NULL);
i((int*)NULL);
i(nullptr);
d(0.0);
d((double)NULL);
d(nullptr);
return 0;
}