一文掌握C++基本语法

今天起终于能正式开始将自己学习C++的旅程记录在CSDN啦!

 

目录

1. C++关键字

2. 命名空间

2.1 命名空间定义

2.2 命名空间使用

3. C++输入&输出

4. 缺省参数

4.1 缺省参数概念

4.2 缺省参数分类

5. 函数重载

5.1 函数重载概念

5.2 名字修饰

5.3 extern “C”

6. 引用

6.1 引用概念

6.2 引用特性

6.3 常引用

6.4 使用场景

6.5 传值、传引用效率比较

6.6 引用和指针的区别

7. 内联函数

7.1 概念

7.1 特性

8. auto关键字(C++11)

8.1 auto简介

8.2 auto的使用细则

8.3 auto不能推导的场景

9. 基于范围的for循环(C++11)

9.1 范围for的语法

9.2 范围for的使用条件

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

10.1 C++98中的指针空值


1. C++关键字

 

2. 命名空间

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

2.1 命名空间定义

使用namespace关键字,后面跟命名空间的名字,然后用{}将成员括起来即可,和C语言的结构体类似

namespace N1 // N1为命名空间的名称
{
	// 命名空间中的内容,既可以定义变量,也可以定义函数
	int a;
	int Add(int left, int right)
	{
		return left + right;
	}
}

存在多个相同的命名空间的时候,编译器编译的时候会把他们合并,如下面

namespace N1 
{
	int a;
	int Add(int left, int right)
	{
		return left + right;
	}
}
namespace N1
{
	int Mul(int left, int right)
	{
		return left * right;
	}
}

命名空间可以嵌套

namespace N2
{
	int a;
	int b;
	int Add(int left, int right)
	{
		return left + right;
	}
	namespace N3
	{
		int c;
		int d;
		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}

PS:命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中,但是成员的生命周期没有改变,仍然是全局的


2.2 命名空间使用

命名空间有3种使用方式

以下均使用该命名空间

namespace N
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}

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

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

这种方法每次使用的时候都要进行::限定,比较麻烦

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

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

这样就可以不用::限定命名空间,直接使用b

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

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

可以使用所有N中内容,但是需要注意的是这样可能会造成名称冲突


 

3. C++输入&输出

#include<iostream>
using namespace std;
int main()
{
	cout << "Hello world!!!" << endl;
	return 0;
}

PS:

1. 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
2. 使用C++输入输出更方便,不需增加数据格式控制

#include <iostream>
using namespace std;
int main()
{
	int a;
	double b;
	char c;
	cin >> a;
	cin >> b >> c;
	cout << a << endl;
	cout << b << " " << c << endl;
	return 0;
}

4. 缺省参数

缺省参数就像女神的备胎,当女神有男朋友的时候,备胎被冷落在一旁,当女神分手后,备胎才可以派上用场。

4.1 缺省参数概念

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

void TestFunc(int a = 0)
{
	cout << a << endl;
}
int main()
{
	TestFunc(); // 没有传参时,使用参数的默认值
	TestFunc(10); // 传参时,使用指定的实参
}

4.2 缺省参数分类

全缺省参数

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

半缺省参数

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

四个注意点:

1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现,且应只出现于声明
 

//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
个缺省值

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


 

5. 函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。比如:有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前
者是“谁也赢不了!”,后者是“谁也赢不了!”

5.1 函数重载概念

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

下面都是函数重载:

int Add(int left, int right)
{
	return left + right;
}
int Add(short left, short right)
{
	return left + right;
}
int Add(int left, short right)
{
	return left + right;
}
int Add(short left, int right)
{
	return left + right;
}
int Add(short left, short right,short three)
{
	return left + right;
}

下面这两个不是函数重载!

short Add(short left, short right)
{
	return left + right;
}
int Add(short left, short right)
{
	return left + right;
}

5.2 名字修饰

那么为什么C++支持函数重载而C语言不支持呢?

一个程序要运行起来要经历以下几步:假设有以下文件

f.h f.cpp test.cpp

1.预处理——头文件展开,宏替换,条件编译,去掉注释

生成f.i test.i

2.编译——检查语法,生成汇编代码

生成f.s test.s

3.汇编——把汇编代码转换成二进制的机器码(让CPU能看懂)

生成f.o test.o

4.链接——找调用函数的定义地址,链接对应上,合并到一起

生成a.out可执行程序

