C++基础语法
一、命名空间
在一个C/C++项目中,变量、函数和类在项目中都是大量存在的,这些变量、函数和类的名称若都是存于全局变量中,可能会导致名称冲突。所以命名空间作用就是对标识符的名称本地化,避免名字冲突和污染——所以有了关键字namespace
1. :: 域作用限定符
先介绍一下这个 ::
符号。
::
:域作用限定符
eg1: 使用局部域和全局域举例
#include <stdio.h>
int a = 0; //全局域
int main()
{
int a = 1; //局部域
printf("%d\n", a); //访问局部域
//::(域作用限定符)
printf("%d\n", ::a); //访问全局域
return 0;
}
//output: 1 0
eg2:在上一个例子的基础上在加上命名空间域举例
namespace kpl
{
int a = 0; //命名空间域
}
int a = 1; //全局域
int main()
{
int a = 2; //局部域
printf("%d\n", a); //访问局部域
printf("%d\n", ::a); //访问全局域
printf("%d\n", kpl::a); //指定访问命名空间域
return 0;
}
code_result:
通常在访问的时候局部域优先,但是加上 :: 就可以访问全局域。在 :: 域作用限定符之前后分别加上命名空间域的名字和要访问的变量、函数、类型,就可以访问命名空间域内部的数据
2. 作用
命名空间:防止命名冲突或名字污染,对标识符的名称进行本地化
命名冲突:
- 我们的项目和库中的名字冲突。
- 互相冲突,因为一个项目可能需要多个人写,因此可能会出现同样的名字。
eg:我们和库中的名字冲突
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
eg_result
rand重定义,以前库中的定义是函数。因为C语言解决不了这样的问题,所以在C++中提出了namespace。
3. 命名空间的定义
①简单使用
命名空间中可以定义变量、函数、类型
//kpl是命名空间的名字,命名空间的名称可以随便定义,一般开发中都是用项目名字作为命名空间名
namespace kpl
{
//变量
int rand = 10;
//函数
int Add(int left, int right)
{
return left + right;
}
//类型
struct Node
{
struct Node* next;
int val;
};
}
②命名空间嵌套
eg:
kpl2是命名空间kpl1中的域。所以再使用的时候要从外向里包含
4. 命名空间的使用
- 加命名空间名称及作用域限定符
namespace kpl
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
printf("1 + 2 = %d, a = %d\n", kpl::Add(1, 2), kpl::a);
return 0;
}
//output:1 + 2 = 3, a = 0
- 使用using将命名空间中某个成员引入(展开部分成员)
建议:常用的库对象、类型等方面使用
namespace kpl
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
using kpl::b;
int main()
{
//这里就不需要加::域访问限定符,可以直接访问b
printf("%d\n", b);
return 0;
}
//output:1
- 使用using namespace命名空间名称引入
建议:不要用这种方式,如果使用这种方式,命名空间就没有存在的意义了。
namespace kpl
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
using namespace kpl;
int main()
{
//对命名空间展开,就等于命名空间的所有内容就展开到全局域中,可以和全局域一样使用。
//对命名空间展开,也意味着该命名空间没做到它该有的作用
int c = Add(10, 20);
printf("%d\n", a);
printf("%d\n", c);
return 0;
}
5. 小结
- 如果展开命名空间域之后,所展开命名空间中的变量和全局变量命名一致会报错。因为命名空间域展开后,其中的变量本质就是全局域的变量,而同一域不能有相同的名称。
- 一个命名空间就定义了一个新的作用域。不展开的情况下,命名空间的所有内容都局限在该命名空间中。
- 同一个工程允许存在多个相同名称的命名空间,编译器最后会合成为一个命名空间。
- 域的分类
- 类域(和class有关)
- 命名空间域
- 作用域
a. 全局域
b.局部域
二、C++输入输出
1. 简单使用
注:都包含在
<iostream>
这个头文件中。cout和cin是全局的流对象,endl是C++特殊符号。这些也都包在一个命名空间std(后面讲)中
- cout(标准输出对象) —— 控制台
- cin(标准输入对象) —— 键盘
- endl(换行输出)
eg:
#include <iostream> //包含C++输入输出头文件
//全部展开命名空间
using namespace std;
int main()
{
int a;
double b;
char c;
//自动识别类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
2. std介绍
std是C++标准库的命名空间,C++标准库的定义和实现都放到这个命名空间当中
eg:
#include <iostream>
int main()
{
std::cout << "hello world!" << std::endl;
return 0;
}
注:为了区分C中的头文件,使用C++头文件不带.h
。同时因为C++要兼容C所以在C++程序中使用C的头文件去掉后缀.h
在头文件前+c
。eg:#include <cstdio>
三、缺省参数
1. 概念
缺省参数:声明或定义函数时为函数的参数指定一个缺省值,在调用的时候,如果没有传参则就使用缺省值。如果传参了就是用传的这个参数。
eg1:
#include <iostream>
using namespace std;
//给参数a一个缺省值
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
//同时调用Func函数一个传参一个不传参
Func(); //没有传参 使用参数的默认值
Func(10); //传参 使用指定的实参
return 0;
}
//output:0 10
eg2:
注:
- 当声明和定义分离时,声明给缺省参数定义可以不给缺省参数(在编译的时候,如果是缺省参数,会转换成参数列表)。如果声明不给定义给,在编译阶段,编译不过去
- Func()调用是直接无参调用,使用缺省参数就会转换成参数列表中的参数
2. 分类
①全缺省参数
//全缺省参数
void Func(int a= 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl; //显示a的值
cout << "b = " << b << endl; //显示b的值
cout << "c = " << c << endl << endl; //显示c的值
}
int main()
{
Func(); //a、b、c全使用缺省参数
//只传一部分参数,从左向右给
Func(1); //a=1 b、c使用缺省参数
Func(1,2); //a=1 b=2 c使用缺省参数
Func(1,2,3); //a=1 b=2 c=3
return 0;
}
代码运行结果:
注: 传参时只传一部分参数时,从左向右给
②半缺省参数
//半缺省参数
void Func(int a, int b = 10, int c = 20) //半缺省参数从右向左给
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
//因为是半缺省参数,a没有给缺省参数,所以至少要传一个实参
//Func(); //error
//只传一部分参数,从左向右给
Func(1); //a=1 b、c使用缺省参数
Func(1, 2); //a=1 b=2 c使用缺省参数
Func(1, 2, 3); //a=1 b=2 c=3
return 0;
}
代码运行结果:
3. 应用
在一些初始化的时候会使用
eg:stack初始化要赋容量初始值capacity
因为扩容等一些操作有代价(而且不一定原地扩容),不熟悉栈的可以看一下这篇C实现的栈
所以我们在对栈初始化的时候可以像下面这样
void StackInit(ST* ps, int capacity = 4) //默认初始化4个大小
{}
int main()
{
//假设我们事先知道要插入100个数据,那我们就开100个数据的空间,
//所以我们可以这样传参
StackInit(&st1, 100);
//当然如果事先不知道要开辟多少空间,可以不传参
StackInit(&st2);
return 0;
}
4. 小结
- 缺省值必须是常量或者全局变量
- 半缺省参数必须从右向左依次给,不能间隔
- 缺省参数不能在函数的声明和定义中同时出现
原因:如果声明和定义同时给缺省值,并且两个位置提供的缺省值不同,那编译器无法确定使用那个缺省值。- 函数声明不给缺省参数,函数定义给缺省参数,在编译阶段,编译不过去
- 原因:可能会导致调用歧义。如果传一个参数,并且函数定义有缺省参数,因为是声明和定义分离,所以分离编译,在链接阶段会出现调用歧义
- 为了解决上述问题,所以当声明和定义分离时,声明给缺省值,定义不给。声明给的缺省值在编译阶段会转换成参数列表。
四、函数重载
1. 概念
C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 类型 类型顺序)不同,常用来处理功能相似,类型不同的数据。
2. 三种重载方式
注:返回值不同,不构成重载。从后面讲的名字修饰原理也可以理解
eg: 直接报错
#include <iostream>
using namespace std;
//参数类型顺序不同
int f(int a)
{
cout << "int f(int a)" << endl;
}
void f(int a)
{
cout << "void f(int a)" << endl;
}
int main()
{
f(1);
return 0;
}
①参数类型不同
#include <iostream>
using namespace std;
//参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
double Add(int left, double right)
{
cout << "double Add(int left, double right)" << endl;
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
cout << Add(1, 2.2) << endl;
return 0;
}
//运行结果:
//int Add(int left, int right) :3
//double Add(double left, double right) :3.3
//double Add(int left, double right) :3.2
②参数个数不同
eg1:
#include <iostream>
using namespace std;
//参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(1);
return 0;
}
//运行结果:
//f()
//f(int a)
eg2:
#include <iostream>
using namespace std;
//构成重载但是函数调用不明确,存在歧义
void f()
{
cout << "f()" << endl;
}
void f(int a = 0)
{
cout << "f(int a)" << endl;
}
int main()
{
f(); //对重载函数的调用不明确
return 0;
}
③参数类型顺序不同
本质还是参数类型不同
#include <iostream>
using namespace std;
//参数类型顺序不同
void f(int a, char b)
{
cout << "void f(int a, char b)" << endl;
}
void f(char a, int b)
{
cout << "void f(char a, int b)" << endl;
}
int main()
{
f(10, 'a');
f('a', 10);
return 0;
}
//运行结果:
//void f(int a, char b)
//void f(char a, int b)
3. 函数重载的原理
C++支持函数重载的原理 – 原因在于名字修饰
- 这里涉及程序运行与预处理的相关知识。当函数的声明和定义在不同的文件,我们要调用该函数时,函数声明所在的文件是没有函数的地址的,只是一个声明,但是要找到这个函数怎么办呢?所以这就是链接阶段处理的问题,函数声明所在文件没有函数的地址,则会去函数定义所在文件的符号表找该函数的地址,然后链接在一起。(这是程序运行与预处理的相关知识)
- 另一个问题,C++支持函数重载,当有好几个函数名相同的函数时,怎么在链接时寻找到正确的函数?
解决问题的方法: 名字修饰(这里介绍Linux中g++编译器的命名规则。gcc是编译C语言写的程序,g++是编译C++写的程序,当然C++包含C所以g++也可以编译C程序,不建议这样)
g++函数修饰后的名字变成: _Z + 函数长度 + 函数名 + 类型首字母
eg:
void ffffff()
{
cout << "f()" << endl;
}
void ffffff(int a)
{
cout << "f(int a)" << endl;
}
修饰后:所以这里就能看出C++如何支持的函数重载
所以在名字修饰规则里,没有返回值因素的存在,所以返回值类型不构成重载,编译器没办法区分。