✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C++
个人主页:Celia's blog~
目录
一、C++简介
二、第一个C++程序
三、namespace 命名空间
3.1 namespace的定义
3.2 命名空间中成员的访问
3.2.1 域作用限定符 ::
3.3 命名空间的使用
四、C++的输入输出
五、缺省参数
5.1 缺省参数的使用规定
六、函数重载
七、引用
7.1 引用的概念
编辑 7.2 引用的特性
7.3 引用的使用
7.4 const引用
7.5 引用和指针的关系
八、inline 内联函数
九、nullptr
一、C++简介
C++(c plus plus)是一种计算机高级程序设计语言,由C语言扩展升级而产生 ,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。
C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。 C++几乎可以创建任何类型的程序:游戏、设备驱动程序、HPC、云、桌面、嵌入式和移动应用等。 甚至用于其他编程语言的库和编译器也使用C++编写。
C++拥有计算机运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。
二、第一个C++程序
C++由C语言演变而来,有着自己的一套全新语法,同时也兼容了C语言的绝大部分语法。在创建C++新文件时,文件的后缀要改成xxx.cpp。
//C语言版本
#include<stdio.h>
int main()
{printf("%s", "Celia");return 0;
}
//C++版本
#include<iostream> //io流 可以类比于C语言的<stdio.h>进行理解
using namespace std;
int main()
{cout << "Celia" << endl; //输出字符串并换行return 0;
}
三、namespace 命名空间
在C语言中,每一个变量都有属于自己的作用域。例如下面的代码:
#include<stdio.h>int rand = 10;
int main()
{printf("%d", rand);return 0;
}
这段代码定义了一个全局变量rand 并赋值为10,在main函数中可以正常打印这个全局变量的值。这时我们新加上一个头文件<stdlib.h>,就会出现如下编译报错,让人费解:
出现这个报错的原因是由于<stdlib.h>这个头文件中包含了一个rand函数,与全局变量名字相同,编译器进行识别时就出现了错误,无法判断rand是全局变量还是函数。
#include<stdio.h>
#include<stdlib.h>//errorint rand = 10;
int main()
{printf("%d", rand);return 0;
}
在实际的项目开发中,如果两人的代码块中某个函数、变量的名字相同,在合并两个代码块的时候就会出现报错,这不利于实际开发。至此,C++中额外引入了一个关键字namespace。
3.1 namespace的定义
- 定义一个命名空间,需要用到关键字namespace,后面加上空间名,然后加上一对花括号{}。在花括号中可以定义变量、函数、结构体。
- namespace本质上是定义了一个全新独立的域,在不同的域中可以有相同的变量名/函数名。
- namespace只能定义成全局,也可以嵌套定义。
- 在C++中,有全局域、局部域、类域、命名空间域。域会影响编译时的语法查找逻辑,有了域的隔离,在不同的域中相同名字的函数/变量报错的问题就得到了很好的解决。全局域和局部域会影响生命周期,类域和命名空间域不会影响生命周期。
- 如果定义了多个同名的namespace,这些同名的namespace会被认为是一个namespace,不会冲突。
- C++的标准库都放在一个叫做std的命名空间中。
//定义了一个叫Celia的命名空间
namespace Celia
{int a = 10;char b = 'x';
}
3.2 命名空间中成员的访问
3.2.1 域作用限定符 ::
我们来看下面的代码示例:
#include<iostream>
using namespace std;int a = 5;namespace Celia
{int a = 10;char b = 'x';
}int main()
{int a = 3;cout << a << endl; //结果是什么?return 0;
}
这段代码会优先在局部域中寻找变量a并输出 ,如果我们想要访问到全局变量a,就需要用到域作用限定符 :: 。
cout << ::a << endl;//输出全局变量a
域作用限定符 :: 默认访问全局的变量/函数。只需要在变量名/函数名前加上 :: 即可。若是想访问到Celia命名空间的变量a,就需要在 :: 前加上Celia。
cout << Celia::a << endl;//输出Celia命名空间中成员整型变量a
3.3 命名空间的使用
我们想要使用命名空间中的成员,主要有以下三个方法:
- 指定命名空间访问,如:Celia::a。
- 使用using将命名空间的某个成员展开,如 using Celia::a。
- 展开命名空间中的全部成员,如 using namespace Celia。
展开的成员不需要再重复使用域作用限定符 :: 修饰即可访问。如果使用域作用限定符,语法上也不算错。
#include<iostream>
using namespace std;
namespace Celia
{int a = 10;char b = 'x';
}using Celia::a; //仅仅展开Celia命名空间成员变量a
using namespace Celia; //展开Celia命名空间全部成员int main()
{cout << a << endl; //已展开,直接访问即可。如果局部有变量a,仍优先访问局部变量cout << Celia::a << endl; //指定域名访问return 0;
}
注意:如果在同一个域中,展开的成员和域中现有的成员同名,会报错。
在之前的代码中,曾出现以下语句:
using namespace std;
这段代码是展开了C++语言标准库中的所有成员,为了编程的便利性,在进行简单代码的书写时,会加上这句话。在实际开发中,不建议这样做,因为这样可能会导致重名的现象。在实际开发中使用指定域名访问和展开域名空间的一部分成员的做法较为常见。
如果未出现这段语句,由于cout(输出)、cin(输入)、endl(换行)这些函数是定义在std命名空间内的,不能直接使用,必须使用指定域名进行访问。int main() {int a; //定义变量astd::cin >> a; //输入astd::cout << a << std::endl; //输出a并换行return 0; }
四、C++的输入输出
- <iostream>是InputOutputStream的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。
- std::cin是istream类的对象,它主要面向窄字符(narrowcharacters(oftypechar))的标准输入流。
- std::cout是ostream类的对象,它主要面向窄字符的标准输出流。
- std::endl是⼀个函数,流插入输出时,相当于插入⼀个换行字符加刷新缓冲区。
- << 是流插入运算符,>> 是流提取运算符。
- C++的输入、输出可以自动检测变量类型(本质上是函数重载),不需要像C语言指定类型进行操作。
- cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
- 有一些编译器的<iostream>包含了<stdio.h>,有一些则没有包含。
- 如果对于输入输出的效率有要求,可以在代码中加入以下代码段,以提高输入输出效率:
//需要展开std命名空间 ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
五、缺省参数
缺省参数是在声明或定义函数时,将该函数的某个或多个参数给定一个缺省值。如果在调用函数传参时,未传入指定的实参,则该参数为指定的缺省值;如果传入指定实参,则该参数为传入的实参值。缺省参数分为全缺省和半缺省(参数列表中不完全都是缺省参数)。缺省参数多用于某些函数在没有指定传参时,仍需要一个默认参数的情况。
#include<iostream>
using namespace std;void Init(int *a, int size = 4) //size为缺省参数
{//开辟空间a = (int*)malloc(sizeof(int) * size);
}
int main()
{int* a = NULL;Init(a); //这里没有传入size,size默认为缺省值4free(a);return 0;
}
5.1 缺省参数的使用规定
- 全缺省就是形参全部都给定缺省值,半缺省就是部分形参给定缺省值。C++规定在给定缺省值时,半缺省必须从右到左依次连续给定,不能间隔跳跃给定缺省值。
- 带缺省参数的函数调用,C++规定必须从左到右依次传参,不能跳跃传参。
- 函数定义和声明分离时,缺省参数不能在声明和定义中同时出现。C++规定,函数定义和声明分离时,必须在声明时给定缺省值。
void Init(int v = 9,int *a, int size = 4) //error 跳跃给定缺省值
{//开辟空间a = (int*)malloc(sizeof(int) * size);
}
void Init(int* a, int size = 4);
void Init(int *a, int size = 4) //error
{//开辟空间a = (int*)malloc(sizeof(int) * size);
}/*===============================================================*/void Init(int* a, int size = 4);
void Init(int *a, int size) //√
{//开辟空间a = (int*)malloc(sizeof(int) * size);
}
六、函数重载
如果两个函数同名,但是它们的参数列表不同(包括顺序,个数,参数数据类型。和返回值无关),这种情况称为函数重载,调用该函数时,会根据传参的不同,在编译时确定具体调用哪个函数。C++支持函数重载,能更灵活的应对不同的调用情况。
int Add(int a, int b)
{return a + b;
}
double Add(double a, double b)
{return a + b;
}
int main()
{cout << Add(1, 1) << endl; //输出2cout << Add(1.5, 1.9) << endl;//输出3.4return 0;
}
//参数类型不同
void func(int a){}
void func(double a){}//参数个数不同
void func(){}
void func(int a){}//参数数据类型顺序不同
void func(int a, char b){}
void func(char a, int b) {}
还有一种情况下,两个函数构成重载,但是在调用时会报错:
void func(int a = 9){}
void func(){}
int main()
{func(); //error,如果传参,可以编译通过,若不传参,会产生歧义return 0;
}
此时编译器不知道调用的到底是哪个函数。
七、引用
7.1 引用的概念
引用,相当于对一个对象起了一个别名。在定义引用时,不同于定义变量,定义引用并不会开辟新的空间,而是与它引用的对象共用一块内存空间。
类型& 别名 = 被引用对象
int main()
{int a = 0;//定义引用p,p是a的别名int& p = a;//定义引用pp,pp是p的别名int& pp = p;p++;pp++;cout << a << endl;//输出2//以下输出三个地址是一样的,都是a的地址cout << &a << endl;cout << &p << endl;cout << &pp << endl;return 0;
}
7.2 引用的特性
- 引用必须初始化。
- 引用一旦有了引用的对象,将不能引用其他对象。
- 一个对象可以有多个引用。
7.3 引用的使用
- 引用主要用于传参和返回值,以减少拷贝增加效率,或在改变引用时改变被引用的对象。
- 引用和指针类似,但使用起来比指针更加的方便。
- 由于引用不可改变指向,所以引用是不能完全替代指针的。
7.4 const引用
- 有const修饰的对象必须用const引用,const引用也可以引用普通对象,但通过这个引用访问该对象的权限缩小了(通过const引用不能改变该对象)。
- 在某些情况下,比如表达式、隐式类型转换的结果,会产生一个临时对象,而临时对象具有常性,如果使用非const引用来引用这些结果,会放大该引用的访问权限(具有常性的对象不可变,非const引用是可以改变被引用的对象的),这是不被允许的。这种情况下,只能使用const引用。
int main()
{const int a = 10;const int& p = a;int b = 20;const int& pp = b;pp++;//error,权限被缩小b++;//√double c = 7.0;const int& p2 = (a + b);//表达式const int& p3 = c;//隐式类型转换return 0;
}
7.5 引用和指针的关系
- 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
- 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。
八、inline 内联函数
- 被inline修饰的函数叫做内联函数。内联函数在被调用时,不会在栈上额外开辟空间,而是在当前函数的栈帧中展开内联函数,这样可以提高效率。
- 如果该函数是递归函数,或者函数中语句过多,加上inline也会被编译器忽略。
- C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。
- inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
inline void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
int main()
{int a = 10;int b = 20;Swap(a, b);cout << a << endl << b << endl;return 0;
}
九、nullptr
我们来观察下面的代码:
void func(int* a)
{printf("int*\n");
}
void func(int a)
{printf("int\n");
}
int main()
{func(NULL); //会输出什么呢?return 0;
}
明明传入的是空指针NULL,为什么会调用参数为整型的函数呢?这是由于在C++中NULL是一个值为0的宏。 那么如果传入(void*)NULL,结果会报错:
为了解决这个从C语言遗留的歧义问题,C++引入了一个新的关键字nullptr,nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型,而不能转换成整型。
void func(int* a) //调用这个函数
{printf("int*\n");
}
void func(int a)
{printf("int\n");
}
int main()
{func(nullptr);return 0;
}