一、 第一个C++程序

经典Hello World实操


#include <iostream>
using namespace std;

int main()
{
	cout << "hello world" << endl;
	return 0;
}

二、C++核心语法扩展

1.命名空间(namespace)

1)为什么需要命名空间?

在实际生产中,一个大的项目需要分成众多小项目来合作完成,最后代码结合在一起从而实现某个功能,每个人代码中的命名习惯不同,就会产生命名重复的问题。

通过namespace划分作用域每个函数去找属于自己域的变量,这样即使命名重复也不会出现问题。

看例子:
在这里插入图片描述

这里可以看到在同一作用域内,变量名会发生冲突从而导致出错。 而祖师爷发明的namespace此时就可以发力了

2)命名空间定义及格式

  • namespace命名空间实际上是划定了与全局域独立的一个域,各个域之间相互独立,所以不同的域中可以定义同一个变量名。
  • C++中有函数局部域,全局域,命名空间域,类域
  • namespace只能定义在全局内,不能定义在函数内部,但是域内还可以嵌套定义
  • C++标准库都放在std的命名空间中
namespace A
{
   int a = 10;
}
namespace B
{
   int a = 20;
}

int main()
{
   //指定命名空间访问
   printf("%d", A::a);
   printf("%d", B::a);
   return 0;
}

3):: 域作用限定符

:: 域作用限定符

作用:指定去找哪个“域”里的东西:
:: 就是 C++ 里的“导航定位符”,用来明确指出某个名字属于哪个作用域。

不给 :: 时,默认从全局中寻找

在全局域中和命名空间域中,含有相同变量名时,如果不指明具体使用哪个,优先使用全局域中变量值
在这里插入图片描述

  • 嫌麻烦可直接全局展开(如 using namespace std;),但容易命名冲突,冲突风险大,项目不推荐,日常练习可以使用。

  • 或者选择局部展开。

//局部展开
#include <iostream>
using  A::b;

namespace A
{
	int a = 10;
	int b = 20;
}
int main()
{
	
	printf("%d", b);
	return 0;
}

局部展开时不需要加namespace,直接写命名空间名即可,如果是完全展开则需要加上namespace

使用using展开,命名空间内的元素相当于放置在了全局变量中,在平时的练习中使用会更加方便,但是实际生产中不会展开,会有冲突问题。

2.C++输入与输出(cin&cout)

在hello world代码中我们可以观察到这样一个头文件
在这里插入图片描述
这和C语言中的#include<stdio.h>作用一致,都是标准输入输出库 头文件

作用:

  • std::cout——向屏幕打印 \ 输出数据(C语言中的printf)
  • std::cin——用于接收键盘数据(C语言中的scanf)
  • std::endl——换行符与\n相似

1)流插入运算符 (<<)

与C语言中的左移右移运算符一致

#include <iostream>

int main()
{
	int a = 10;
	//C语言写法
	printf("%d\n", a);
	//C++写法
	std::cout << a << std::endl;
	return 0;
}

<<指向cout,表示a流向cout(屏幕),输出打印

在c语言中打印需要手动输入格式 “%d” “%s”…但是在C++中无需手动输入格式,会自动识别格式并输出,而且C++程序包含C语言所以printf可以使用。

2)流提取运算符(>>)

#include <iostream>

int main()
{
	int a = 0;
	std::cin >> a;
	std::cout << a << std::endl;

	return 0;
}

>>指向cin,表示从cin(键盘)流入a

同理cin函数也不需要手动输入格式,直接输入则可以自动识别,但是如果输入格式不正确,如图中a为整型,我们输入字符‘c’时,会识别失败,从而放弃对a的写入。

3.缺省参数

在函数定义时就给一个初始值,在调用函数时如果不给定参数则会直接使用定义时的初始值

#include <iostream>

int Add(int x = 10, int y = 20int z = 30)
{
	return x + y + z;
}

int main()
{
//全缺省(不传参)
	int re1 = Add();
	std::cout << re1 << std::endl;
	
//全缺省(传参)
	int re2 = Add(1, 2, 3);
	std::cout << re2 << std::endl;
	return 0;
}

re1 = 60
re2 = 6

在re1中,没有给Add传参数,所以直接使用定义时的初始值,在re2中传入参数则正常利用传入参数计算

全缺省和半缺省

  • 全缺省:整个函数都不传入参数时,全部利用定义时的初始值计算,若传入参数则利用传入参数进行计算
  • 半缺省:在函数定义时的形参中不完全给全

注意事项:

  1. 缺省值必须从右至左连续提供(不能跳跃)

正确:int Add(int x, int y = 20, int z = 30 );
错误:int Add(int x = 20, int y, int z = 30 );

  1. 传入参数必须从左至右(不能跳跃)

正确:Add(1, 2, 3);
错误:Add(1, , 3);

4.函数重载(Function Overloading)

在C语言中,任何类型的变量、函数名也不能命名冲突,但是C++中对此进行了巨大改变,同名的函数可以多次定义

函数重载定义: 同名函数的不同形态
在这里插入图片描述

Add函数被多次定义,但是很明显函数内的参数都各有不同(个数不同,顺序不同,类型不同)

创建重载函数时的三个规则

  1. 参数个数不同
  2. 参数顺序不同
  3. 参数类型不同

