命名空间
命名空间定义(namespace)
- 普通的命名空间
- 命名空间的嵌套
- 多个命名空间名称相同,编译器会自动合并
//普通的命名空间
namespace N1 // N1为命名空间的名称
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
}
//命名空间可以嵌套
//举个例子:N2代表计算机学院,包含N3物联网工程
namespace N2
{
int c = 30;
int d = 40;
int Sub(int left, int right)
{
return left - right;
}
namespace N3
{
int a = 50;
int b = 60;
int Mul(int left, int right)
{
return left * right;
}
}
}
//命名空间可以存在多个相同名称的命名空间
namespace N1
{
int Div(int left, int right)
{
return left / right;
}
}
命名空间的使用
- 命名空间 + ::(作用域限定符)
- 使用using将命名空间中成员引入
- 使用using namespace 命名空间名称引入
// 作用域限定符
int a = 10;
int main()
{
int a =1;
printf("%d\n",a); //访问main函数中的a
printf("%d\n",::a); //访问全局变量a
return 0;
}
//命名空间使用
namespace N2
{
int c = 30;
int d = 40;
int Sub(int left, int right)
{
return left - right;
}
namespace N3
{
int a = 50;
int b = 60;
int Mul(int left, int right)
{
return left * right;
}
}
}
N2::N3::a;
using namespace N2;
int main()
{
//命名空间 + ::
printf("%d\n",N2::N3::a); //50
//using N2::N3::a;
printf("%d\n",a); //50
//using namespace N2;
printf("%d\n",a); //50
}
缺省参数
缺省参数:声明或定义时为函数的参数指定一个默认值,调用时如果没有指定则采用该值
全缺省参数:所有参数都有默认值
半缺省参数:部分函数带有缺省值,从右向左依次给出
//全缺省参数
void TestFunc(int a = 0,int b = 1,int c = 2)
{
cout << a << " " << b << " " << c << endl;
}
//半缺省参数(如果只给a的话,会报错,只给a和c也会报错)
//必须从右往左依次给出
void Test2Func(int a, int b, int c = 1)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
//全缺省参数
TestFunc(10, 20, 30);//10 20 30
TestFunc(); //0 1 2
TestFunc(10);//10 1 2
TestFunc(10, 20);//10 20 2
//半缺省参数
Test2Func(10, 20, 30); //10 20 30
Test2Func(10, 20);// 10 20 1
//Test2Func(10);//报错
//Test2Func();//报错
return 0;
}
半缺省参数不能再函数声明和定义同时出现,一般放到声明的位置
缺省值必须是常量或者全局变量
//编译器报错:如果两个位置提供的值不同,编译器无法确定使用哪个缺省值
void TestFunc(int a = 5);
void TestFunc(int a = 10)
{}
int main()
{
return 0;
}
函数重载
同一作用域声明几个功能类似的同名函数,同名函数的形参列表(个数、类型、次序)必须不同
#include <iostream>
using namespace std;
//使用cout<<""<<ebdl;要引头文件
int Add(int left, int right)
{
return left + right;
}
//参数类型不同
double Add(double left, double right)
{
return left + right;
}
//参数顺序不同
double Add(double left, int right)
{
return left + right;
}
double Add(int left, double right)
{
return left + right;
}
//个数不同
double Add(double a)
{
return a;
}
int main()
{
cout << Add(1, 2) << endl; //3
cout << Add(1.1 , 2.0) << endl; //3.1
cout << Add(1.2, 2) << endl; //3.2
cout << Add(1, 2.3) << endl;//3.3
cout << Add(5.2) << endl; //5.2
return 0;
}
与返回值类型无关
//编译器报错
int Add(int left, int right)
{
return left + right;
}
double Add(int left, int right)
{
return left + right;
}
无参函数与同名全缺省参数不要同时存在
void TestFunc()
{}
void TestFunc(int a = 0)
{}
int main()
{
//编译器报错:对重载函数的调用不明确
TestFunc();
return 0;
}
- C语言和C++对函数的名字的修饰规则
C语言中,编译器对函数名字的修饰规则:仅在函数名字前加 _
C++函数名字修饰规则:将参数的类型增加到最终的名字中,如 double Add(double left, double right);在编译器中的修饰 (?Add@@YANNN@Z)
两个函数仅仅只在返回值类型上不一样,不能形成重载
- extern"C"
有时候在C++工程中将代码按照C的风格来编译,在函数前加extern"C"
引用
引用类型必须和引用实体必须是同类型
int main()
{
int a = 10;
int& ra = a;
//double& rd = a;//错误
ra = 20;
cout << a << endl;//20
cout << &a << endl;
cout << &ra << endl;
return 0;
}
特性
- 一个变量可取多个引用
- 引用在定义时必须初始化
- 一旦引用一个实体,再不能引用其他实体
int main()
{
//引用的生命周期没有实体的长
int a = 10;
int b = 5;
int& ra = a;
//int&rb; 没有初始化,错误
//int& ra = b; 多次引用,错误
int& rra = a;
ra = 20;
cout << &rra << endl;
cout << &a << endl;
cout << &ra << endl;
return 0;
}
常引用
int main()
{
//方式一
const int a = 10;
//int& ra = a; error
const int & ra = a;
//ra = 20; error
//方式二
const int& b = 10;
//方式三
double d = 12.34;
//int& rd = d; 类型不匹配
const int& rd = d;
//const 类型的引用,可以给一些临时变量取别名
d = 10.34;
cout << rd << " " << &rd << endl;
cout << d << " " << &d << endl;
//rd 和 d不是同一块空间
return 0;
}
引用的使用场景
- 引用作为函数的参数
实参--->值传递 ,可以通过修改形参改变外部实参
如果是引用类型的参数,但在函数体中不需要改变形参,最好传递const 类型的引用
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
//注意:如果是引用类型的参数,但在函数体中不需要改变形参,最好传递const 类型的引用
void TestRef(const int& a)
{}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
cout << "a=" << a << endl;//20
cout << "b=" << b << endl;//10
return 0;
}
- 引用作为函数的返回值类型
如果以引用的方式作为函数的返回值类型,不能返回栈上的空间
如果以引用的类型返回,那么返回的变量的生命周期一定要比函数的生命周期长
int & TestRet()
{
int ret = 10;
//ret 是栈上的空间,而栈空间随着函数的结束已经被系统收回了
return ret;
}
int main()
{
int& a = TestRet(); //a所引用的空间为一段无效的空间
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", a);
return 0;
}
引用和指针的区别
- 传值、传指针、传引用的效率比较
/*
这是一段测试传值、传指针、传引用的效率测试代码
*/
#include <Windows.h>
struct A
{
int array[10000];
int size;
};
/*/
void TestRef(A param)
{
}
void TestRef(A* param)
{
}
void TestRef(A& param)
{
}
*/
void Test()
{
A a;
size_t begin = GetTickCount();
for (size_t i = 0; i < 10000000; ++i)
{
//TestRef(a); 传值
//TestRef(&a); 传地址
//TestRef(a); 传引用
}
size_t end = GetTickCount();
cout << end - begin << endl;
}
int main()
{
Test();
return 0;
}
传指针和传引用效率差不多
- 指针和引用的区别
int main()
{
int a = 10;
int *pa = &a;
*pa = 20;
int& ra = a;
ra = 20;
return 0;
}
相同点
引用和指针在底层的处理方式完全相同,引用就是按照指针的方式处理
不同点
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
引用的类型
int a = 10;
int& ra = a; //int& ---> int* const
const int& cra = a; //const int* const
内联函数(inline)
概念
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
Add(1, 2);
return 0;
}
内联函数:在编译阶段已经展开(以空间换时间)
如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联
如果想看编译器如何处理内联函数的,将编译器的Debug更改为Release模式
宏和内联函数
- 宏的优缺点
优点:增强代码的复用性,提高性能
缺点:不方便调试,代码的可读性差,容易误用,不安全(没有类型安全的检查)
- C++中替换宏
宏函数---->inline
宏常量---->const 修饰(常量&具有宏的属性(替换))
auto关键字(C++11)
auto
在C++11以前:auto修饰的变量,是具有自动存储器的局部变量
C++11:auto是类型声明时的一个占位符,编译器在编译期会将auto替换为变量实际的类型。
int main()
{
int a = 10;
auto b = 20;
auto c = 'c';
auto d = 12.34;
//auto e; 错误,使用必须初始化
//typeid(b).name() 查看类型
cout << typeid(b).name() << endl; //int
cout << typeid(c).name() << endl; //char
cout << typeid(d).name() << endl; //double
return 0;
}
使用细则
int main()
{
int a = 10;
auto pa1 = &a;
auto* pa2 = &a;
auto& ra = a;
ra = 20;
// auto c = 0,d =1.0; //错误,同行出现多个类型
cout << typeid(pa1).name() << endl; //int*
cout << typeid(pa2).name() << endl; //int*
cout << typeid(ra).name() << endl; //int
cout << a << endl; //20 说明ra修改了a的值
return 0;
}
//编译器报错:编译器无法对a的实际类型进行推倒
void TestFunc(auto a)
{}
int main()
{
int a[] = { 1,2,3 };
//编译器报错
auto b[3] = a;
return 0;
}
- auto在定义变量时必须初始化
- auto 和 auto* 没有区别,但是引用的时候必须加上& auto &
- 同行auto定义多个变量时,必须为同类型变量
- auto不能作为函数参数,不能直接用来声明数组
- auto不能定义类的非静态成员变量
- 实例化参数时,不能使用auto作为模板参数
优势
在拥有初始化表达式的复杂类型变量声明时的简化
namespace N1
{
int a;
namespace N2
{
typedef int DataType;
int b;
}
}
int main()
{
N1::N2::DataType a = N1::N2::b;
//使用auto
auto a2 = N1::N2::b;
return 0;
}
可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误
int main()
{
const float pi = 3.14;
double r = 2.0;
//c的类型让编译器来推倒
auto c = 2 * pi * r;
cout << c << endl; //12.56
cout << typeid(c).name() << endl; //double
return 0;
}
范围for
范围for:for(auto & e:array){ } | 要求 |
---|---|
用“:”隔开 | for循环范围确定 |
第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围 | 迭代对象要实现++/--操作 |
int main()
{
int array[] = { 1,2,3,4,5,6,7,8,9,0 };
//之前的方法
//for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
//{
// array[i] *= 2;
//}
//for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
//{
// cout << array[i] << " ";
//}
//cout << endl;
//范围for
for (auto& e : array)
{
e *= 2;
}
for (auto& e : array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
指针空值--->nullptr(C++11)
在C语言中NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量容易发生混淆,Test(0);Test(NULL);没有区别
为了避免混淆,C++11提供了nullptr,即:nullptr代表一个指针空值常量
nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型
void TestFunc(int)
{
cout << "int" << endl;
}
void TestFunc(int* pa)
{
cout << "int*" << endl;
}
int main()
{
TestFunc(0); //int
TestFunc(NULL); //int
TestFunc(nullptr); //int*
return 0;
}