C++学习记录——일 C++入门(1)

文章目录

  • 一、C++关键字
  • 二、C++第一个程序
  • 三、命名空间
      • 1、域作用限定符
      • 2、了解命名空间
      • 3、命名空间的使用
  • 四、C++输入输出
  • 五、缺省参数
  • 六、函数重载
  • 七、引用
      • 1、引用符号
      • 2、引用的部分使用场景


一、C++关键字

C++学习记录——일 C++入门(1)_第1张图片

关键字有98个,在之后的文章中再逐个写出来。

二、C++第一个程序

#include 
using namespace std;

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

不同于printf,c++用cout,而endl也就是’\n’的意思,<<则是流输入。去掉 << endl就不是自动换行了。

C++兼容C的所有语法,我们可以直接用。

三、命名空间

1、域作用限定符

命名空间是什么?有什么作用?首先可以看到的是,这是一块空间。但这个空间为何叫命名?我们实际写代码时,如果多人写代码,然后引用到其中一个文件来,就会出现一个问题,如果有两个人创建的变量名一样,那么程序就出错了,所以C语言就有这样一个缺陷。C++为了解决这种冲突,就出现了命名空间这个语法。
我们先一点点来理解命名空间
C语言中有全局域和局部域 ,有了全局变量后,在一个函数里再加入一个同名局部变量,那么在函数里打印出来的值就是局部变量的值。看代码

int a = 0;

void f1()
{
	int a = 1;
}

void f2()
{
	int a = 2;
	printf("%d\n", a);
}

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

这时候的结果就是0和2。如果我们在f2函数里也想打印全局变量怎么办?当然,在int a = 2之前就printf一下即可,不过这里用的不是这个方法。我们要用到一个域作用限定符。

void f2()
{
	int a = 2;
	printf("%d\n", a);
	printf("%d\n", ::a);
}

::这个域作用限定符前面有空格就表示在全局范围寻找,如果全局没有相应的变量名,就会出错。

C++学习记录——일 C++入门(1)_第2张图片

2、了解命名空间

现在开始多个文件使用,创建两个.h文件。写入一些代码,让这些代码有重复命名的部分

struct Node
{
	struct Node* next;
	int val;
};

struct Queue
{
	struct Node* head;
	struct Node* tail;
};
struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
};

yy.h和dd.h文件。这时候把这两个都引进来。
在这里插入图片描述

这里会出现重定义的错误。在C语言里,这样只能改名;但是C++可以用命名空间解决。在刚才的域中,一个域里不能出现两个同名变量,f2函数里正常的输出a,就输出局部变量的值,而输出全局变量的值需要加上域作用限定符。这里的命名空间相当于加一个域,把文件所有的代码都套上一个空间,那么两个文件代码虽然相同,但在不同空间里,也就互不影响。所以能够看出命名空间影响使用。不过命名空间不影响生命周期,全局变量还是全局变量,局部变量还是局部变量。

namespace dd
{
	struct Node
	{
		struct Node* next;
		struct Node* prev;
		int val;
	};
}

像这样,包进命名空间里再引用就不会出问题了。但是问题还没结束。现在在cpp文件里建立一个变量,用struct Node类型,这时候程序还是出错了。虽然有了命名空间,但程序在搜索struct Node的声明时不会在命名空间里面找,而是在全局域和局部域里面找,所以还是报错了。如何解决?前面我们提到了域作用限定符,::前面为空格时是指定全局域里面找,现在加上命名空间,struct dd :: Node,struct yy :: Node,两个Node变量就不冲突了。至于不放在struct前面,是因为同名冲突的是Node,而不是struct,比如我还可以声明struct snode,那么查找的也就是snode这个标识。

#include "dd.h"
#include "yy.h"

int main()
{
	struct yy::Node m;
	struct dd::Node n;
	return 0;
}

以及想要用同样名字的变量,也要放进命名空间里才可。在yy空间里再声明一个num的整型,那么外面就要用yy::num才能操作它。

如果命名空间也重名了怎么办?对于重名的空间,程序会把他们认为是一个空间,所以这就和没加命名空间一样。可以改里面的名字,也可以改命名空间的名,也可以嵌套一个空间。

C++学习记录——일 C++入门(1)_第3张图片

