本文重点:
1.命名空间
2.缺省参数
3.函数重载
4.引用
5.内联函数
6.auto关键字
7.基于范围的for循环
1.命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
#include <stdio.h>
#include <stdlib.h>
int number = 10;
int main()
{
printf("%d\n", number);
return 0;
}
// 编译后后报错:error C2365: “number”: 重定义;以前的定义是“函数”
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
1.1 命名空间定义
1.定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
// Name是命名空间的名字,一般开发中是用项目名字做命名空间名。
namespace Name
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Subtration(int left, int right)
{
return left - right;
}
struct Node
{
struct Node* next;
int val;
};
}
2. 命名空间可以嵌套
project.h
namespace Name1
{
int a;
int b;
int Subtration(int left, int right)
{
return left - right;
}
namespace Name2
{
int c;
int d;
int multiplay(int left, int right)
{
return left * right;
}
}
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
project.cpp
namespace Name1
{
int Divide(int left, int right)
{
return left / right;
}
}
project.h文件的Name1和project.cpp的Name1会被合并成一个命名空间。
1.2 命名空间使用
命名空间中成员该如何使用呢?比如:
#include<iostream>
using namespace std;
namespace name
{
int val1 = 0;
int Divide(int left, int right)
{
return left / right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 编译报错:error C2065: “val1”: 未声明的标识符
cout<<val1<<endl;
return 0;
}
命名空间的使用有三种方式:
加命名空间名称及作用域限定符
#include<iostream>
using namespace std;
namespace name
{
int val1 = 100;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
cout<<name::val1<<endl;
return 0;
}
使用using将命名空间中某个成员引入
#include<iostream>
using namespace std;
namespace name
{
int val1 =100;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
using name::val1;
int main()
{
cout<<val1<<endl;
return 0;
}
使用using namespace 将命名空间引入
#include<iostream>
using namespace std;
namespace name
{
int val1 =100;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
using namespace name;
int main()
{
cout<<val1<<endl;
return 0;
}
2.缺省参数
2.1缺省参数概念
缺省参数是声明或定义函数时为参数指定一个缺省值.在调用该函数时,如果没有指定实参则采用改形参的缺省值,否则使用指定的实参.
#include<iostream>
using namespace std;
void FuncTion(int val = 100)//缺省值为100
{
cout << val << endl;
}
int main()
{
FuncTion( );//没有传参时,使用参数的缺省值;val=100;
FuncTion(1010);//传参时,使用指定参数的实参;
FuncTion(2023);
return 0;
}
2.2缺省参数的分类
全缺省参数:
#include<iostream>
using namespace std;
void Add(int val_a = 3,int val_b=2,int val_c=1)
{
int ret = val_a + val_b + val_c;
//3+2+1;
//ret=3
cout << ret << endl;
}
int main()
{
Add( );//都没有传参时,参数全部使用为缺省值;
return 0;
}
半缺省参数
#include<iostream>
using namespace std;
void Add(int val_a = 1,int val_b=2,int val_c=3)
{
int ret = val_a + val_b + val_c;
//10+20+1;
//ret=31
cout << ret << endl;
}
int main()
{
Add(10, 20, 30);
Add(10, 20);
Add(10);
return 0;
}
注意:
!.半缺省参数必须从左往右依次来给,不能跳过某个参数;
2.缺省参数不能在函数声明和定义中同时出现
3.缺省值必须是常量或者全局变量
3.函数重载
概念:函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题,函数的返回值类型可以相同。
3.1参数类型不同
#include<iostream>
using namespace std;
void Add(int left,int right)//数据类型为int
{
int ret =left+right;
cout << ret << endl;
}
void Add(double left, double right)//数据类型为double
{
double ret = left + right;
cout << ret << endl;
}
int main()
{
//根据参数类型匹配相应的函数
Add(10, 10);
Add(12.00, 3.418);
return 0;
}
3.2参数个数不同
#include<iostream>
using namespace std;
void Add(int left,int middle,int right)//三个参数
{
int ret =left+right+middle;
cout << ret << endl;
}
void Add(int left, int right)//两个参数
{
int ret = left + right;
cout << ret << endl;
}
int main()
{
//根据参数个数匹配相应的函数
Add(10, 10);
Add(10,20,30);
return 0;
}
3.3参数类型顺序不同
#include<iostream>
#include<string>
using namespace std;
// 3、参数类型顺序不同
void func(int a, string b)
{
cout << "f(int a,string b)" << endl;
}
void func(string b, int a)
{
cout << "f(char b, string a)" << endl;
}
int main()
{
func(10,"字符串在后");
func("字符串在前", 10);
return 0;
}
3.4C++支持函数重载的原理--名字修饰(name Mangling)
为什么C++支持函数重载,而C语言不支持函数重载呢? 在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
1. 实际项目通常是由多个头文件和多个源文件构成,编译后链接前,a.o的目标 文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就 会到b.o的符号表中找Add的地址,然后链接到一起。
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的 函数名修饰规则。
4. 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使 用了g++演示了这个修饰后的名字。
5. 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度 +函数名+类型首字母】。 采用C语言编译器编译后结果 。
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。 采用C++编译器编译后结果 。
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参 数类型信息添加到修改后的名字中。
Windows下名字修饰规则
对比Linux会发现,windows下vs编译器对函数名字修饰规则相对复杂,
6. 通过这里就理解了C语言没办法支持重载,因为同名C函数没办法区分。而C++是通过函数修 饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
4.引用
是给已存在变量取了一个别名,引用不是新定义一个变量,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:张三,玩的好的同学们叫他小三,爸妈叫他阿三,指的都是同一个人。
语法:类型& 引用变量名(对象名)=引用实体;
#include<iostream>
using namespace std;
int main()
{
int a = 100;
int& b = a;
cout << a << endl;
cout << b << endl;
double A = 2.323;
double& B = A;
cout << A << endl;
cout << B << endl;
//注意:引用类型必须和引用实体是同一类型;
return 0;
}
注意:引用类型必须和引用实体是同一类型;
4.2引用特性
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他实体
#include<iostream>
using namespace std;
int main()
{
int a = 100;
//int& b;//error
int& b = a;
int& c = a;
int& d = b;
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d<< endl;
//都指向同一块空间,都是a的引用,引用的引用可以成为引用的实体
double A = 200.2;
double B = 100.10;
double& C = A;
double& C = B;//error C已经引用了A实体,不能再引用实体B;
return 0;
}
4.3常引用
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
return 0;
}
4.4引用的使用场景
1.做参数
#include<iostream>
using namespace std;
void Swpa(int& left, int& right)//引用做参数
{
int temp = left;
left = right;
right = temp;
}
//void Swpa(int left, int right)
//{
// int temp = left;
// left = right;
// right = temp;
//}
int main()
{
int a = 10;
int b = 20;
Swpa(a, b);
return 0;
}
2.做返回值
#include<iostream>
using namespace std;
int& Count()
{
static int n = 0;
n++;
return n;
}
int& Add(int a, int b)//函数调用结束c的空间还给操作系统,所以c不能做引用返回
{
int c = a + b;
return c;
}
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
引用返回的好处
1、减少拷贝
2、调用者可以修改返回对象
注意:如果函数返回时,出了函数作用域,返回对象还在(还没有还给操作系统)则可以使用引用返回,如果已经还给系统了,则必须使用传值返回;
4.6引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和引用实体共用同一块空间。
但在底层实现上实际是有空间的,因为引用是按照指针的方式来实现的。
#include<iostream>
using namespace std;
int main()
{
int A = 100;
int& B = A;
int* ptr = &B;
cout << B<< endl; //100
cout << *ptr << endl;//100
return 0;
}
引用和指针的不同点:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
5.内联函数
5.1 概念 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。
6.auto关键
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错
6.1auto简介
使用auto修饰的变量,是具有自动存储的局部变量。在C++11中标准委员会赋予auto全新的含义;auto不再是一个存储类型指示符,而是作为一个新的类型来指示编译器,auto声明的变量必须由编译器在编译时期推断而得。
注意:
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编 译期会将auto替换为变量实际的类型。
6.2 auto的使用细则
1. auto与指针和引用结合起来使用 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须 加&;
int main()
{
double bb = 10;
auto aa = &bb;//加*
auto* ss = &bb;//不加*
auto& c = bb;
auto cc = bb;//不加& cc为bbd的类型
int a[] = { 1,3,4 };
auto b = a;
auto b1 = a[1];
cout << typeid(bb).name() << endl;
cout << typeid(ss).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(cc).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(b1).name() << endl;
}
2. 在同一行定义多个变量 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
int function()
{
auto a = 1, aa = 2;
auto b = 3, bb = 3.0;//编译失败,初始化类型不同;
}
3 auto不能推导的场景
1. auto不能作为函数的参数
int function(auto a)
{
}
2. auto不能直接用来声明数组
int function()
{
int a[] = { 1,2,3,4 };
auto b[] = { 1,2,3 };
cout << typeid(b).name() << endl;
}
7.基于范围的for循环
for循环后的括号由冒号“ :”分为两部分:第一部分是范 围内用于迭代的变量,第二部分则表示被迭代的范围。
int main()
{
int array[] = { 1,2,3,4,5,6,7,8,9 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i]<< endl;
}
for (auto a : array)
{
cout << a<< endl;
}
}
范围for的使用条件 1. for循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定。
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}