- 历史
- 四个阶段以及推荐书籍
- 正文
- 命名空间
- 命名空间的嵌套
- 命名空间合并
- 命名空间使用方式
- 流运算符
- 缺省参数
- 全缺省
- 半缺省
- 函数重载
- 原理
- 参数类型不同
- 参数个数不同
- 参数顺序不同
- 特殊情况 --- 构成重载,但是有歧义
- 引用
- 指针 VS 引用
- 使用场景
- 常引用
- 内联函数
- 特性
- auto 关键字
- auto的使用
- auto不能自动推导的场景
- NULL和nullptr
- NULL 和 0 在C++可能出现的问题
为啥会有C++?因为C语言不适合,于是提出了面向对象(OOP:object oriented programming)的思想。
C++ Primer
C++是一种计算机高级程序设计语言,由 C语言 扩展升级而产生 , 最早于1979年由 本贾尼·斯特劳斯特卢普 在AT&T贝尔工作室研发。
在C语言基础上进行拓展,增加了类的机制,完成了了一个可以运行的预处理程序,称之为C with classes。
1、我们自己定义的变量,函数可能跟库里面重名冲突。
2、公司的项目,通常比较大。多人协作,两个同事写的代码会命名冲突。
例如:
如果加上#include
会报错,因为其中包含了一个rand函数,会命名冲突
#include
//#include //如果加上这条会报错,因为其中包含了一个rand函数,会命名冲突
int rand = 0;
int main()
{
printf("hello world\n");
printf("%d",rand);
return 0;
}
C没有很好的办法解决。
而C++提出了命名空间(namespace
)这个概念,其中可以放函数,类型,变量
#include
#include
//定义了一个叫bit的命名空间 --- 域
namespace hw
{
int rand = 0;
}
int main()
{
printf("hello world\n");
printf("%d",rand);
//printf("%d", hw::rand);
return 0;
}
::
是域作用限定符
优先局部域
如果左边是空白,即去全局域寻找变量
例如:
int a = 0;
int main()
{
int a = 1;
printf("%d", a);//局部域寻找 打印 1
printf("%d", ::a);//全局域寻找 打印 0
}
命名空间的嵌套
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
int main()
{
N2::N3::Sub(1,1);
return 0;
}
命名空间合并
有没有一种可能?就是命名空间也会冲突?NO
其实如果有相同的命名空间,他们不会冲突,而是会合并
例如:
- 展开命名空间
using namespace hw;
全部展开,用起来方便,但是没有隔离效果了 —慎用 - 指定命名空间
hw::xxx
能够做到最好的命名隔离 - 单独展开某一个成员
using hw::ListNode
单独展开某一个成员
C++库定义在一个叫std的命名空间中
流运算符
需要展开std命名空间
<<
流插入
可以自动识别类型,有些时候cout
不如printf
方便,所以推荐哪个方便用哪个
>>
流提取
int a = 0;
double b = 3.14;
cin >> a >> b
缺省参数
C++支持参数缺省,C语言不支持
在函数声明或定义的时候为函数指定的一个缺省值,如果没有指定实参,则会使用缺省值作为形参。
但是缺省参数不能再函数声明和定义的时候同时出现,推荐写在声明部分。
缺省值必须是常量或全局变量
void Fun1(int a = 0)
{
cout << a << endl;
}
int main()
{
Fun1(1);
Fun1();
return 0;
}
运行结果是 1
0
;
如果有多个缺省值,缺省值是从左至右缺省;不能跳过
void Fun2(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Fun2();
Fun2(1);
Fun2(1, 2);
Fun2(1, 2, 3);
return 0;
}
半缺省
缺省一部分,并且从右往左缺省,不能从左往右
void Fun2(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Fun2();
Fun2(1);
Fun2(1, 2);
Fun2(1, 2, 3);
return 0;
}
函数重载
什么是重载?
函数重载就是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,但是返回值不能作为函数重载的条件(因为调用的时候区分不了),常用来处理实现功能类似数据类型不同的问题。
简单讲解函数重载原理
参数类型不同int Fun(int a, int b)
{
cout << "int Fun(int a, int b)" << endl;
return a + b;
}
double Fun(double a, double b)
{
cout << "double Fun(double a, double b)" << endl;
return a + b;
}
int main()
{
Fun(1, 2);
Fun(1.0, 2.0);
return 0;
}
参数个数不同
void Fun()
{
cout << "Fun()" << endl;
}
void Fun(int a)
{
cout << "Fun(int a)" << endl;
}
int main()
{
Fun();
Fun(1);
return 0;
}
参数顺序不同
void Fun(char a, int b)
{
cout << "void Fun(char a, int b)" << endl;
}
void Fun(int a, char b)
{
cout << "void Fun(int a, char b)" << endl;
}
int main()
{
Fun(1, 'a');
Fun('a', 1);
return 0;
}
特殊情况 — 构成重载,但是有歧义
void Fun()
{
cout << "Fun()" << endl;
}
void Fun(int a = 0)
{
cout << "Fun(int = a)" << endl;
}
int main()
{
Fun();//有歧义
Fun(1);
return 0;
}
这种情况会直接报错,代码跑不起来,因为不知道Fun()
到底要调用哪个函数
引用
引用应该使用的名称是一个名字,它是一个已经存在的别名。(其实跟显示生活中的外号很像) 可以把这个名字引用为一个名称,就可以引用一个变量名称或变量名称来说明。并且没有开辟新空间
注意:引用类型必须和引用实体是同种类型的
int a = 10;
int& b = a;
printf("%p\n", &a);
printf("%p\n", &b);
可以看到地址相同
还可以有多个引用
int a = 10;
int& b = a;
int& c = b;
int& d = a;
指针 VS 引用
它们之间有:
- 不存在空引用。引用必须连接到正常的内存。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
- 有多级指针,但是没有多级引用
- sizeof意义不一样。指针都是4或者8字节,然而引用却是引用类型的大小
- 指针一定要显式解引用使用,引用是编译器自己处理
- 引用一旦引用一个实体,再不能引用其他实体。
int a = 10;
int& b = a;
int c = 20;
b = c;
可以看到地址没变,只是值变了(专一)
- 指针可以在那个时候指向另一个对象。
int a = 10;
int* pa = &a;
int* pb = pa;
int c = 20;
int* pc = &c;
pb = pc;
而指针就是值和地址都变了(善变)
做参数
之前如果交换两个值,需要用到指针,现在可以直接传引用,直接交换。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b);
return 0;
}
做返回值
正常传返回值
int Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
cout << ret << endl;
return 0;
}
但如果传引用呢
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
cout << ret << endl;
return 0;
}
虽然打印的值没有问题,但是其实这里传的返回值是临时变量的引用(即c
在栈帧中的引用)
但是栈帧其实已经被销毁了,而VS下栈帧销毁,里面的值没有清空,所以看到的是30。
综上,其实这种方式并不是所有地方都能用
如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回
其实引用返回还有优势,如果返回的对象很大,或者深拷贝对象的时候,可以提高性能
引用返回还可以改变返回对象
权限放大,不可行
const int a = 10;//不行,权限放大了
int& b = a;
权限不变
const int a = 10;//权限不变
const int& b = a;
权限缩小
int a = 10;//权限缩小
const int& b = a;
提个问题,以下写法合法吗?
double d = 3.14;
int& i1 = d;
上面不合法
double d = 3.14;
const int& i1 = d;
这种合法
为啥呢?
因为赋值会产生临时变量,然而临时变量具有常性,不能被修改,所以直接引用会导致权限扩大,就不合法了。
通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline
,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline
限定符。
inline
是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
inline
对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline
的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
inline
不建议声明和定义分离,分离会导致链接错误。因为inline
被展开,就没有函数地址了,链接就会找不到。
auto 关键字
在早期C/C++ 中auto
的含义是:使用auto
修饰的变量,是具有自动存储器的局部变量
C++11中,标准委员会赋予了auto
全新的含义即:auto
不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto
声明的变量必须由编译器在编译时期推导而得。
注意:使用auto必须要初始化
auto
会去掉const属性
auto
与指针和引用结合起来使用
用auto
声明指针类型时,用auto
和auto*
没有任何区别,但用auto
声明引用类型时则必须加&
int a = 10;
auto b = &a;
- 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
auto a = 1,b = 2;
auto c = 3,d = 3.14;//编译失败
3.范围for
普通遍历数组
int arr[] = {1, 2, 3, 4, 5};
for(int i = 0; i < sizeof(arr)/sizeof(arr[0]) ; ++i)
{
cout << arr[i] << endl;
}
C++11范围for
//自动依次取arr中的每个元素,赋值给e
for(auto e : arr)
{
cout << e << endl;
}
//每个数都+1
for(auto& e : arr)
{
e++;
}
范围for必须是数组名
以下情况是不能使用范围for的,因为数组名已经变成了指针
void Test(int a[])
{
for(auto e : a)
{
cout << a[i] << endl;
}
}
int main()
{
int arr[] ={1, 2, 3, 4, 5};
Test(arr);
return 0;
}
auto不能自动推导的场景
auto不能做参数,不能声明数组
NULL和nullptr
指针没有合适的值初始化时,我们一般会把它设置为空指针
int* ptr1 = NULL;
int* ptr2 = 0;
其实NULL是一个宏,实现如下
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
NULL 和 0 在C++可能出现的问题
void test(int* )
{
cout << "int*" << endl;
}
void test(int )
{
cout << "int" << endl;
}
int main()
{
test(0);
test(NULL);
}
看到结果,发现并没有调用到第一个函数.
所以C++建议使用nullptr
。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)