C++的8个基础语法

C++入门

  • 命名空间
    • 定义
    • 命名空间的使用
  • C++输入&输出
  • 缺省参数
    • 定义
  • 函数重载
    • 定义
    • C++支持函数重载的原理--名字修饰
  • 内联函数
    • 定义
    • 特性
  • auto关键字
    • 类型别名的思考
    • auto定义
    • 使用细则
    • 不能推导的场景
  • 基于范围的for循环
    • 定义
    • 范围for的使用条件
  • 指针空值nullptr

命名空间

在C语言当中,我们知道命名是不能重复的,那么写代码的时候很容易遇到这种问题,引头文件的时候,里面会包含很多库函数,有些时候我们并不是很了解这个头文件里面有什么库函数,这时候如果我们自定义的函数或者是变量和库函数的命名冲突就很令人头疼,有时候想名字都要想半天,C++当中namespace关键字的出现就是针对这种问题的。
例:

rand是随机值的函数名,所以报错是rand重定义,也就是命名冲突。
使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染。

定义

正常的命名空间定义

#include 
#include 
namespace baiye//baiye是命名,这里也不能有命名冲突的情况,比如这里用rand进行命名就会与头文件stdlib.h中的rand函数名冲突
{
	int rand = 10;//这些定义的变量,函数,类型等等就是baiye的成员
	int add(int x, int y)
	{
		return x + y;
	}
	struct app
	{
		int a;
		int* p;
		struct app* next;
	};
}

注意定义在函数中才是局部变量,这里并不是局部变量,是全局变量,并不影响生命周期,只是被限制查找。
命名空间的嵌套

namespace N1
{
	int rand = 10;
	int add(int x, int y)
	{
		return x + y;
	}
	namespace N2
	{
		int x = 20;
	}
}

命名空间的合并
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

//test.cpp
namespace N1
{
	int rand = 10;
	int add(int x, int y)
	{
		return x + y;
	}
}
//test.h
namespace N1
{
	int x = 20;
}

当程序运行起来之后就是这样的:

namespace N1
{
	int rand = 10;
	int add(int x, int y)
	{
		return x + y;
	}
	int x = 20;
}

注意:一定要命名空间的名字相同且在同义级才可以。上面的嵌套两个命名空间就不是同一级。
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。命名空间是限定域。
命名空间只能影响查找规则,查找是局部优先,然后才是全局查找,在命名空间的话需要指定的方式去查找。

命名空间的使用

命名空间就像在整个程序中在某个空间盖起了一个房子,然后加了一把锁,防止房子里面的成员命名与外面冲突,如果我们要访问就需要去开锁。

::是域作用限定符,前面加上命名空间的域名,后面是这个域中你要访问的成员,如果不加就是全局查找
加命名空间名称及作用域限定符

#include 
#include 
namespace N1
{
	int rand = 10;
	int add(int x, int y)
	{
		return x + y;
	}
}
int main()
{
	printf("%d\n", N1::rand);//在这里N1::就相当于命名空间N1这个域的钥匙一样
	return 0;
}

C++的8个基础语法_第1张图片
使用using将命名空间中某个成员引入

#include 
#include 
namespace N1
{
	int x = 10;
	int y = 20;
	int add(int x, int y)
	{
		return x + y;
	}
}
using N1::x;//这里就像域N1中的x走出了房子,所以x和全局变量没有区别了,也不会被限制查找
int main()
{
	printf("%d\n", N1::y);
	printf("%d\n", x);
	return 0;
}

C++的8个基础语法_第2张图片
使用using namespace 命名空间名称 引入

#include 
#include 
namespace N1
{
	int x = 10;
	int y = 20;
	int add(int x, int y)
	{
		return x + y;
	}
}
using namespace N1;//这个相当于把N1这个房子给拆了,成员都成为了不被限制搜索的全局变量
int main()
{
	printf("%d\n", y);
	printf("%d\n", x);
	return 0;
}

C++的8个基础语法_第3张图片

C++输入&输出

#include 
//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;//把标准库直接展开,使用cout等方便,不然就要std::cout,很麻烦
int main()
{
	cout << "Hello world!!!" << endl;
	return 0;
}


说明:

  1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
  2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
  3. <<是流插入运算符,>>是流提取运算符。
  4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。
  5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,这里先不过多解释
    C++的8个基础语法_第4张图片
    这里就是将你要输入或者是打印的东西插入流中,包括换行等等操作也是插入流中,要注意顺序。
    自动识别数据类型
#include 
using namespace std;
int main()
{
	int x;
	double y;
	char w;
	cin >> x;//输入
	cin >> y;
	cin >> w;
	cout << w << endl;//输出
	cout << x << endl;
	cout << y << endl;
	return 0;
}