在链接阶段,编译器看到test.o调用了哪些函数,就会去f.o的符号表中找对应函数的地址,然后链接在一起,那么要怎么去找呢?C++和C语言采用了不同的名字修饰,就会用不同的名字去找

在C++:

我们发现名字由_Z+函数名长度+函数名首字母+函数按顺序参数首字母,因此支持函数重载(名字不一样,找的对象就不一样)

C:

这里我们发现C语言则是直接用函数名作为地址查找对象,回想我们的函数指针,是不是函数指针和函数名都可以调用函数呢。因此如果C语言中出现函数重载,那么调用的时候就会查找到两个不同地址,C语言不知道要链接哪一个,因此出错。 

5.3 extern “C”

我们知道C语言可以调用C语言的静态库和动态库,C++可以调用C++的静态库和动态库,那么C++能不能调用C的库?C能不能调用C++的库?

答案是可以的。

我们先来看C++调用C的库,这里我们用C语言实现的栈的代码生成一个静态库

再新开一个项目,调用这个库,这里对新项目的属性进行更改

 然后来看一下现在能否调用

 

 这是不是意味着C++不能调用C的库

别急,这时候extern C就登场了

 我们发现加了这个关键字后就链接成功了。这个关键字的意思是告诉编译器将该部分按照C语言的规则来进行编译,我们调用C库,当然就要按C的规则来啦。

再来看一下C语言能否调用C++的库

我们把后缀名更改一下

 


(这里是c语言了,因此只能用typedef过的别名,不能再用一个Stack,因为C语言不支持) 

好像不行,我们刚刚是在C++的部分进行修改,这次我们也在C++的部分进行修改,我们来到test_01,对头文件部分的声明进行如下修改

这样是不是就顺利链接成功了,这里的预编译进行了判断,如果文件是C++的,那么就用C的规则进行编译,也可以这么写


 

6. 引用

6.1 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

类型& 引用变量名(对象名) = 引用实体;

int a = 10;
int& ra = a;//<====定义引用类型

PS:引用类型必须和引用实体是同种类型的

6.2 引用特性

1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

6.3 常引用

void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}

这里我们取别名的规则是:
对原引用变量,权限只能缩小,不能放大。这里权限指的是读写权限

因此对const常量只能进行读,也就只能用const引用。

在下面将double赋给int别名的过程中,编译器会产生“临时变量”,将double d中整数部分赋给临时变量, 这个临时变量再将这个整数赋给另一个int类型的临时变量,最后int把临时变量赋给rd,也就是说rd引用的其实是这个int临时变量,而临时变量具有常属性,因此要用const引用


6.4 使用场景

1.作函数参数

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

在学习C语言的时候我们如果要交换两个数,那么就需要传址调用函数,而现在可以直接用引用进行修改,这就是引用做参数的好处,输出型参数
另外,如果是传值调用的话,我们知道形参是实参的一个临时拷贝,而传引用的时候并没有拷贝这一步,这就提高了效率

2.作函数返回值

int& Count()
{
	static int n = 0;
		n++;
	return n;
}
int main()
{
	cout<<Count()<<endl;
	cout << Count() << endl;
	cout << Count() << endl;
}

 结果是显然的,我们返回的是静态变量n的别名,而出了函数作用域n并没有销毁,那么来看下面这个代码

int& Count()
{
	//static int n = 0;
	int n = 0;
		n++;
	return n;
}
int main()
{
	int& ret = Count();
	cout<<ret<<endl;
	cout << ret<< endl;
	cout << ret<< endl;
}

 n和ret地址一样

这里n不再是静态变量了,出了函数作用域后空间返还,而我们的ret是n的别名,这是不是就相当于野指针。因此只有第一次调用函数后ret中的值是1,调用一次函数(函数重载<<)后n的空间被制成随机数,因此剩下两次都是随机数,因此我们用引用作返回值的时候返回的应该是出了函数定义域还存在的变量的引用,否则就应该按值返回,这里和指针是一个道理。

明白了这些下面这个程序的结果就应该很明确了,应该是7

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

 道理是一样的。

另外引用作返回值也会提高效率


6.5 传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
这里用程序来比较一下

#include <time.h>
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;
}

不是很明显的原因是我们计算机的速度实在太快,但是还是有效率差异的

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;
}

指针和引用的效率是一样的,因为引用的底层和指针的底层是一样的!

