目录
一:命名空间
举个栗子:假如一个班级有两个名叫Bug的同学,那么为了平时区分他们,我们是不是不得不通过其它额外信息来区分他们,比如家在哪?外貌等等来区分呀.
同样的情况类比到C++里面,倘若你写了一个函数Y(),可能另一个库中也存在着这样的相同函数名一个函数Y(),因此当你要调用这个函数时,编译器就无法识别要调用哪一个Y()函数了.
因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。其实本质上,命名空间就是定义了一个范围。
定义命名空间:
定义命名空间需要用到 namespace 关键字
具体格式: namespace+命名空间名称 + { }
namespace N1 //N1代表命名空间的名称
{
int x;
int sum(int a,int b )
{
return a+b;
}
//命名空间的成员,如函数,变量等等
}
注意:
- 命名空间定义可以嵌套;
- 一个工程允许有多个相同的名字的命名空间,编译器最后会合成到同一个命名空间中;
- 一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
使用命名空间:
-
命名空间名称+ :: + 变量名
-
using+命名空间名称+::+变量名 -->使用时不需要加作用域限定符,即直接在下面使用即可.(推荐使用)
-
using namespace +命名空间名称 -->所有成员全部暴露出在命名空间里 (不推荐,易造成命名空间污染)
eg:
namespace N
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
以下是三种使用方法:
int main()
{
printf("%d\n", N::a); //第一种
return 0;
}
using N::b;
int main()
{
printf("%d\n", b); //第二种
return 0;
}
using namespace N;//第三种
int main()
{
printf("%d\n", Add(10, 20));
printf("%d\n", Sub(10, 20));
return 0;
}
二:C++中的输入输出
C++中的输入输出不同于C 语言,下面是两者间的对比:
注意:
- 使用cout标准输出(控制台) 和 cin标准输入(键盘) 时,必须包含< iostream >头文件以及 std标准命名空间
- 相比C语言C++输入输出更方便,不需增加数据格式控制.
三:缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
void Test(int a = 10, int b = 20)
{
cout << a << " " << b << endl;
}
int main()
{
Test(); //10 20 没传参,使用默认参数值
Test(5, 5);//5 5 传参,使用指定的实参
return 0;
}
缺省参数分类:
1.全缺省参数
void Test1(int a = 1, int b = 2, int c = 3)
{
cout << a << " " << b << " " << c << endl;
}
2.半缺省参数
void Test2(int a, int b = 4, int c = 5)
{
cout << a << " " << b << " " << c << endl;
}
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
四:函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型 或者 类型顺序)必须不同。如下面的例子:
int Add(int a, int b)
{
return a + b;
}double Add(double a, double b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}double Add(int a, int b, double c)
{
return a + b + c;
}
double Add(int a, double c, int b)
{
return a + b + c;
}
注意下面的例子不构成函数重载(函数返回值不同不构成函数重载)
int Add(int a, int b)
{
return a + b;
}
double Add(int a, int b)
{
return a + b;
}
五:函数重载原因
还有一点为什么C语言不存在函数重载???
下面我们通过两个例子对比来看:
通过这几个例子总结如下:
总的来说C语言不支持函数重载是因为: C语言函数名修饰规则不支持.假如存在C语言两个重载函数,他们经过底层的函数名修饰规则修饰后函数名相同: (均为_+函数名),在链接时倘若进行调用无法区分到底调用哪一个函数.而假如C++的函数名修饰规则支持,,会因为函数参数列表的不同而不同 (前缀+函数名+参数信息).
(补充g++编译器函数名修饰规则,修饰后的函数名为 _Z+函数名字符长度+函数名+类型缩写)eg:原函数名int sub(float a, int b);修饰后的为_Z3subfi . 要了解更加详尽的可以去访问 C++函数重载 C/C++的调用约定
条件编译:
在C++编程中,可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。
extern "C" int Add(int left, int right){
return right + left;
}
int main()
{
printf("%d", Add(1, 2));
return 0;
}
六:引用
引用是某个已存在变量的另一个名字。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
void test()
{
int a = 10;//类型& 引用变量名(对象名) = 引用实体;
int& ra = a; //定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意:引用实体类型和引用类型必须一致即是同一种类型.
引用特点:
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
引用的使用场景:
1.做参数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
2.做返回值
int& add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = add(1, 2);
cout << ret << endl; //3
add(3, 4);
cout << ret << endl; //7
system("pause");
return 0;
}
注意: 引用类型做返回值, 引用实体的生命周期要大于函数的生命周期
传值、传引用效率比较:
#include <iostream>
using namespace std;
#include <time.h>
struct A
{
int a[10000];
};
void TestFunc1(A a)
{}
void TestFunc2(A& a)
{}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 1000000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 1000000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(int*)-time:" << end1 - begin1 << endl;//1296
cout << "TestFunc2(int&)-time:" << end2 - begin2 << endl;//16
}
// 运行多次,检测值和引用在传参方面的效率区别
int main()
{
TestRefAndValue();
return 0;
}
通过以上例子可以看出:传值和指针在作为传参以及返回值类型上效率相差很大。
引用/指针比较(重点):
- 引用在定义时必须初始化,指针没有要求(因为: int& a=10; <=> int* const a=10; 内容可以被修改,指向的地址不可被修改)
- 引用在初始化时引用一个实体后,就不能再引用其他实体(即指向唯一),而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
七:内联函数
内联函数:以inline修饰的函数.编译时C++编译器会在调用内联函数的地方展开,这会没有函数压栈的开销,内联函数提升程序运行的效率。
内联函数特点:
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
inline int add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = 0;
ret=add(1, 2);
return 0;
}
八:auto关键字(C++11)
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
auto e = 10.0;
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
cout << typeid(d).name() << endl;//int
cout << typeid(e).name() << endl;//double
//auto f; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto使用注意:
1.使用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个
类型进行推导,然后用推导出来的类型定义其他变量。
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
3.auto不能作为函数的参数 (auto不能作为形参类型,因为编译器无法对a的实际类型进行推导)
4.auto不能直接用来声明数组
5. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
6. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
7.auto不能定义类的非静态成员变量
8.实例化模板时不能使用auto作为模板参数
九:范围for(C++11)
在C语言中对数组进行遍历我们有以下两种做法(数值遍历,指针地址范围遍历):
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
在C++11中,范围for使用方法:
for(当前遍历到的元素值:遍历范围)
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto& b : arr)
b *= 2;for (auto& b : arr)
cout << b << " ";
return 0;
}
注意for循环迭代的范围必须是确定的.对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
十:指针空值nullptr
C++11提供了nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:
typedef decltype(nullptr) nullptr_t;
nullptr 使用注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。