那么用的时候就是yy::xx::num。

3、命名空间的使用

前面的代码都已经放入了命名空间,现在再往yy里添加一些函数,比如push,init等,然后在外部调用。如果直接调用,肯定是不行的,程序会报错,这应该怎么解决?
现在对于这个问题,我们有三个办法解决。

指定命名空间访问

编译器自然不会让你一个个都加上

#include "yy.h"

int main()
{
	struct yy::Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
}

创建变量时带上命名空间即可,不过用不同的变量还是要每个都用符号,稍显麻烦,但也实用。

using全局展开

默认情况下,程序不会直接命名空间查找。但是我们可以把命名空间给展开,就像引用头文件一样。

#include "yy.h"
using namespace yy;

int main()
{
	struct Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
}

不加上using这一行代码,程序就不会去命名空间里找。加上就会找一遍yy.h里非命名空间的代码,再找一遍命名空间。

C++为了区分用户写的和标准库自带的,把标准库也放到了一个命名空间,就是std。
比如cout就是std里的。实际写代码时,我们可以全局展开,也可以带上std::。不过一般写项目时很少全部展开,因为全部展开后相当于没有命名空间了,所有标准库的代码都包含了进来,那么用户写代码时就会局限很多。所以指定访问也就有用处了。

部分展开

这里还有第三个办法。部分展开的意思是用什么展开什么。我要用cout,endl,那就展开这两个

#include 
using std::cout;
using std::endl;

四、C++输入输出

之前已经写过,<<是流插入,endl等价于换行符

#include 
using namespace std;

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

现在我们可以这样理解,cout相当于一个控制台,hello world字符串流向cout,endl流向字符串。另外还有流提取,比如cin。从后面的变量中提取出来给到cin,cin >> n。

C++的流插入流提取有个很好的特性——自动识别类型

	//自动识别类型
	int n = 0;
	cin >> n;
	double* a = (double*)malloc(sizeof(double) * n);
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << endl;
	}
	return 0;

能够看到a和n类型不一样,但cin和cout都可以帮你解决问题。默认情况下保留5位小数,如果想要控制精度可以使用C语言的方法,比如"%.4f"等等,而C++方面的则要复杂。

五、缺省参数

C语言并没有缺省参数,实际上这个东西很有用。先看代码。

void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func(1);
	Func();
	return 0;
}

实际输出结果是1和0。缺省参数就是如果不传参,就按照自带的数值运行,传参就用传的参数来运行。

多个参数且都是缺省参数时就是全缺省参数,可以全部传,也可以只传一个,但是不能隔开传,比如传了第1和第3个参数,必须连续传参。

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

int main()
{
	Func(1, 2, 3);
	Func(1, 2);
	Func(1);
	Func();
	return 0;
}

C++学习记录——일 C++入门(1)_第4张图片

除此以外,还有半缺省参数。其实就是一部分缺省,但必须从右往左连续缺省。

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

int main()
{
	Func(1, 2, 3);
	Func(1, 2);
	Func(1);
	return 0;
}

C++学习记录——일 C++入门(1)_第5张图片

关于缺省参数的好处,我们可以联想一下之前的数据结构。比如栈,初始化malloc开空间时,我们总得先开一块,然后不断calloc,对于具体的数值可能操作者不知道是多少,所以代码比较麻烦。用上缺省参数的话,我们就可以往函数传数值,不传也没关系,有固定的数值。

struct Stack
{
	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* ps, int n = 100)
{
	ps->a = (int*)malloc(sizeof(int) * n);
	ps->top = 0;
	ps->capacity = 0;
}

int main()
{
	Stack st;
	StackInit(&st);
	return 0;
}

缺省参数在使用的时候,头文件和源文件不能同时有缺省参数,否则程序会不清楚该用哪一个而报错。缺省参数在头文件声明时加上即可,加在源文件也会导致函数参数出错。

六、函数重载

前面说到的命名空间,可以解决同名变量。C语言中,同名函数也有同样的问题,只能改名,而C++有函数重载。

int Add(int left, int right)
{
	cout << left + right << endl;
	return 0;
}

double Add(double left, double right)
{
	cout << left + right << endl;
	return 0;
}

int main()
{
	Add(1, 2);
	Add(1.1, 2.2);
	return 0;
}

