C++入门

本文介绍了C++的基础语法。包括命名空间解决变量冲突的方法,输入输出流的使用,缺省参数的规则,函数重载的条件及原理,引用的概念、使用场景和与指针的对比,内联函数的定义、特性与缺陷,auto自动推导类型,以及nullptr的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:

首先C的语法99%都适用于C++
C语言有很多缺陷,C++祖师爷为了补充缺陷所编写

一.命名空间:

首先看一下以下代码的问题:
在这里插入图片描述
这个报错的原因是rand重定义,在stdlib.h库中里面有rand,导致与全局变量rand冲突。
C语言并没有解决这个冲突,只能换个名字避免。

域作用限定符:

先提出个域的概念:全局域,局部域,命名空间域,类域。

全局域与局部域:

例如以下代码:
在这里插入图片描述
取的局部优先原则打印的1。
那要打印全局域的x=0就需要:域作用限定符“::”
在这里插入图片描述
域::x如果域没有内容,默认指的是全局域

命名空间域:

如果全局变量有多个x,那么就用到C++的命名空间域
在这里插入图片描述
可以看出两个x都能打印出来。

域的搜索优先级:

编译器搜索原则:不指定域:1. 当前局部域
             2.全局域
        指定域:直接去指定域搜索
验证如下:
在这里插入图片描述

顺便把先前的rand命名冲突解决了:
在这里插入图片描述
结构体用域表达比较特殊:
在这里插入图片描述
这就引出另一个问题,这样写多个命名空间域里面的变量就很麻烦,如下:
在这里插入图片描述
因此有一个办法可以解决,把命名空间域展开:
在这里插入图片描述
画图讲解一下:
在这里插入图片描述
在平时,只能通过域作用限定符“::”把命名空间域中的变量一个个“连接”,using namespace是直接把命名空间域的格子打开,随意使用其中变量。
但是,又出现了一个问题:在项目中是很少把命名空间域展开的,怕冲突太多。
所以用把某个成员名引入的语法:
例如如下:
在这里插入图片描述

总结:

命名空间的使用方法有三种:
1.加命名空间名称及作用域限定符:

int main()
{
	printf("%d\n", N::a);
	printf("%d\n", N::b);
	return 0;
}

2.使用using将命名空间中某个成员引入

using N::b;
int main()
{
	printf("%d\n", N::a);
	printf("%d\n", b);
	return 0;
}

3.使用using namespace 命名空间名称 引入