C++的8个基础语法_第5张图片
因为C++兼容C的语法,所以有些情况下用C的输入输出更加方便。

缺省参数

定义

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

#include 
using namespace std;
int add(int x = 10, int y = 15)
{
	return x + y;
}
int main()
{
	int sum;
	sum = add();
	cout << sum << endl;
	sum = add(1);
	cout << sum << endl;
	sum = add(1, 2);
	cout << sum << endl;
	return 0;
}

#include 
using namespace std;
int add(int x, int y = 15, int z = 5)//半缺省参数,必须从右往左依次来给出,不能间隔着给
{
	return x + y + z;
}
int main()
{
	int sum;
	sum = add(1);//这里必须有一个参数
	cout << sum << endl;
	sum = add(1, 2);
	cout << sum << endl;
	return 0;
}


注意:1.缺省参数不能在函数声明和定义中同时出现。(如果声明和定义同时出现,编译器就不知道应该用哪个缺省值)2.缺省值必须是常量或者全局变量

函数重载

顾名思义,可以命名相同的函数,当然,不是在不同的命名空间里。

定义

函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
参数类型不同

#include 
using namespace std;
int add(int x, int y)
{
	return x + y;
}
double add(double x, double y)
{
	return x + y;
}
int main()
{
	int a;
	int b;
	double c;
	double d;
	cin >> a >> b;
	cin >> c >> d;
	int sum;
	sum = add(a, b);//传的两个值都是整形
	cout << sum << endl;
	sum = add(c, d);//传的两个值都是双精度浮点型
	cout << sum << endl;
	return 0;
}

这里就自动识别参数中不同数据类型的传值应该去哪个函数。
C++的8个基础语法_第6张图片
参数的个数不同

#include 
using namespace std;
int add(int x)
{
	return x;
}
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int a;
	int c;
	int d;
	cin >> a;
	cin >> c >> d;
	int sum;
	sum = add(a);
	cout << sum << endl;
	sum = add(c, d);
	cout << sum << endl;
	return 0;
}

这里是自动识别参数的个数。
C++的8个基础语法_第7张图片
不同数据类型的参数顺序不同

#include 
using namespace std;
int add(int x , char y)
{
	return x + y;
}
int add(char x, int y)
{
	return x + y;
}
int main()
{
	int a;
	char b;
	char c;
	int d;
	cin >> a >> b;
	cin >> c >> d;
	int sum;
	sum = add(a, b);
	cout << sum << endl;
	sum = add(c, d);
	cout << sum << endl;
	return 0;
}

这里必须是参数不同的数据类型,不然编译器无法判断传入哪个函数
例:
C++的8个基础语法_第8张图片
这里就产生了歧义,也叫做二义性。
C++的8个基础语法_第9张图片

C++支持函数重载的原理–名字修饰

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
因为一个项目中,有很多个头文件和源文件组成,函数是很重要的部分,两个不同文件中,一个调用函数一个是函数的定义,这个时候就要找函数的地址把他们链接起来,那么找地址的部分在函数重载是怎么实现的?
我们知道,函数名就是函数地址,那么编译器就会用修饰规则去根据函数重载的函数名和参数进行修饰,变成两个相对接近的名字。
C和C++的符号表区别是这样的:
C++的8个基础语法_第10张图片
地址和函数名是编的,但是原理是这样的,C语言中在链接的过程中只有函数名被修饰,C++中函数名和参数一起被修饰。
让我们看一下Linux环境下的g++中C和C++链接时候的情况:
采用C语言编译器编译后结果:
C++的8个基础语法_第11张图片
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
采用C++编译器编译后结果:
C++的8个基础语法_第12张图片
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参
数类型信息添加到修改后的名字中。
我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度
+函数名+类型首字母】。
再来看看Windows下名字修饰规则
C++的8个基础语法_第13张图片
对比Linux会发现,windows下vs编译器对函数名字修饰规则相对复杂难懂,但道理都
是类似的,我们就不做细致的研究了。
通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分,调用主要是看参数,和返回值无关。

内联函数

定义

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
我们知道,在创建函数调用时,会产生一个函数栈帧,占用内存,如果我们函数中里面还有很多个不同或者是相同的函数(比如一个自定义函数当中有很多个交换两个数据的自定义函数),那么会栈溢出:
C++的8个基础语法_第14张图片
假设函数1,2,3,4都是一个函数,那么我们在C语言当中可以把这个函数定义成宏,在预编译的时候,头文件会被展开,宏会进行文本替换,所以,就不会创建这么多的函数栈帧。
不过用宏代替函数,写的时候非常麻烦,稍微不注意就会运算错误,并且还不能进行调试。
在C++中,用inline关键字修饰函数,会改掉宏替代函数的缺点,在调用的地方直接展开。
代码1:

