初见Cpp(C++)

        从本篇开始,往后将开始更新C++有关的文章,本篇作为对C++的一个铺垫。将会较为详细的讲解一些有关C++的基本知识,便于读者从C语言阶段晋升到C++阶段。以下是对C++的一些介绍:

        C++是在C的基础上,容纳进去了面对对象的编程思想,并且增加了许多有用的库,以及编程范式等等。熟悉C语言之后,对C++学习有一定的帮助。所以本篇的主要目标为:

        1.补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的;

        2.为后序类和对象的文章打下基础。

以下为本篇的目录:

目录

1. C++关键字 

 2. 命名空间

2.1 命名空间定义

2.3 命名空间的使用

3. Cpp中的输出与输入 

4. 缺省参数

4.1 缺省参数概念

4.2 缺省参数的分类

4.3 缺省参数的注意事项

5. 函数重载

5.1 函数重载的概念

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

6. 引用

6.1 引用概念

6.2 引用的特性

6.3 常引用

6.4 使用场景

6.5 传值、返回值与传引用、返回引用效率比较

6.6 指针与引用的区别

7. 内联函数

7.1 内联函数的概念

7.2 内联函数的特性

8. auto关键字---C++11 

8.1 问题引出

8.2 auto简介与使用细节

9. 基于范围的 for 循环 

9.1 范围 for 的语法

10. 指针空值 nullptr (C++11)

1. C++关键字 

        在命名对象时,我们不能将对象名命名成关键字,C++中的关键字如下:        

        C++总计63个关键字,C语言32个只有32个关键字。

 2. 命名空间

        在C/C++中,变量、函数和后面将要学习的类都是会大量存在的,对于这些变量、函数和类的名称将都存在于全局作用域之中,这可能会导致很多同名的冲突。使用命名空间的目的就是对标识符的名称进行本地化,避免命名冲突或名字污染,namespace关键字的出现就是针对这些问题的。如下图:

        当我们定义一个全局变量rand时,这时候报错:出现重定义。是因为,在 stdlib.h 头文件中存在 rand() 函数,所以存在命名冲突。

2.1 命名空间定义

        定义命名空间,需要使用 namespace 关键字,后面跟命名空间的名字,然后接一对 {} 即可,在 {} 中,即为命名空间的成员,如下:

// JZhong为命名空间的名字,一般开发中使用项目名字做命名空间
namespace JZhong {
	int rand = 10;
	int Add(int a, int b) {
		return a + b;
	}
	struct Node{
		int data;
		struct Node* next;
	};
}

// 命名空间可以嵌套
namespace JQZhang {
	int rand = 15;
	int Sub(int a, int b) {
		return a - b;
	}
	// 嵌套命名空间
	namespace GGJ {
		int a;
		int b;
		int Add(int left,int right){
			return left + right;
		}
	}
}

// 通常在一个工程中允许存在多个相同名称的命名空间,编译器最后会将其合成同一个命名空间
namespace JQZhang {
	int Mul(int a, int b) {
		return a * b;
	}
}

        如上所示,对于命名空间的定义,有着三个特性:

        1. 一般以开发项目名字作为命名空间;

        2. 命名空间中可以在嵌套一个命名空间;

        3. 相同名称的命名空间最后将会合并。

2.3 命名空间的使用

        对于命名空间的使用存在三种方式:

        1. 加命名空间名称及作用域限定符

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

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

        先介绍一个符号:“ :: ”,这两个冒号的符号被称为作用域限定符,对于这个限定符使用方式如下:

int main() {
	//第一种方法,命名空间名称+作用域限定符
	printf("%d\n", JZhong::rand);
	printf("%d\n", JQZhang::rand);
	printf("%d\n", JQZhang::GGJ::Add(3, 5));
	return 0;
}
//第二种方法,使用using将命名空间中的某个成员引入
using JQZhang::Sub;
using JZhong::Node;
using JZhong::Add;
int main() {
	Node node;
	node.data = 10;
	node.next = nullptr;
	printf("%d\n", Add(6, 8));
	printf("%d\n", Sub(6, 8));
	return 0;
}
// 第三种方法,使用using namespace 命名空间名称 引入
using namespace JQZhang;
int main() {
	GGJ::a = 10, GGJ::b = 20;
	printf("%d\n", Mul(GGJ::a, GGJ::b));
	printf("%d\n", Sub(6, 8));
	return 0;
}

3. Cpp中的输出与输入 

        对于Cpp中的输入与输出,我们一般采用的函数为:标准输出流对象 cout、标准输出流对象 cin。对于这两个的使用,必须包含 <iostream> 头文件以及按照命名空间使用方法使用std。(注:Cpp同时兼容 printf 和 scanf函数)。

#include <iostream>

// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;

int main() {
    // endl函数代表'\n'
	cout << "hello world!\n" << endl;
	int i = 0;
    // 对i进行输入
	cin >> i;
	std::cout << "hello world" << i << std::endl;
	return 0;
}

        对输入输出的说明:

        1. 使用cout标准输出对象(控制台)cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
        2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream >头文件中。
        3. “<<” 流插入运算符,“>>” 流提取运算符。

        4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。

        ps:关于cout和cin还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等等。因为C++兼容C语言的用法,对于控制浮点数输出精度可使用 printf 函数。
        std 命名空间的使用惯例:

        1. 在平时的使用中,可以将 std 直接展开,这样更方便。

        2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。所以不太建议直接在项目中展开 std 命名空间,可以使用以上第二种方法,展开一些常用的函数。

4. 缺省参数

4.1 缺省参数概念

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

        当不传参时,函数就调用原指定的参数;传参之后则使用传过去的参数。

4.2 缺省参数的分类

        缺省参数一种存在两种缺省参数,其中分别为:全缺省参数、半缺省参数。

        全缺省参数:

void Func(int a = 0, int b = 1, int c = 2) {
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

        半缺省参数:

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

4.3 缺省参数的注意事项

        对于缺省函数的使用,存在着这样的一些注意事项:

        1. 半缺省参数必须从右向左依次给出,不能存在间隔。如下图:

        2. 缺省参数不能在函数声明和定义中同时出现,如下图所示:

        如上图所示,这样的定义会出现重定义的报错,我们需要在函数定义中将缺省参数都去掉。

        3. 缺省值必须是常量或者全局变量;

        4. C语言不支持缺省参数的使用。

5. 函数重载

5.1 函数重载的概念

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

        注:只能是形参列表不同,若列表相同,但返回值不同,则会报错。   如下图:

        对于重载函数一共有三种模式:

        1. 参数类型不同:

// 1.参数类型不同
int Add(int left, int right) {
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

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

        2. 参数个数不同:

// 2.参数个数不同
void Func() {
	cout << "Func()" << endl;
}

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

        3. 参数类型的顺序不同:        

// 3.参数顺序不同
void F(int a, char b) {
	cout << "void F(int a, char b)" << endl;
}

void F(char a, int b) {
	cout << "void F(char a, int b)" << endl;
}

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

        为什么在C++中支持函数重载,而C语言中不会支持函数重载呢?

        主要是在链接阶段出现了问题。在C/C++中,一个程序要运行起来,需要经过以下几个阶段:预处理、编译、汇编、链接。若想详细了解可看这篇文章:编译与链接(C/C++)-CSDN博客,具体的过程如下图(无预处理过程):

        在链接阶段时,对于函数的调用,我们需要使用函数名去找到函数的地址,然后将其链接在一起。但是在C语言中,存在两个同名的函数,C语言无法区分具体要调用哪个函数,所以会报错。但是在C++中,对于函数有着不一样的函数修饰规则,所以在C++中每一个函数的函数修饰都不一样,所以可以找到对应的函数。如下图:

        对于不同的函数修饰方式不一样,所以可以找到对应函数。

6. 引用

6.1 引用概念

        引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共同使用同一块内存空间。(可以理解为周树人和鲁迅、林冲和豹子头的关系)

        使用方法:类型& 引用变量名(对象名) = 引用实体。

6.2 引用的特性

        1. 引用在定义时必须初始化;

        2. 一个变量可以引用多个引用;

        3. 引用一旦引用某个实体,就不能在引用其他实体(引用不可以改变指向)

int main() {
	int a = 10;
	int c = 20;
	//int& rc; 为初始化,会报错
	int& ra = a;
	int& rra = ra;
	printf("%p\n", &a);
	printf("%p\n", &ra);
	printf("%p\n", &rra);
	return 0;
}

6.3 常引用

        对于引用的类型一定要与被引用的变量一样,如下:

int main() {
	const int a = 10;
	//int& ra = a;   //错误的方式
	const int& ra = a;

	int b = 10;
	//const int& rb = b;  //错误的方式
	int& rb = b;

	double d = 12.34;
	//int& rd = d; 错误的方式
	return 0;
}

6.4 使用场景

         第一种:做参数,使用引用做参数可以解决使用指针的大多数情况,如下:

int Swap(int& left, int& right) {
	int tmp = left;
	left = right;
	right = tmp;
}

        对于这样的函数,我们就不需要对传入的参数进行取地址了。

        第二种:做返回值,对于使用引用做函数返回值,我们需要注意的是返回的值不能是局部变量,因为局部变量一旦出函数,这个局部变量就变成了随机值,返回的类型可以是:静态变量,动态函数开辟的变量、全局变量等等。

int& Count() {
	static int n = 10;
	n++;
	// ...
	return n;
}

6.5 传值、返回值与传引用、返回引用效率比较

        以值作为参数或者返回类型,在传参和返回阶段,函数不会直接传递实参或将本身变量直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回类型效率是十分低下的,特别是当参数或者返回值特别大时。

        反而,对于传引用和返回引用则不同,引用是对变量的一个别名,对于引用的传递和返回,其实是直接对其传参,或者返回,其中并没有拷贝。测试代码如下:

#include <time.h>

struct A {
	int a[10000];
};

void TestFunc1(A a) {};
void TestFunc2(A& a) {};
A a;
A Testfunc1() {return a; };
A& Testfunc2() {; return a; };

int main() {
	A a;
	// 分别调用以上函数10000次,看计算时间
	// 以值作为函数参数
	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();
	// 以值作为返回值
	size_t begin3 = clock();
	for (size_t i = 0; i < 10000; ++i)
		Testfunc1();
	size_t end3 = clock();
	// 以引用作为返回值
	size_t begin4 = clock();
	for (size_t i = 0; i < 10000; ++i)
		Testfunc2();
	size_t end4 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "传参:" << endl;
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
	// 分别计算两个函数运行结束后的时间
	cout << "返回值:" << endl;
	cout << "Testfunc1()-time:" << end3 - begin3 << endl;
	cout << "Testfunc2()-time:" << end4 - begin4 << endl;
	return 0;
}

        计算结果如下图:

6.6 指针与引用的区别

        对于传地址和传引用有着很多的相似处,但是其中还是存在一些区别,如下:

        1. 引用必须将其初始化,而指针可以不用初始化;

        2. 引用不可以改变指向,但是指针可以改变指向,这个区别是指针和引用在使用上的很大区别,比如当我们遍历链表的时候,就需要使用指针而不能使用引用;

        3. 引用相对而言会更加的安全,引用很少存在野引用,而对于指针使用不当则容易出现野指针;

        4. sizeof 计算指针,一般情况计算的是指针所占空间的大小、对于引用就是计算的是引用变量的大小。对于 ++ 的使用、解引用访问也存在区别;

        5. 存在多级指针,但不存在多级引用;

        6. 访问方式不同,指针需要显式解引用、而引用则由编译器自己处理。

        

        对于引用——将变量取一个别名,指针——指向存储变量的地址,这两种类型的变量语法概念上不同,但是对于底层实现:引用是使用指针实现的,如下图:

        如上图的汇编代码,引用部分和指针部分的汇编代码一致,说明引用的底层实现是使用指针实现的。

7. 内联函数

7.1 内联函数的概念

        以 inline 修饰的函数被叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数可以提升程序运行的效率。

        提升效率的原因:当我们需要常用到一些代码量较小的函数时,每一次建立的都需要建立新的栈帧,开辟的空间和花的时间较多,但是使用内联函数时,就可以在调用函数时,将其替换,节省时间和空间。如下图:

7.2 内联函数的特性

        1. inline 函数是一种时间换空间的做法。通常只将代码量较小的函数使用 inline 修饰,若遇到代码量较大的代码,不适合使用内联函数将其展开,若代码量太大的代码多次展开会导致代码膨胀,使目标文件变大。

        2. inline 对于编译器只是一个建议,不同的编译器关于 inline 实现的机制不同。有的编译器对于代码量较大并且使用 inline 修饰的代码,并不会将其展开。

        3. inline 函数不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址,链接时就找不到该函数。 

       

        使用内联函数的特性,其实就相当于C语言中的宏,在预编译阶段直接将函数展开,但是更建议使用内联函数。

        宏的优点虽然为:增强代码的复用性、提高性能。但是也存在一些较为突出的缺点不方便调试,导致代码可读性、可维护性差,还没有类型安全的检查

        而使用内联函数,就可以更好的抵消掉宏的副作用。同时,我们还可以使用 const enum 进行常量定义

8. auto关键字---C++11 

8.1 问题引出

        随着程序越来越复杂,程序中用到的类型也越来越复杂,如:

        1. 类型难于拼写;

        2. 含义不明确导致容易出错。如下:

#include <string>
#include <map>

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<std::string, std::string>::iterator 只是一个类型,但这个类型太长了,特别容易写错。但是我们也可以使用 typedef 对类型进行改名,不过就算改名也只能改一个,还存在其他冗长的类型,还需要一一进行改名。但是使用 auto 就会很好的解决这个问题,如下:

8.2 auto简介与使用细节

        在C++11版本中,auto 作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。auto 可以自动的推测变量的类型,从而将变量的类型确立。

        如上图,对于定义的变量,auto 可以自动推断其变量类型。

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


        auto 使用细节:

        1. auto 与指针和引用结合使用时:用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须加 &

        2. 在同一行定义多个变量时:在同一行定义多个变量时,定义的变量必须是相同类型的,否则将会报错:

        3. auto 不能用来声明数组以及作为函数的参数:

9. 基于范围的 for 循环 

9.1 范围 for 的语法

        在C++98的标准中,当我们要遍历一个数组时,需要使用如下方式:

        但是在C++11标准中引入了基于范围的 for 循环,for 循环后的括号由冒号“ : ”将其分为两部分:第一部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。如下:

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

        当数组传入到一个函数中时,就不可以在使用范围 for 循环了,因为函数传入的参数仅仅只为数组的首地址,不含有数组的范围。

        若要在使用范围 for 循环时,更改遍历的值,我们可以使用引用来进行操作,如下:

10. 指针空值 nullptr (C++11)

        在传统的C语言的 <stddef.h> 中,我们可以看见如下的代码:

#ifndef NULL
	#ifdef __cplusplus
		#define NULL 0
	#else
		#define NULL ((void *)0)
	#endif
#endif

        可以看到,在C语言中,NULL被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,如:

        在起初的程序本意是想通过func(NULL)调用指针版本的func(int*)函数,但是由于NULL被定义为0,因此与程序的初衷相悖。所以这时候,我们就可以使用 nullptr 关键字,代表空指针。

  • 63
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值