6.6 引用和指针的区别

从语法概念上:引用是别名,没有额外开空间,而指针存储变量的地址,开辟了4/8字节的空间

从底层实现的角度:引用实际上是有空间的,因为引用是按指针的方式来实现的

来看一下指针和引用的汇编

汇编指令完全一样,也印证了底层是一样的

那引用和指针的不同点是什么呢?

1. 引用在定义时必须初始化,指针没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 没有NULL引用,但有NULL指针
4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全

 

7. 内联函数

7.1 概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
在学习C语言的时候,我们遇到函数体短小且频繁调用的函数的时候,采用的优化方式是使用宏进行替换,但是宏太繁琐啦,难以看懂,因此c++就产生了内联函数。

那么内联函数是如何进行优化的呢?来看代码。

int Add(int a, int b)
{
	return a+b;
}
int main()
{
	Add(1,2);
	return 0;
}

当我们不用inline定义函数的时候,函数是怎么调用的呢?

进行函数压栈,然后用call指令调用。那么用了inline后呢

我们发现在汇编代码中直接对函数调用的代码进行了替换,没有压栈和call调用指令。 


7.1 特性

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
例如如果在.h和.cpp文件中分别声明和定义内联函数,那么在链接的时候就会出现错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

那么内联函数相比宏有哪些优点?

1.内联函数在debug版本下支持调试,而宏不支持

2.内联函数就是普通函数的写法,解决了宏晦涩难懂的问题

 

8. auto关键字(C++11)

8.1 auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}

PS:

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

8.2 auto的使用细则

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

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	*a = 20;
	*b = 30;
	c = 40;
	return 0;
}

2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
	auto a = 1, b = 2;
	auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

8.3 auto不能推导的场景

1. auto不能作为函数的参数

 2. auto不能直接用来声明数组

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4. auto在实际中最常见的优势用法就是C++11提供的新式for循环,还有lambda表达式等进行配合使用。 

int main()
{
	int arr[4] = { 1,2,7,9 };
	for (auto e : arr)
		cout << e << endl;
	return 0;
}

 

9. 基于范围的for循环(C++11)

9.1 范围for的语法

for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
 

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " ";
}
int main()
{
	TestFor();
	return 0;
}

PS: 

范围for循环与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

9.2 范围for的使用条件

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

2. 迭代的对象要实现++和==的操作。
 

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

10.1 C++98中的指针空值

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

在传统头文件中有如上定义,我们发现在C++中Null被宏替换为0,而如果我们要对一个指针赋空值,仍采用NULL的话有些不妥,因为0毕竟是一个数字,而不是指针类型,因此C++11引入了nullptr,其实也就相当于(void*)0。

OK!

 

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
掌握Ubuntu,您可以采取以下步骤: 1. 下载Ubuntu镜像:您可以从官方网站下载Ubuntu的最新版本。根据您的硬件架构选择合适的版本。 2. 创建启动U盘:使用免费的win32diskimager工具将下载的Ubuntu镜像烧录到U盘上。这样您就可以通过U盘启动安装Ubuntu。将U盘插入计算机,并按照工具的指示进行操作。 3. 安装Ubuntu:将启动U盘插入需要安装Ubuntu的计算机,重新启动计算机。在启动时,选择从U盘启动。按照屏幕上的提示进行Ubuntu的安装过程。您可以选择安装到硬盘上,或者选择试用Ubuntu而不进行实际安装。 4. 更新系统:在安装完成后,建议您更新系统以获取最新的补丁和软件包。打开终端并运行以下命令:sudo apt update && sudo apt upgrade 5. 安装必要的软件:根据您的需求,可以安装各种软件。例如,如果您需要进行深度学习开发,可以安装CUDA和PaddlePaddle。 6. 学习命令行操作:Ubuntu是一个基于Linux的操作系统,使用命令行是非常常见的。您可以学习一些基本的Linux命令,例如文件和目录操作、软件包管理等。 7. 探索图形界面:Ubuntu提供了直观的图形界面,您可以通过点击图标和菜单来执行各种操作。尝试打开不同的应用程序,了解它们的功能和用法。 通过以上步骤,您将能够快速上手并掌握Ubuntu操作系统。记得多练习和实践,以加深对Ubuntu的理解和熟练度。祝您成功!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

printf("雷猴");

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

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

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

打赏作者

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

抵扣说明:

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

余额充值