C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr

C++基础入门

    • C++ 关键字
    • 命名空间
    • 缺省参数
  • 函数重载
      • 函数重载底层实现原理
      • extern “C”
  • 引用
      • 引用的底层实现原理.....(后序更新)
    • 内联函数
    • auto 关键字
    • [空指针 nullptr](https://www.cnblogs.com/porter/p/3611718.html)
    • 基于范围的 for 循环

C++ 关键字

问:c++共有多少个关键字?
答:在c++98版本 一共有63个关键字

命名空间

使用命名空间的目的是:对标识符的名称进行本地化,以避免命名冲突或名字污染; 比方说:正在读博客的你和我一起写个项目工程,但是我们写的代码可能会有一些重复的定义变量名/类名/函数名,我定义一个变量 a ;可能你也定义一个变量 a ,所以就可能会发生冲突;而且我们自己写代码的时候也可能会发生相同的定义名称,这时候你就很为难编译器,因为它不知道哪一个是对的;

下面是:命名空间的定义和使用,相信一看代码就懂~~!!

#include 
#include 

namespace A
{
	int a = 10;
}

namespace B
{
	int a = 20;
}

int a = 30;
int main()
{
	printf("%d\n", A::a);
	printf("%d\n", B::a);
	printf("%d\n", a);
	system("pause");
	return 0;
}

接下来简要总结命名空间的使用~
(1)使用 using A::a ; 仅仅使用A空间的 a
(2)使用 using namespace A ;使用A空间的所有对象

点它==》命名空间的注意事项和常见的错误


缺省参数

先看一段代码,打印的结果是 20 10

void print(int a = 10)
{
	printf("%d ", a);
}

int main()
{
	print(20);
	print();

	system("pause");
	return 0;
}

很明显如果用户指定参数的话,则打印的是指定参数,否则则打印默认参数,定义函数里的参数可以形象的理解为备胎

看似简单的概念,背后往往有我们看不见的坑,如果不注意就会踩~
(1)参数不可以随便无顺序的指定,比方说:

void func(int a = 10, int b; int c = 20);

这样就错了,因为半缺省参数必须从右往左依次来给出,不能间隔着给
(2) 缺省参数不能在函数声明和定义中同时出现
为什么?因为如果两个地方都有,害怕你定义和声明的参数值不同,这时候编译器将无法确定使用哪个备胎(默认参数值)
C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第1张图片
错误是:重定义默认参数,意思就是重复定义了相同的值;
(3)缺省值必须是常量或者全局变量

(4)C语言不支持(编译器不支持)

C语言为什么不支持?这就引申出下一个概念《函数重载》


函数重载

这是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序) 必须 不同,常用来处理实现功能类似数据类型不同的问题;总结起来就是:如果两个函数的 函数名相同 && (参数个数 || 参数类型不同 || 参数顺序 );这就是函数重载;

注意一点,函数重载和函数的返回值类型没有关系

也就是说仅仅是返回值类型不同是不能构成函数重载滴~!!
C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第2张图片
why? why? why?我们先可以回顾一下C语言下是怎么回事?

void func1(int a);

int main()
{
	func1(10);

	system("pause");
	return 0;
}

这个编译的时候会通过吗?
回答 ‘会通过’ 的老铁,怕是你上 C 语言的时候偷偷打游戏了吧~~哈

这个是不会通过的,我们先看看出错原因?
C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第3张图片
说的是:编译器识别不了函数名,可是明明在代码里有这个 func1 函数名的啊,大家要看仔细点,那个是函数声明,不是函数定义《声明和定义区别》
所以编译器不会识别函数名,编译器找不到 _func1 这个符号,这个符号就是编译器经过汇编之后转换的名称,C语言的名字修饰规则非常简单,只是在函数名字前面添加了下划线。可是这和C++函数重载有啥关系?当然有关系。。。不信你看下面

我们再看看 C++语言里的汇编代码
C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第4张图片
我们发现C++的函数修饰非常复杂,如果我们再仔细看,会发现一些乱码 “?func1@@YAXH@Z” 这又是啥?这个东西就是编译器在汇编的时候将函数声明那一行的代码所转换的二进制代码,简单来说就是编译器将函数、变量的名称,变量顺序,参数个数,重新改编的机制;目的当然是为了区分函数;但是C语言在编译的时候根本就不管 形参类型和个数,但是C++ 却把它弄得很严格;
总结起来就是:c++ 只要两个函数的参数类型/个数/顺序不同,编译器就可以区分开来,但是C语言无法区分,因为C语言在编译的时候不管参数类型,这样就导致了如果有两个函数名相同的函数,C语言下编译器是无法识别的~~