using namespce N;
int main()
{
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

二.输入&输出:

输出:

C语言中打印都是printf
C++则是:

include <iostream>

using namespace std;

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

其中<<叫做流插入。
cout 是io流,其中c是控制台(console)的意思,out就是向其输出
控制台相当于windos的CMD,就是下面这个
在这里插入图片描述
endl相当于"\n",endline的缩写,例子如下:
在这里插入图片描述
流插入能够自动识别打印的是什么类型:
在这里插入图片描述

输入:

C语言中输入时scanf;
C++常用的是cin:

int main()
{
	int i = 0;
	cin >> i;
	return 0;
}

其中 >>叫做流提取,并且也是自动识别类型
iostream在这里不在这里深入讲解

三.缺省参数:

以下代码为例:
在这里插入图片描述
可以看出Func()没有给出参数,则按照声明函数中的a = 0作为参数,给出Func(1)则啊= 1作为参数。

全缺省参数:

void Func(int a = 10, int b = 20, int c = 30)
{
	cout<<"a = "<<a<<endl;
	cout<<"b = "<<b<<endl;
	cout<<"c = "<<c<<endl;
}

半缺省参数:

void Func(int a, int b = 10, int c = 20)
{
	cout<<"a = "<<a<<endl;
	cout<<"b = "<<b<<endl;
	cout<<"c = "<<c<<endl;
}

注意:1. 半缺省参数必须从右往左依次来给出,不能间隔着给
   2. 缺省参数不能在函数声明和定义中同时出现
反例1:
在这里插入图片描述
反例2:
|在这里插入图片描述| 在这里插入图片描述

可以看出声明和定义存在时,会报错,是因为如果声明和定义参数给的值不一样时,会产生歧义,所以祖师爷规定只能在声明里应用缺省参数。
原因:
以此代码作为例子讲解:

Stack.hStack.cpptest.cpp
#include < iostream>
using namespace std;
void Func(int a, int b = 20, int c = 30);
#include “Stack.h”
void Func(int a, int b, int c )
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
#include “Stack.h”
int main()
{
Func(1,2,3);
return 0;
}

在这里插入图片描述

以上是代码执行过程缩减.。
假设:1.如果声明和定义函数都有缺省参数,可能会造成两者不一样,会出现分歧。
2.如果声明有缺省参数,定义无缺省参数,可以生成汇编然后向下进行。
3.如果声明无缺省参数,定义有缺省参数,编译就卡住了,无法向下进行。
故:在声明应用缺省参数,在定义不用。
再扩展一下最后链接的过程:
首先看反汇编:
在这里插入图片描述
在这里插入图片描述
可以看出call的不是函数的地址,jump是函数的地址
在形成汇编时:call (?),而不是call函数地址。在链接的时候才能在某一个文件中找到call函数的地址。(这里实力不佳,只知道结论,证明不出来)

四.函数重载:

C语言不许有同名函数
C++允许有同名函数,要求:函数名相同,参数不同,构成函数重载(这里要注意,必须在同一个域里面才能构成函数重载)
例子如下:
在这里插入图片描述
函数重载有三种:
1.参数类型不同;
2.参数个数不同
3.参数顺序不同(本质还是类型不同)
这里举例一下顺序不同
在这里插入图片描述
这里来谈一下C语言为什么不支持重载:
在链接时,C语言时直接用函数名去找地址,有同名函数的时候分不开。
C++有函数名修饰规则:名字中引入参数类型,各个编译器自己实现一套(命名规则没有做到统一)
这里说一下g++编译器的规则:_Z[函数名的长度][函数名][类型]
举个例子:
在这里插入图片描述
这里就是拿Linux上的g++编译器演示:

在这里插入图片描述在这里插入图片描述

gcc的编译器:

在这里插入图片描述在这里插入图片描述

在VS下演示(只写函数声明,不写函数定义可以显示出来):

在这里插入图片描述在这里插入图片描述

可以看到VS的编译规则跟gcc,g++的编译规则不一样
看个题:
在这里插入图片描述
这里选D,因为参数相同,只改变返回值的类型是不构成重载的

五.引用:

引用就是取别名,祖师爷嫌指针太麻烦想出的。
引例:
可以看出a,b的地址是一样的,就是名字不一样,就比如鲁迅是周树人也是鲁迅。
在这里插入图片描述
这里是交换的例子,语法是x是a的别名,y是b的别名,所以可以交换。这里先说语法,先不谈论底层。
在这里插入图片描述
注意:1.引用必须初始化。
2.引用定义后,不能改变指向。
例子:
在这里插入图片描述
初始化也是相同的道理:
在这里插入图片描述
综上所述:指针和引用的功能类似,重叠。
C++的引用,对指针使用比较复杂的场景进行一些替换,让代码更简单易懂,但是不能完全替代指针
引用不能完全替代指针的主要原因:引用定义后,不能改变指向。

函数中的引用:

1.做参数:

做参数时是做输出型参数。
扩展:

输入型参数就是传的拷贝,不可以改变输出型参数是在作函数参数的时候,是可以改变的
在这里插入图片描述在这里插入图片描述

2.做返回值:

首先是返回值为int类型,存储a的栈虽然会销毁,但是a的值会传到寄存器里,然后赋值给ret。

在这里插入图片描述
这里返回值为别名,a的别名也是a,a在栈中,所以在调用函数后栈会销毁,a的空间就没了,相当于“野别名”,ret = 原先存a的地址存的数据,有的编译器会报错。
在这里插入图片描述
返回值是别名,返回的是栈里面的空间,ret是别名,所以只能指向a原先在的地址,也是个“野引用”,原先的空间一旦被覆盖,用ret很危险。
在这里插入图片描述
所以当返回变量为局部变量的时候,不能用引用。
基本都是栈用引用做返回值。

引用与指针的效率对比:

传参:

测试代码如下:

#include<iostream>
#include<stdio.h>
#include <time.h>

using namespace std;

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 < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

可以看出引用的效率比指针快不少
在这里插入图片描述

返回值:

测试代码如下:

#include<iostream>
#include<stdio.h>
#include <time.h>

using namespace std;

struct A 
{ 
	int a[10000]; 
};
A a;
// 值返回
A TestFunc1() 
{ 
	return a; 
}
// 引用返回
A& TestFunc2() 
{ 
	return a; 
}
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	return 0;
}

在这里插入图片描述
引用做返回值的效率比指针的效率低了不少

引用与指针的区别:

语法:引用时别名,不开空间,指针是地址,需要开空间存地址。
底层:引用底层是用指针实现的

在这里插入图片描述在这里插入图片描述

2.引用必须初始化,指针可以初始化也可以不初始化。
3.引用不能改变指向,指针可以。
4.引用相对更安全,没有空引用,但有空指针,容易出现野指针,但是不容易出现野引用
5.sizeof和++用法不一样。比如引用sizeof是类型的大小,指针是地址的大小

const引用:

我们发现是不能运行的,是因为10是具有常性,不可被更改加个const即可
在这里插入图片描述在这里插入图片描述

又引出一个问题:

别名取不了,是因为权限放大,本来就只能只读,现在取完还想改,是不可行的加个const即可
在这里插入图片描述在这里插入图片描述

关于函数传参一样:

发现传不过去,也是一样,权限放大,一旦传过去n不仅有读的权限还有改的权限加个const即可
在这里插入图片描述在这里插入图片描述

以上都是权限放大的案例,权限缩小和平移是可取的:
在这里插入图片描述