#include 
using namespace std;
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 10;
	int y = 20;
	int z = add(x, y);
	cout << z << endl;
}

C++的8个基础语法_第15张图片
没有inline修饰的函数在汇编时会有call add(调用add函数)
代码2

#include 
using namespace std;
inline int add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 10;
	int y = 20;
	int z = add(x, y);
	cout << z << endl;
}

再查看这段代码时要做一些设置(因为debug下面需要调试,所以默认不会展开):
C++的8个基础语法_第16张图片
C++的8个基础语法_第17张图片
C++的8个基础语法_第18张图片
这里就没有去调用add函数,说明展开了。

特性

当然,也不是什么函数都能修饰成内联函数的。
不然,去把递归的函数修饰一下,岂不是很精彩。

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
#include 
using namespace std;
inline int add(int x, int y)
{
	int sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	sum = x + y;
	return sum;
}
int main()
{
	int x = 10;
	int y = 20;
	int z = add(x, y);
	cout << z << endl;
}

C++的8个基础语法_第19张图片
所以,内联函数最好还是被调用频繁,而且行数不多的函数。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
我们知道,在一个项目中,往往函数的定义和声明不在同一个文件内,所以编译器链接的时候,会有一个符号表,里面会存入函数的地址,然后再调用函数的时候会在符号表中找这个函数的地址从而进行调用。
然而用inline修饰过的函数却不会出现在函数表内,这时编译也不会通过:
C++的8个基础语法_第20张图片
C++的8个基础语法_第21张图片
C++的8个基础语法_第22张图片
C++的8个基础语法_第23张图片
这是编译报错,意思是链接错误。
因为每个文件都是自己工作自己的,不会干扰到其他文件,当编译器看到函数被inline修饰的时候就已经不会把地址放在符号表里面了,至于编译器忽略iniline的事情是再调用的时候,所以不用担心。
这个程序当中,头文件虽然在源文件展开,但是并没有函数的定义在源.cpp展开,所以没办法被使用。
C++的8个基础语法_第24张图片
C++的8个基础语法_第25张图片
当我们把定义和声明放在一起就可以运行了。
C++的8个基础语法_第26张图片

auto关键字

类型别名的思考

在C++当中会有一些很长的类型名,例:

#include 
#include 
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
   "橙子" },
	  {"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

std::map::iterator 是一个类型,但是该类型太长了,特别容
易写错。

至于用typedef给类型取别名,那么我们就要记住很多别名,非常麻烦。

auto定义

auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
typeid(b).name()的意思是打印b的类型

#include 
using namespace std;
int main()
{
	int a = 10;
	auto b = a;//这里自动推导a赋值给b的类型是什么
	auto c = "a";
	auto& d = a;
	auto* e = &a;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
	return 0;
}


注意
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

使用细则

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。

#include 
using namespace std;
int main()
{
	int a = 10;
	auto b = &a;
	auto& d = a;
	auto* e = &a;
	cout << typeid(b).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;

	return 0;
}

C++的8个基础语法_第27张图片
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

不能推导的场景

auto不能作为函数的参数
因为函数在创建栈帧的时候编译器无法推导a类型,也就无法得知栈帧创建的具体大小。
auto不能直接用来声明数组

基于范围的for循环

定义

for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
这里等于把arr数组遍历一遍然后赋值给e。

for里面的arr数组是会遍历整个数组的。
当然如果你想改里面的值就要用引用了,指针是不行的,因为数组里面的都是int类型的。
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

范围for的使用条件

for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围。
对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

int test(int arr[])//因为传过来的不是数组,而是指针,所以无法确定数组的范围
{
	for (auto& e : arr)
		cout << e << endl;
}

迭代的对象要实现++和==的操作。(这个以后说)

指针空值nullptr

我们平时初始化一个指针如果不知道他指向谁,那么就要让他指向一个空指针(NULL),这是一个良好的习惯。
其实NULL是一个宏,C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL   0//也就是说NULL等于0
#else
#define NULL   ((void *)0)
#endif
#endif

C++也是一样的,那么使用重载函数的时候就会遇到以下问题:
C++的8个基础语法_第28张图片
如果想让NULL走第二个函数就要强制转换类型,因为库不能随意改动,只能打补丁,所以就出现了一个关键字nullptr,效果是和NULL一样的,只不过没有这个BUG。
C++的8个基础语法_第29张图片
注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

你可能感兴趣的:(C++,笔记,c++,c语言,开发语言)