这段代码放在C语言里一定出错,但是C++却没有问题。对于这两个函数C++都能够识别出来并给出准确结果。函数重载一般的情况是两个函数参数类型,个数,类型顺序至少有一个不同。类型顺序不同指的是像下图这样:

C++学习记录——일 C++入门(1)_第6张图片

类型和个数相同,但是顺序不同也要重载。函数重载发生也要函数都在一个空间里,不是一个命名空间也就不会又重载。

函数重载貌似就这样简单地写完了,但是实际上有个关键性问题没有解决。为什么两个同名函数,C++真的能识别出来,C语言就不行?

编译器要先辨别出两个不同的函数,这样比起C语言是不是要慢一点?函数重载并不在运行时识别的,而是在编译阶段识别的。编译的时候C++对函数进行了处理,这个处理就是修饰了函数名。我们先打一个断点,转到反汇编去看。

C++学习记录——일 C++入门(1)_第7张图片

C++学习记录——일 C++入门(1)_第8张图片

可以看到两个函数下面都有一个call,后面括号里的地址也不一样。看一下调用两个函数那两行时程序在干嘛

C++学习记录——일 C++入门(1)_第9张图片

C++学习记录——일 C++入门(1)_第10张图片

所以可以看到两个函数在编译阶段就已经不一样了, 变成了一行行指令。虽然如此,还是有疑问。程序走到了调用函数的那一行后,程序是怎么知道就要去调用这个函数?

这里就牵扯到C++的修饰。C语言是按照函数名来查找,而C++是修饰过后按照修饰的指令来找。每个平台的修饰都不同,但规则都一样。所以每个函数也就变成了一些唯一的指令,C++通过这些指令找到每个函数。在C语言中如果用同名函数,就会出现这样:

在这里插入图片描述

在C++里,你可以看到这样

C++学习记录——일 C++入门(1)_第11张图片

这时候报的外部符号就是修饰过的函数名了。

关于函数重载这里也并不是细致的说明,用linux的也可以去linux看看它如何修饰的。

七、引用

1、引用符号

用C语言的时候,指针要分为一级,二级,三级指针等等,为了改变实参的数据,我们总得想到传地址,用相应的指针接收,这个指针就麻烦,有可能就出错,所以C++就出了引用这个概念。C++的引用和Python的赋值相似。创建 i 这个变量并赋值后,我们可以把i当做这个值的标签,而引用则是又给它加了一个标签,所以我们可以给他加更多标签,也可以嵌套标签。引用后,新变量名的变化,旧变量名也会相应地变化了。

#include 
using namespace std;

int main()
{
	int a = 0;
	int b = a;
	int& c = a;

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	return 0}

C++学习记录——일 C++入门(1)_第12张图片

不过如上图那样,再来一个int& z = k,那么z++后k和i的值都加一了。另外,赋值的本质是什么?程序又创建了y这个变量,然后把值复制过去。所以j和k两个都++,不会影响到i。

2、引用的部分使用场景

void Swap(int* x, int* y)
{
	;
}

int main()
{
	int a = 0;
	int b = 1;
	Swap(&a, &b);
	return 0;
}

之前C语言想要改变实参的数值,就需要传地址。现在到了C++,我们用引用来实现。

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 1;
	Swap(a, b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

给a和b各自带上一个标签,也就是引用,那么x和y的改变就会影响到a和b了。
指针也一样可以引用,相当于两个指针指向同一块空间,不过名字不一样而已。

再看一下之前写链表的一个场景。

typedef struct Node
{
	struct Node* next;
	int val;
}Node;

void PushBack(Node* phead, int x)
{
	Node* newnode = (Node*)malloc(sizeof(Node) * 10);
	if (phead == NULL)
	{
		phead = newnode;
	}
}

int main()
{
	Node* plist = NULL;
	PushBack(plist, 1);
	PushBack(plist, 2);
	PushBack(plist, 3);
}

像这样的情况,我们就需要传二级指针才能改变链表的数据。但是在C++里,引用即可。

void PushBack(Node*& phead, int x)
{
	Node* newnode = (Node*)malloc(sizeof(Node) * 10);
	if (phead == NULL)
	{
		phead = newnode;
	}
}

下一篇再继续写。

结束。

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