六.内联函数:

引子:

假设调用100w次函数,就会建立100w个栈帧,然后再销毁,很麻烦。

int Add(int a,int b)
{
	return a + b;
}

int main()
{
	for (int i = 0 ;i<1000000;i++)
	{
		int ret = Add(1, 2);
	}
	return 0;
}

在C语言中,是用宏解决的

#define Add(a,b) ((a)+(b))

int main()
{
	for (int i = 0; i < 1000000; i++)
	{
		int ret = Add(1, 2);
	}
	return 0;
}

但是宏的易错点很多:因为是宏是预处理阶段进行替换,所以没有分号,也不是函数,并且需要括号控制优先级。
缺点也很多:调试不出来,语法复杂,没有类型的安全检查。
很麻烦,C++祖师爷就发明了内联函数:

inline int Add(int a,int b)
{
	return a + b;
}

int main()
{
	for (int i = 0 ;i<1000000;i++)
	{
		int ret = Add(1, 2);
	}
	return 0;
}

定义:

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
这里可以验证inline是否建立栈帧:
1.在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2022的设置方式)
debug版本操作如下:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

调用函数的反汇编内联函数的反汇编
在这里插入图片描述在这里插入图描述

可以看出直接相加了,没有调用函数。

缺陷:

大一点的函数就不适合运用内联函数,会造成代码膨胀。(这里是比较可执行程序)
在这里插入图片描述
代码膨胀带来的影响:我们平常下载游戏的时候会有安装包,安装包下载的就是可执行二进制程序,如果过于大下载的就慢。(就比如王者荣耀进去还要下载一会)

特性:

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,如果函数很长,就不会展开(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)
  3. 递归是不能用inline的,加了也会忽略掉
    当然,也不能把所有函数都加上内联。例如:300行的函数加上内联,技术显得(你懂的)
  4. 建议内联函数声明与定义不要分离,inline被展开,函数地址就没了,在链接时main函数想要调用call的地址就没有。
    在解释之前我们看一个关于链接错误的例子:
Stack.cppStack.h2_26test.cpp
在这里插入图片描述在这里插入图片描述在这里插入图片描述

这里就会出现一个重定义的问题
在这里插入图片描述
因为.h里面包含着函数声明和定义,.h在预处理阶段的时候会展开,Add函数就重复包含了,符号表里有两个Add。
所以解决这个问题解决需要:
1.声明和定义分离。(就一个文件里面有Add的定义)
2.用static修饰函数,静态修饰链接属性,只会在当前文件可见。(底层就是不会进符号表)
3.内联函数,inline修饰的函数不会进符号表,直接在.h文件中的函数变为内联函数
所以:小函数用内联,大函数用静态

然后你就会发现很乱,内联函数的定义和声明写哪的问题。
错误示范:

Stack.cppStack.h2_26test.cpp
在这里插入图片描述在这里插入图片描述在这里插入图片描述

运行会发现有链接错误
在这里插入图片描述
因为inline的函数并没有进入符号表,所以在链接的时候call地址不知道在哪。
所以,只需要声明和定义一起写:
在这里插入图片描述

七.auto

auto是自动推导类型
例子:自动把k的类型识别成int
在这里插入图片描述
指针:可以看出p1,p2都一样

在这里插入图片描述在这里插入图片描述

引用也同理:

在这里插入图片描述在这里插入图片描述

这些都基本用不上,而且用多了找类型都麻烦,只有类型够长才用得上。

平常写函数类型auot写
在这里插入图片描述在这里插入图片描述

在auto不能做函数的参数,但能做auto的返回值(C++新标准支持做返回值)
这里说一下返回值,不建议用,因为很乱。
例子:函数无限叠加,不知道返回的是声明类型,得挨个看
在这里插入图片描述

for与auto

引例:关于遍历数组

#include <iostream>

using namespace std;

int main()
{
	int array[] = { 1,2,3,4,5 };
	for (int i = 0;i < sizeof(array)/sizeof(int);i++)//以前的写法
	{
		cout << array[i] << ' ';
	}
	cout << endl;
	for (auto i : array)//现在的写法
	{
		cout << i << ' ';
	}
	return 0;
}

这里就用到迭代器的知识,依次取数组中的值赋值给i,自动迭代,自动判断结束
这里给数组的每个成员×2:之所以用引用,因为i是数组成员的复制
在这里插入图片描述
还有一个重要的是,迭代器不能迭代指针:(目前只需知道数组即可)
在这里插入图片描述

八.nullptr

引例:可以想想以下代码输出的什么

#include <iostream>

using namespace std;

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

void f(int* x)
{
	cout << "void f(int* x)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	return 0;
}

在这里插入图片描述
出现的原因就是C++中define NULL的替换
在这里插入图片描述

所以得写成这样:
在这里插入图片描述
C++11认为这样不合适,所以用nullptr
在这里插入图片描述
并且sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同都是8

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅碎时光807

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值