这也就是为什么C语言不支持函数重载的原因!!

更加详细的解释:函数重载c/c++调用约定

函数重载底层实现原理

C++ 底层在函数编译的时候会进行重命名机制,这个重命名会因为函数参数不同和参数的顺序不同,最终生成的编译符号也不一样,由于不同的编译器规则是不一样的,但是这不重要,只要能生成唯一的符号就行了,

extern “C”


引用

关于引用的概念也特别好理解,就是取一个“别名”,比如你叫 “张三”,但是在家还有一个乳名叫 “蛋蛋”,而蛋蛋这个就是引用;引用不开辟新的空间,他和引用的对象共用一块内存空间;就像 “张三" 和 “蛋蛋” 指的都是同一个人!!

为什么要有这个概念?存在的作用是什么?后面讲!!

那该如何定义?

类型名& 引用变量名(蛋蛋) = 实体(张三);
注意:引用变量名的对象类型必须和实体是同种类型的;

//还有其他什么特点吗?

  1. 引用在定义的时候必须初始化; 就像一个’蛋蛋‘总得要指明是谁,总得要说出来给那个娃娃去乳名叫“蛋蛋”,不然这个名词就没有存在的意思
  2. 一个变量可以有多个引用; 就像一个人可以有多个“外号”一样,外号就相当于是引用
  3. 引用一旦引用一个实体后,再不能引用其他实体; 这个我实在想不到生活中有什么例子,不过好像微博的名字不能重复是吧,全国人民所有的微博名字都不重复,那就是不能在引用其他实体的意思~~!!

===下面是使用引用的场景!!!c++的大佬定义出来这个引用,总得要做点事情吧,不然定义出来干嘛尼!!~~

1.作为参数,,热身热身,先写一段代码

#include 
#include 
using namespace std;

void swap(int& a, int& b)
{
	int tem = a;
	a = b;
	b = tem;
}

int main()
{
	int aa = 10;
	int bb = 20;
	cout << aa << "  " << bb << endl;
	swap(aa, bb);
	cout << aa << "  " << bb << endl;

	system("pause");
	return 0;
}

由于操作形参就是在操作实参,指的是同一个对象,所以会改变aa,bb的值

来点难度!!~

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;
	system("pause");
	return 0;
}

打印结果是多少?答:7
ret 明明引用的是Add(1,2),为什么会变为Add(3,4);
个人觉得:由于c在栈开辟的空间,所以在作为返回值的时候会随着函数的调用结束而被系统回收;这时候编译器会给一个警告!

C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第5张图片
注意:如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。

在前面加一个"static" 关键字 =》 static int c = a + b;

这样 c 就不会随着函数结束而被释放;

之前我们提到为什么要使用引用,当然是它的效率高,比方说:在传参数的时候

这里我们使用 clock 这个函数对时间做记录,单位是毫秒ms

#include 
#include 
#include 
using namespace std;

struct A
{
	int a[10000];
};

void func1(A& a){}

void func2(A a){}

void test()
{
	A a;
	int max = 10000;
	size_t begin = clock();
	for (int i = 0; i < max; ++i)
	{
		func1(a);
	}
	size_t end = clock();

	size_t begin2 = clock();
	for (int i = 0; i < max; ++i)
	{
		func2(a);
	}
	size_t end2 = clock();

	cout << "传引用所用的时间为:" << end - begin << endl;
	cout << "传值所用的时间为  :" << end2 - begin2 << endl;

}

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		test();
	}

	system("pause");
	return 0;
}

从结果我们可以看到差别还是很大的,特别是在做一些大型的数据传输的时候,

C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第6张图片

我们再来看一下他们分别作为返回值类型的效率

struct {int a[10000];};

A a;
A& func1(){return a;}

A func2(){return a;}

int main(){
	size_t begin1 = clock();
	for (int i = 0; i < 1000; ++i){
		func1();
	}
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < 1000; ++i){
		func2();
	}
	size_t end2 = clock();

	cout <<"传引用:"<<end1 - begin1 << endl;
	cout <<"传值  :"<<end2 - begin2 << endl;

	system("pause");
	return 0;
}