注意事项:
1.返回值不同不能作为重载函数的条件!!!(因为函数在调用时,不会区分想用哪个)
2.只有一个形参的函数不能再同时定义一个没有参数的函数,因为构成缺省参数,编译器会不知道调用哪个

//构成缺省参数
void f()
{
	cout << "f()" << endl;
}

void f(int x = 10)
{
	cout << "f(int x)" << endl;
}

int main()
{
	f();//报错编译器不知道调用哪个函数
	return 0;
}

三.C++关键语法特性

1.引用(&)

1)常规引用

概念: 给已存在的变量取 “ 别名 ”,语法概念上不开空间

使用方法:(如图所示)
在这里插入图片描述

直接在新定义的变量名前加一个&(C语言中为取地址符号,在这里通用),从这里还可以看到,引用变量和被引用变量的地址是同一块,那么就可以得出,引用不会为其单独开辟空间

作为新的功能出现在C++中,那么他一定是解决了之前存在的问题,那么引用(&)是解决了C语言中的什么问题呢?

优点:
方便传参的使用,在C语言中,若不传入指针,那么形参的改变并不会改变实参,但是传入指针又会显得麻烦,容易弄混指向问题,但是引用变量与被引用变量使用同一块空间,所以形参用引用来接受便可以直接对实参进行改变。
在这里插入图片描述

引用来承接参数就可以直接使用,不会存在忘记解引用的问题

注意事项:
1.被引用变量必须初始化
2.一个变量可以有多个引用(即一个变量可以有多个别名)
3.一个引用一旦与被引用变量捆绑则不能改变被引用对象(指针可以改变指向,但是引用不可以)

2)const引用

可以引用一个const对象,但是必须使用const引用(权限相同)
const引用也可以引用普通变量(权限不可以放大,但是可以缩小)

权限放大问题

被const修饰的变量必须使用const引用来引用,因为被cosnt修饰,所以无法改变i的值在这里插入图片描述

权限缩小问题

普通变量可以被const引用,相当于限制了引用 j 的访问权限,但是 i 的访问权限不变。
在这里插入图片描述

临时变量与其常属性

注意事项:

  1. 表达式计算结果:如 a + b;
  2. 函数返回值:按值返回 (return Value)时
  3. 隐式类型转换:不同类型之间的赋值或传递(如double类型值赋值给int类型)

以上三种情况都会产生临时变量,具有常属性(右值性),所以在引用时也需要const引用

//表达式计算结果错误示范
int main()
{
	int a = 10;
	int b = 20;

	int& c = a + b;
	return 0;
}

a + b的表达式结果并不是直接赋值给引用c的,而是放入一个临时变量中,再将临时变量的值拷贝给c,从而达到赋值操作

a + b具有常属性,但是左值引用c的值可以被修改,左值可被修改,右值无法修改故产生问题

//函数返回值错误示范
int Add(int& a, int& b)
{
	return a + b;
}

int main()
{
	int a = 10;
	int b = 20;
	int& c = Add(a, b);
	return 0;
}

同理Add的返回值也存在于临时变量中,左值可修改,右值无法修改,故报错

//隐式类型转换错误示范
int main()
{
	int a = 10;
	double& b = a;
	return 0;
}

在这里插入图片描述

3)引用和指针的关系

相辅相成,各有各的特点,无法替代对方

  • 语法概念上,引用不开辟空间;指针需要存储地址,需要开空间
  • 引用了一个对象时就不可以在引用其他对象;指针可以改变指向对象
  • 引用可以直接访问对象;指针需要解引用访问对象
  • 引用对象的大小为被引用对象的大小,而指针的大小始终相同(32位下四字节,64位八字节)

2.inline(内联函数)

在程序运行过程中,调用函数在汇编语言下需要call函数开辟栈帧等操作,而内联函数inline是直接展开代码执行
在这里插入图片描述
但是在编译器中,inline内联函数只适用代码短小的函数,代码非常多时,若多次调用函数展开,意味着每次调用的地方都会复制一份完整的函数指令,最终可执行文件会变大**(代码膨胀Code Bloat)**

inline在编译器眼中更像是一个建议,如果代码过长编译器则不会接受你的建议,继续正常开辟栈帧调用函数。

3.nullptr与NULL

  • C语言中NULL被宏定义成了((void*)0)
  • C++中NULL则被定义成了整型 0

C和C++中的标准不同,有些场景使用NULL会出现问题

在这里插入图片描述
本想通过f(NULL)调⽤指针版本的f(void*函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。
这里传入NULL本想让他调用第二个,可是C++的规定使得NULL为整型所以只能调用第二个

在C中还有一个隐藏坑:

void 可以隐式转换成任意类型指针,但是C++中不会隐式类型转换*

// C++
void* p1 = NULL;
int* p2 = p1; // 此时如果 p1 不强转则会报错,需要 int* p2 = (int*)p1;

// C(不严格)
void* p1 = NULL;
int* p2 = p1;

在这里插入图片描述
在C++中即使强转NULL类型,函数f(int x) 也不能接受值,这里构成了函数重载(参数类型不同)*

所以创造了nullptr,在C++中,他既是空指针,也可以隐式类型转换成任意类型指针。
在这里插入图片描述
下期预告:类和对象(上)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