
C++是兼容C的,所以我们可以在C++环境下写C语言。
C++之父:本贾尼・斯特劳斯特卢普。
一、命名空间
cpp
#include<stdio.h>
#include<stdlib.h>
int main() {
int rand = 10;
printf("%d\n", rand);//代码可以正确执行
}
cpp
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main() {
printf("%d\n", rand);//会编译报错
}
思考:stdlib.h中存在rand这个函数,那么我们确自定义了rand这个变量。当random是局部变量时,代码可以成功执行,当random是全局变量时,代码确会引起编译报错,这是什么原因呢?
下面我们需要了解C++的标识符查找规则和引起标识符命名冲突的本质原因。
(1)C++标识符查找规则
标识符:变量、函数、类、常量、命名空间。
编译器查找标识符时,会从当前最内层的作用域开始查找;如果找到了,就不会再去外层作用域查找。
(2)引起标识符命名冲突的本质原因
同一作用域下不能存在同名的标识符。
那么用怎么样才能避免编译报错呢?
这里就引入了命名空间的概念。
(3)命名空间
①概念
命名空间就是我自己造了一个域,用来存放标识符。用来解决同一个域下命名冲突的问题。
用(命名空间名::标识符)来访问我域里的标识符。
②语法
语法:
cpp
namespace 命名空间名 {
// 你自定义的标识符
}
③解决标识符冲突问题
cpp
#include<stdio.h>
#include<stdlib.h>
namespace mycode{
int rand = 10;
}
int main() {
printf("%d\n", mycode::rand);//正常打印出10
}
那么在同一个命名空间下,标识符一样该怎么处理,这里我们就需要了解命名空间的嵌套。
(4)命名空间的嵌套
①概念
在父域里辟多个子域。用来解决父域中标识符冲突问题。用(父域命名空间名::子域1::标识符)来访问我域里的标识符。
②语法
cpp
namespace 外层命名空间名 {
namespace 内层命名空间名1 {
// 你自定义的标识符
}
namespace 内层命名空间名2 {
// 你自定义的标识符
}
//......
}
③示例
cpp
#include<stdio.h>
namespace code{
namespace codeA {
int a = 10;
}
namespace codeB {
int a = 20;
}
}
int main() {
printf("%d\n", code::codeA::a);//打印10
printf("%d\n", code::codeB::a);//打印20
}
(5)命名空间的使用
①指定命名空间访问
就是我们前面用的::
②展开某个成员访问
就是访问命名空间里的某个成员。
语法:
cpp
using 命名空间名::成员;
示例:
cpp
#include<stdio.h>
namespace name {
int a = 3;
int b = 6;
}
int main() {
using name::a;//展开某个成员
printf("%d\n", a);
}
③展开命名空间的全部成员
语法:
cpp
using namespace 命名空间名;
示例:
cpp
#include<stdio.h>
namespace name {
int a = 3;
int b = 6;
}
int main() {
using namespace name;//展开全部成员
printf("%d\n", a);
printf("%d\n", b);
}
(6)补充
在多个文件使用命名空间时
C++ 不允许同一个作用域下有两个同名的类或变量,但允许同一个命名空间被写在不同的文件、不同的位置,编译器会自动把它们合并成一个。
C++标准库都存放在一个名叫std(standard)的命名空间中。
二、C++的输入输出
(1)<iostream>
是Input Output Stream的缩写,为你提供了输出输出(cin、cout)和操作规则(<<、>>)。
(2)std::cin和std::cout
std::cin是在控制台输入的意思,我们知道C++的标准库都放在std这个命名空间里。所以前面要加上std::,这里面的c是console(控制台)的意思,in是input(输入的意思)。
std::cout与上面唯一的区别是out是输出的意思。所以表示的是在控制台输出的意思。
(3)<< 和 >>
<<是流插入运算符,和cout搭配使用。例如std::cout<<"hello world"。把hello world在终端打印出来。可以一次打印多个内容。
cpp
#include<iostream>
int main() {
using namespace std;
cout << "hello world" << endl;
cout << "我的刀盾喜欢奶龙" << endl << "咕咕嘎嘎" << endl;//在屏幕上打印两行内容
}
>>是流提取运算符,和cin搭配使用。也可以一次输入多个内容。
cpp
#include<iostream>
int main() {
using namespace std;
int a,b;
cout << "请输入两个整数:";
cin >> a >> b;
cout << "两个数之和为:" << a + b << endl;
}
需要注意的C++可以自动识别变量类型,不需要我们手动输入占位符。
(4)endl
endl表示换行的意思。
三、缺省参数
(1)基本概念
定义函数时,给定参数一个默认值(这个值也叫做缺省值)。在传参数时,如果不传参数就使用默认值,传参数就使用你传的参数。
cpp
#include<iostream>
using namespace std;
void Print(int a= 3) {
cout << a << endl;
}
int main() {
Print();//打印3
Print(6);//打印6
}
(2)全缺省
给函数的全部参数设置缺省值。
cpp
#include<iostream>
using namespace std;
void Print(int a= 3,int b = 2) {
cout << a << "," << b << endl;
}
int main() {
Print();//打印3,2
Print(0,0);//打印0,0
}
(3)半确省
给函数的部分参数设置缺省值。半缺省中,C++规定缺省值必须从右向左连续传。在使用时,必须传够没用缺省值的参数,且是按照从左向右传的顺序。
cpp
#include<iostream>
using namespace std;
void Print(int a, int b = 1, int c = 2) {
cout << a << "," << b << ","<< c << endl;
}
int main() {
//Print();//没有传够参数,会触发编译错误
Print(0);//打印0,1,2半缺省起码传一个
Print(6,6);//打印6,6,2
Print(5,5,5);//打印5,5,5
}
函数声明和定义分离时,函数的缺省参数只能出现一次,且出现在声明部分。
cpp
#include<iostream>
using namespace std;
void Print(int a, int b = 3);//函数声明部分(缺省值必须写在声明里)
void Print(int a , int b) {//函数定义部分
cout << a << "," << b << endl;
}
int main() {
Print(3);//打印3,3
Print(2,6);//打印2,6
}
四、函数重载
C++规定在同一作用域内,可以存在一个或多个同名函数。但是这个同名函数需要满足参数个数不同 || 参数类型不同 || 参数顺序不同(参数类型的顺序不同)。
cpp
#include<iostream>
using namespace std;
int Add(int a, int b, int c) {//参数个数不同
return a + b + c;
}
int Add(int a, int b) {
return a + b;
}
int main() {
int a = Add(1,1,1);
int b = Add(1, 1);
cout << a << endl << b << endl;
}
cpp
#include<iostream>
using namespace std;
int Add(int x,int y) {//参数类型不同
return x + y;
}
double Add(double x, double y) {
return x + y;
}
int main() {
int a = Add(2, 3);
double b = Add(1.2, 1.3);
cout << a << endl << b << endl;
}
cpp
#include<iostream>
using namespace std;//参数类型不同
double Calc(int a,double b) {
return a + b;
}
double Calc(double a,int b) {
return a - b;
}
int main() {
double a = Calc(2, 2.2);
double b = Calc(2.2, 2);
cout << a << endl;//a的参数第一个是整型,第二个是浮点型,所以调的是加法函数(4.2)
cout << b << endl;//b的参数第一个是浮点型,第二个是整型,所以调的是加法函数(0.2)
}
五、引用(reference)
(1)基本概念
给已有的变量起一个别名。直接在原内存上操作这个变量。
(2)语法
类型& 引用别名 = 引用对象;
以我们以前常写的Swap函数为例,我们来体会引用的妙处。
cpp
#include<iostream>
using namespace std;
void Swap(int* a, int* b) {//用指针的写法
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int a = 2, b = 3;
cout << "交换前" << "a = " << a << " " << "b = " << b << endl;
Swap(&a, &b);
cout <<"交换后" << "a = " << a << " " << "b = " << b << endl;
}
cpp
#include<iostream>
using namespace std;
void Swap(int& a, int& b) {//用引用的写法
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 2, b = 3;
cout << "交换前" << "a = " << a << " " << "b = " << b << endl;
Swap(a, b);
cout << "交换后" << "a = " << a << " " << "b = " << b << endl;
}
(3)引用的要求
①初始化
对于局部/全局使用引用时必须进行初始化。比如顺序表的初始化,在main函数内部使用引用。
cpp
#include<iostream>
using namespace std;
int main() {
int a = 10;
int& b = a;//引用时必须进行初始化
cout << &a << endl;
cout << &b << endl;//在这里可以看到a,b是同一个地址
}
对于函数形参里的引用,你传的实参就是已经完成了初始化。比如前面的Swap函数。你传完参数,其实就完成了对形参的初始化。
②命名
对于不同作用域下引用的别名可以相同,比如我们上面的Swap函数。同一作用域下引用的别名不能与原变量的名字相同。
③多个引用
一个变量可以有多个引用,一个引用的改变一定会改变原变量和其它引用的值。
cpp
#include<iostream>
using namespace std;
int main() {
int a = 2;
int& b = a;
int& c = a;//这里可以写a也可以写b
cout << "变化前" << a << ","<<b << ","<< c << endl;
c++;
cout << "变化后" << a << "," << b << "," << c << endl;//这里可以观察到
//a,b,c的值均发生了变化
}
④引用的绑定问题
引用一旦绑定某个实体(有内存地址,能存储数据的对象;例如变量、数组),就终身只是这个实体的别名。再次对引用的操作,只是在修改引用的值,内存永远都是那一块。
cpp
int main() {
int a = 2;
int b = 3;
int& ra = a;
ra = b;//只是赋值操作
//int& ra = b;//重定义错误
}
⑤引用的优点
引用在实践中往往用于函数传参或者做返回值。因为它直接对内存进行操作,就减少了拷贝从而提高了时间效率。
六、const引用
(1)概念
被const修饰的引用。原数据进行const引用后权限只能不变或者变小。
(2)权限变化
原变量权限为只读,const引用后权限还是只读。
cpp
int main() {
const int a = 3;//权限为只读,不能修改 a = 3的值
const int& ra = a;//权限仍然为只读,不能修改 ra = 3的值
//int& ra = a;//错误写法,权限不能被放大
}
原变量为可读可写,const引用后权限为只读。
cpp
int main() {
int a = 3;//可读可写
const int& ra = a;//只读
a = 6;//正确写法,没有超出权限
//ra = 3;//错误写法,超出了权限
}
(3)const引用修饰临时变量
①什么是临时变量
变量是指内存空间和存储的值。
临时变量是指为了保证代码能跑,编译器自己制定的一份临时空间。生命周期随着这条语句的执行结束立马销毁。
常见的有单独的常量(表达式算出来没有用变量存储的也算),不同类型转化时触发的临时变量的创建。
②const引用可以修饰临时变量的原因
对于普通的引用只能绑定有内存地址的变量。核心原因是普通的引用可读可写,对于临时变量这种悄么的产物,本身就不安全。你还权限放大,这不就太过分了。
而const引用可以修饰临时变量的主要原因就是你临时变量虽然本身就不安全。反正我只读,都会把你改为安全的,反正不会对你进行操作。唯一的变化就是延长了临时变量的生命周期,const引用什么时候销毁,临时变量什么时候销毁。
③const引用修饰常量
cpp
int main() {
const int& ra = 20;//安全
int& rra = 20;//不安全,属于权限放大
}
cpp
int main() {
int a = 20;
const int& ra = a * 3;//安全
int& rra = a * 3;//不安全,权限放大
}
④const引用修饰不同类型转化
cpp
int main() {
int a = 3;
const double& ra = a;//安全
double& rra = a;//不安全,权限放大
}
(4)引用和指针的区别
①语法:引用只是给变量取一个别名,不需要单独再开空间。而指针是存储地址的变量,所以需要单独开空间。
②初始化:引用在定义时必须进行初始化;而指针一般建议初始化,避免野指针。
③特性:引用在绑定一个对象后,就不能再改变;而指针绑定一个对象后可以换帮绑。
④访问:引用可以直接访问对象;而指针需要解引用才能访问对象。
⑤大小:引用的大小取决于对象的大小;指针的大小取决于操作系统的位数(32位下占4个字节;64位下占八个字节)
⑥安全性:指针很容易出现空指针和野指针;而引用不存在这个问题。
七、宏
(1)概念
宏是在预处理阶段,给一段文本(值/表达式/代码)绑定一个宏名(符号),分为有参宏和无参宏。
(2)无参宏
语法:
cpp
#define 宏名 替换文本
例子:
cpp
#define val 18
int main() {
using namespace std;
cout << val << endl;//在执行程序是编译器会自动将val换成18
}
(3)有参宏
语法:
cpp
#define 宏名 (参数1,参数2,......) 替换文本//括号里是参数列表
例子:
cpp
#define Add(a,b) ((a) + (b))
int main() {
using namespace std;
cout << Add(2, 3) << endl;//输出5
}
注意:这里a和b加括号,原因是为了避免运算符优先级带来的计算错误。
八、inline
(1)概念
被inline修饰的函数叫做内联函数。inline的作用是将代码在调用处展开,不需要再建议栈帧了,提高了效率。
(2)注意
inline只是建议,编译器可以采纳也可以不采纳。一般内联函数都是几行的小代码。
cpp
#include<iostream>
using namespace std;
inline int Add(int a,int b) {
return a + b;
}
int main() {
cout << Add(2, 3) << endl;//就是这个意思cout << 2+3 << endl;
}
(3)内联函数和宏的区别
①本质:内联函数是函数;宏是文本替换
②运算符优先级:内联函数不存在问题;宏需要手动添加括号
③调试:内联函数可以调试,宏不能调试
④编译器:编译器可以拒绝内联;编译器不能拒绝宏,宏是强制替换
八、nullptr
(1)概念
nullptr是C++11引入的一个关键字,用来表示空指针。
(2)引入的原因
NULL在C++表示的是0的意思,如果在函数重载时,就会发生到底读取错误。
cpp
#include<iostream>
using namespace std;
void Print(int* x) {
cout << "int*" << endl;
}
void Print(int x) {
cout << "int" << endl;
}
int main() {
Print(NULL);//你预期的结果是int,可是运行结果确是int
Print(nullptr);//符合预期结果
}