这个结果可是差的可是特别大了
C++入门,函数重载,内联函数,缺省参数,引用,命名空间,auto关键字,nullptr_第7张图片
为什么值和引用的效率会差的如此之大,是因为不管在传参的时候还是在作为返回值类型的时候,‘值’ 它总是先要进行拷贝,也就是做一个副本,而不是直接对实体进行操作,所以会花费很大的时间,这就是我们有时候在电脑里移动一个文件的时候感觉很快,但是拷贝的时候就很慢;

引用的底层实现原理…(后序更新)

引用 vs 指针 之间的区别?

  1. 引用在定义时必须初始化,指针没有要求

  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

  3. 没有NULL引用,但有NULL指针

  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  6. 有多级指针,但是没有多级引用

  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  8. 引用比指针使用起来相对更安全

内联函数

内联函数是什么?

  • 对函数进程内联扩展,是一种编程语言结构

内联函数的特点是什么

  • 以inline关键字修饰
  • 以空间换取时间的做法,省去调用函数的开销
  • 编译器将使用相应的函数代码替换函数调用

为什么要使用内联函数

  • 对于小型函数来说,由于函数的执行时间要远远小于函数的调用时间,所以使用内联函数将调用函数替换为一个函数体(在函数的调用点进行替换或者插入)这样可以避免函数的调用造成一定的时间开销;

宏 vs 内联函数

  • 宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。

  • 增加代码的复用性;提高性能

  • 可读性差;没有类型安全检查;不方便调试;可维护性差;

内联函数:

  • 节省了变量在栈上的开销和函数返回/调用的时间开销,内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)

  • 如果函数的代码较长,使用内联将消耗过多内存,如果函数体内有循环,那么执行函数代码时间比调用开销大。

内联函数的使用场景 和 注意事项

  1. 如果代码比较复杂,带有循环和递归不适合使用内联,因为这可能会消耗极大的内存空间,所以就要在空间和时间方面权衡进行考量,不过对于编译器来说,如果发现函数中有循环或者递归就会直接忽略使用内联,把它当做普通函数;
  2. 不可以把内联函数的定义和声明分开写(分别写在两个.cpp文件里)因为这会导致链接错误,如果内联函数被展开就没有函数地址,导致链接的时候找不到地址;
  3. 适用于一些小型简单的函数,对于那些大型的复杂函数没必要使用内联函数,因为一些大型的复杂函数它的执行时间也是很慢得,函数调用所使用的时间对于函数执行的时间是微不足道的;

点它==》深入理解内联函数

auto 关键字

auto的用法还是比较多的,先做一个简答的总结,后序再继续更新

  • 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
  • auto不能作为函数的参数,也不能声明数组类型,因为编译器无法对其的实际类型进行推导

空指针 nullptr

  • 在以往的c头文件中会把NULL定义为0,或者一个无类型指针(void*)常量
  • 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
  • 为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。因此:为了避免混淆,C++11提供了nullptr
  • nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:typedef decltype(nullptr) nullptr_t;
  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

https://www.cnblogs.com/mrlsx/p/5510496.html

基于范围的 for 循环

这是个很有意思的事情,而且用起来也很方便,先看一段代码

int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	{
		cout << a[i] <<" ";
	}
	cout << endl;
	for (int* p = a; p < a + sizeof(a) / sizeof(a[0]); ++p)
	{
		cout << *p <<" ";
	}

	system("pause");
	return 0;
}

这两种方法是我们平时所用遍历数组元素,但是有一个缺陷就是,在遍历的时候必须要计算出数组范围,如果算错还可能导致越界;所以C++ 11 提出了一个更加简洁的办法就是~~~

范围for循环的用法 :for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

	int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
	for (int e : a)
	{
		cout << e << " ";
	}

稍微注意一点就是:被迭代的范围必须要是确定的,如果被迭代的作为参数传到函数的话,就会报错,因为范围不确定,,比如:

void TestFor(int a[])
{
	for(int& e : a)
	cout<< e <<endl;
}

学习c++是一个很漫长的过程,让我们一起加油!!~~~

你可能感兴趣的:(学习篇---服务端,C/C++,内联函数和函数重载,缺省参数和引用,命名空间,auto关键字,nullptr)