C++入门(基础语法)

文章目录

  • 写在前面
  • 1 C++关键字
  • 2 命名空间
    • 2.1 如何定义一个命名空间?
    • 2.2 命名空间的使用
  • 3 C++的输入&输出函数
  • 4 缺省参数
    • 4.1 缺省参数的定义
    • 4.2 缺省参数的分类
      • 4.2.1 全缺省参数
      • 4.2.2 半缺省参数
      • 4.3 注意事项
  • 5 函数重载
  • 6引用
    • 6.1引用的概念
    • 6.2 引用的特性
      • 6.2.1 引用在定义的时候必须初始化
      • 6.2.2 一个变量可以有多个引用
      • 6.2.3 引用一旦引用一个实体,再不能引用其他实体
    • 6.3 常引用
    • 6.4 使用场景
      • 6.4.1 作为函数参数
      • 6.4.2 作为函数返回值
    • 6.5 引用和指针的区别
  • 7. 内联函数
    • 7.1 内联函数特点
    • 7.2 内联函数和宏的对比

写在前面

C++在C的基础上引入了面向对象编程思想,并丰富了许多实用的库和编程范式。对于已经熟悉C语言的学习者,掌握C++可以更好地理解如何弥补C语言在某些方面的不足,并对C语言设计中不够合理的地方进行优化,涉及到作用域、IO、函数、指针、宏等方面的改进。
本篇文章主要介绍了:C++是如何填补C语言语法的一些不足,以及介绍了C++如何优化C语言设计中不合理的地方,如作用域、IO、函数、指针、宏等方面的改进。

1 C++关键字

C++系统中预定义的、在语言或编译系统的实现中具有特殊含义的单词。下面我们只是看一下C++有多少关键字,不对关键字进行具体的讲解。后面我们学到以后再细讲。
C++(C++98)中总计63个关键字,其中包含了C语言的32个关键字,下面表格列举出了这63个关键字:

asm do if return try continue
auto double inline short typedef for
bool dynamic_cast int signed typeid public
break else long sizeof typename throw
case enum mutable static union wchar_t
catch explicit namespace static_cast unsigned default
char export new struct using friend
class extern operator switch virtual register
const false private template void true
const_cast float protected this volatile while
delete goto reinterpret_cast

2 命名空间

命名空间是用来组织和重用代码的。如同名字一样的意思,NameSpace(名字空间),之所以出来这样一个东西,是因为人类可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象,对于库来说,这个问题尤其严重,如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题了。由于C语言没有很好的办法解决这个问题,而C++为了解决这个问题,于是引入了名字空间这个概念

2.1 如何定义一个命名空间?

命名空间的定义使用关键字 namespace,后跟命名空间的名称,然后接一对{},在{}中定义变量/函数/类型。如下所示:

namespace Z // Z就是命名空间的名称
{
	//命名空间的内容
	int a = 10;
	int b = 20;
	....
	int Add( int x, int y)
	{
		return x + y;
	}
	struct Stu
	{
		int name[20];
		int id;
		.....
	};
}

上面是普通的命名空间,命名空间可以嵌套。如下所示:

namespace Z
{
	int a;
	int b = 20;
	....
	int Add( int x, int y)
	{
		return x + y;
	}
	namespace ZZB
	{
		int m = 10;
		int n = 20;
		int Sub(int x, int y)
		{
			return x - y;
		}
	}
}

另外,在同一个工程中还允许存在多个相同名称的命名空间,编译器最后会j将他们合并为同一个命名空间。

ps:
1.一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
2.命名空间里面的变量仍然是全局的,存储在静态区,命名空间只是起到一个隔离的作用,并不会改变其全局变量的属性。

2.2 命名空间的使用

首先定义一个命名空间:

namespace ZZB
{
	int a = 10;
	int b = 20;
	
	int Add(int x, int y)
	{
	return x + y;
	}
	
	struct Node
	{
		......
	};
}

那么我们该如何使用上面命名空间中的成员呢?下面就给出了命名空间的使用的三种方式。

  • 方法一:加命名空间名称及作用域限定符。(域作用限定符 ::
    指定作用域,做到了最好的命名隔离,但是使用起来不方便。

语法格式:命名空间名称 + :: +成员名称

int main()
{
	printf("a = %d\n", ZZB::a);
	printf("b = %d\n", ZZB::b);
}

运行结果如下:

a = 10
b = 20
请按任意键继续. . .
  • 方法二: 使用using将命名空间中指定成员引入。
    使用 using 关键字可以将命名空间中的指定成员引入到当前作用域中,因此我们在代码中可以直接使用命名空间中的成员,而无需使用完全限定的名称。

语法格式:using 命名空间名称 + :: +成员名称

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

运行结果如下:

a = 10
b = 20
请按任意键继续. . .
  • 方法三:使用using namespace 命名空间名称引入, 将整个命名空间的成员全部展开,用起来方便了,但隔离失效了,在较大的项目中,为了避免命名冲突,最好只引入必要的成员,而不是整个命名空间。这样有助于代码的可维护性和清晰性,慎用。

语法格式:using namespace 命名空间名称

using namespace ZZB;
int main()
{
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	return 0;
}

运行结果如下:

a = 10
b = 20
请按任意键继续. . .

3 C++的输入&输出函数

C++的输入和输出函数分别为:cin 和 cout。它们可以自动识别变量类型不需增加数据格式控制,比如:整形–%d,字符–%c。
使用时必须包含< iostream >头文件以及std标准命名空间。因为 cout 和 cin 是实现在std标准命名空间中的。

早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用< iostream >+std的方式。

使用方式如下:

#include 
using namespace std;//展开命名空间std

int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b; //输入 a = 10, b = 20
	cout << a << " " << b << endl;//endl代表换行
	return 0;
}

结果如下:

10 20
10 20
请按任意键继续. . .

4 缺省参数

4.1 缺省参数的定义

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

#include 
using namespace std;//展开命名空间std
void print(int data = 0)
{
	cout <<"data = " << data << endl;
}
int main()
{
	print();//没有传参时,使用默认值
	print(100);//传参时,使用指定实参
	return 0;
}

结果如下:

data = 0
data = 100
请按任意键继续. . .

4.2 缺省参数的分类

4.2.1 全缺省参数

声明或定义函数时为函数的每一个参数都指定一个默认值。

#include 
using namespace std;//展开命名空间std
int Add(int x = 10, int y = 20, int z = 30)//每一个参数都指定一个默认值
{
	return x + y + z;
}
int main()
{
	Add();
	return 0;
}

4.2.2 半缺省参数

是声明或定义函数时为函数的部分参数指定一个默认值,需要注意的是半缺省参数必须从右往左依次来给出,不能间隔着给。

#include 
using namespace std;//展开命名空间std
int Add(int x, int y = 20, int z = 30)//部分参数都指定一个默认值
{
	return x + y + z;
}
int main()
{
	Add();
	return 0;
}

4.3 注意事项

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现
// test.h
void print(int data = 0)

//test.c
void print(int data = 10)
{
	cout <<"data = " << data << endl;
}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
  1. 缺省值必须是常量或者全局变量
  2. C语言不支持(编译器不支持)

5 函数重载

详情参看如下文章:C++入门(基础语法)之函数重载

6引用

6.1引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
注意:引用类型必须和引用实体是同种类型

语法:类型& 引用变量名(对象名) = 引用实体
C++入门(基础语法)_第1张图片
注意:引用类型必须和引用实体是同种类型的,这是为了保证在使用引用时类型匹配,避免潜在的错误。
在这个例子中,b声明为 double 类型的引用,但试图引用一个 int 类型的变量a,这是不允许的。

int main()
{
	int a = 10;
	double& b = a;
	return 0;
}

C++入门(基础语法)_第2张图片

6.2 引用的特性

6.2.1 引用在定义的时候必须初始化

C++入门(基础语法)_第3张图片

6.2.2 一个变量可以有多个引用

类似于一个人可以有很多个外号。
C++入门(基础语法)_第4张图片

6.2.3 引用一旦引用一个实体,再不能引用其他实体

一旦引用被初始化为引用某个实体,它在其生命周期内将一直引用该实体,而不能再引用其他实体。这是引用的特性之一,称为引用的一次性绑定,也就是引用不能改变其指向。

int main()
{
	int a = 10;
	int b = 20;

	int& c = a;//引用a
	cout << "c: "<< c << endl;

	c = 30;//修改的是a的值,因为c引用的是a
	cout << "a: " << a << " c: " << c << endl;

	int& c = b;//错误,一旦引用被初始化为引用a,就不能再引用其他实体b
	cout << c << endl;

	return 0;
}

C++入门(基础语法)_第5张图片

6.3 常引用

常引用是指通过 const 关键字声明的引用,它用于指示引用的目标对象是常量,即不可被修改。
常引用的语法形式为:

#include 
using namespace std;
int main()
{
	const int a = 10;
	//int& ra = a; //编译报错
	const int& b = 10;//引用常量
	//int& rb = 10;//编译报错
	const int& c = a;//引用 const 修饰的常变量
	return 0;
}

6.4 使用场景

6.4.1 作为函数参数

  1. 引用作为函数参数可以避免传递大型对象时的拷贝开销,从而提高函数调用的效率。在函数调用过程中,如果采用的是传值调用,那么形参是实参的一份临时拷贝。因此,在传递大型对象时,需要将对象拷贝给形参,这会导致函数调用开销较大。而引用是对象的别名,使用引用作为函数参数时,无需进行对象的拷贝操作,因此可以避免这种开销。
  2. 在C语言中,如果我们想要在函数内部修改外部实参的值,通常需要传递实参的地址,然后通过指针来间接修改实参的值。这是因为函数参数是按值传递的,形参是实参的一份拷贝,对形参的修改不会影响到实参本身。因此,为了能够修改实参,需要传递实参的地址,并通过指针来操作实参的值。
    而在C++中,引用提供了另一种方式来实现类似的效果。使用引用作为函数参数时,形参就是实参的别名,对形参的修改会直接反映到实参上,因此无需像在C语言中那样通过指针来操作。这样,可以更直观地在函数内部修改实参的值,同时避免了指针带来的复杂性和可能的错误。引用的使用让代码更加简洁和易读,提高了代码的可维护性和可读性。

以下代码是使用引用来交换两个对象的值:

#include 
using namespace std;
void Swap(int& e1, int& e2)
{
	int tmp = e1;
	e1 = e2;
	e2 = tmp;
}
int main()
{
	int a = 10;
	int b = 20;

	cout << "交换前:a = " << a << " b = " << b << endl;
	Swap(a, b);
	cout << "交换前:a = " << a << " b = " << b << endl;

	return 0;
}

代码运行结果:
C++入门(基础语法)_第6张图片

6.4.2 作为函数返回值

引用作为函数返回值是 C++ 中的一种特性,它允许函数返回一个对象的引用,而不是对象的拷贝,这样可以避免返回大型对象时的拷贝开销,从而提高效率,由于返回的是对象的引用,因此也有了对返回对象的修改能力。
下面详细介绍了引用作为函数返回值的一些情况和用法:

  1. 返回引用以支持修改返回对象:
    例如有一个数组int nums[] = {1, 2, 3, 4, 5},现将数组中所有偶数乘2。

代码如下:

#include 
#include 
using namespace std;
//获取pos位置的元素
int& Get(int* nums, int n, int pos)
{
	assert(pos >= 0);
	assert(pos < n);
	return nums[pos];
}
int main()
{
	int nums[] = { 1, 2, 3, 4, 5 };
	int n = sizeof(nums) / sizeof(nums[0]);

	for (int i = 0; i < n; ++i)
	{
		if (Get(nums, n, i) % 2 == 0)
		{
			Get(nums, n, i) *= 2;
		}
	}
	for (int i = 0; i < n; ++i)
	{
		cout << nums[i] << ' ';
	}

	cout << endl;
	return 0;
}

代码运行结果如下:
C++入门(基础语法)_第7张图片

  1. 返回引用以避免不必要的拷贝:
    通过返回引用,可以避免在函数返回值时创建返回对象的临时拷贝,从而提高程序的性能和效率。
    C++入门(基础语法)_第8张图片上面的传值返回,返回的是返回对象的临时拷贝,当返回对象较大时,拷贝开销太大,影响性能。而传引用返回,不会产生拷贝,效率比较高。
    需要注意的是,传引用返回不能返回临时变量的引用,因为临时变量在函数调用结束后,会被销毁,引用会成为野引用,导致行为未定义。

例如下面的代码:
Add 函数返回了对临时变量 c 的引用,而 c 是一个局部变量,在函数调用结束后会被销毁,引用会指向一个不再存在的对象,导致访问无效的内存。因此,在 main 函数中使用返回的引用 ret 访问值时,会导致未定义行为,输出结果是不确定的(取决于编译器清不清理内存)。

//错误示范
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;
}

再来看一个例子:
代码如下:

#include 
using namespace std;
int& Add(int x, int y)
{
	int z = x + y;
	return z;
}
int main()
{
	int& ret1 = Add(1, 2);
	int& ret2 = Add(3, 4);
	return 0;
}

我们通过调试窗口来监视 ret1 和 ret2 值的变化。
C++入门(基础语法)_第9张图片
下面我们画一下函数调用栈帧图,来理解一下:
C++入门(基础语法)_第10张图片
为避免这种问题,应该确保返回的引用指向的对象在函数调用结束后仍然有效,可以通过返回
1.函数参数的引用,2.返回静态变量或全局变量的引用,3.返回动态分配内存的指针,并在适当的时候释放内存等方式来实现。

6.5 引用和指针的区别

  1. 引用概念上定义一个变量的别名,它们在内部表示为原变量的另一个名称。而指针存储一个变量地址,允许直接访问该地址的变量。

  2. 引用在定义时必须初始化,指针没有要求,而指针可以在任何时候进行初始化,也可以为空或未初始化。

  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。一旦引用被初始化为某个实体,它将一直引用该实体。指针可以在运行时被重新赋值,指向不同的对象。

  4. 没有空引用,但有 NULL 指针,引用必须指向一个已经存在的对象,因此没有空引用的概念。而指针可以具有 NULL 值,表示它不指向任何有效的对象。

  5. 在 sizeof 中含义不同。引用的 sizeof 结果为引用类型的大小,而指针的 sizeof 结果始终为4个或者8个字节。

  6. 引用自加1即引用的对象增加 1,指针自加1即指针向后偏移一个类型的大小,指向下一个相同类型的变量。

  7. 有多级指针,但是没有多级引用。指针可以指向另一个指针,这就是多级指针。而引用只能是一级的,不能指向另一个引用。

  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。

  9. 引用比指针使用起来相对更安全。引用通常更直观和安全,因为它们在初始化后不能被重新赋值,不会出现野指针或悬空指针的问题。

7. 内联函数

内联函数是 C++ 中的一种被 inline 关键字修饰的函数,它通常用于提高程序的执行效率。内联函数的特点是在每次调用时将函数体的代码直接嵌入到调用位置,而不是通过函数调用的方式进行执行。这样可以减少函数调用建立栈帧的开销,从而提高程序的执行效率。
C++入门(基础语法)_第11张图片

7.1 内联函数特点

  1. 以空间换时间: inline 是一种以空间换时间的优化手段,它通过将函数体直接插入到调用位置来减少函数调用的开销,从而提高程序的运行效率。但是,这可能会导致目标文件变大(代码膨胀),因为函数体会在每个调用位置都被复制一次。
  2. 编译器处理 inline 的建议: 编译器在处理 inline 函数时,会根据函数的规模、是否递归、调用频率等因素来决定是否将函数视为内联函数。一般来说,规模较小、不是递归、且频繁调用的函数更有可能被编译器内联。但是,对于具体的实现机制,不同的编译器可能会有不同的策略。
  3. 声明和定义分离的问题:分离声明和定义可能导致链接错误的问题,这是因为 inline 函数在被展开时就没有了函数地址,所以在链接阶段会找不到对应的函数地址,从而导致链接错误。因此,一般来说,不建议将 inline 函数的声明和定义分离,而应该将其放在同一个文件中。

7.2 内联函数和宏的对比

在C++中,一般是不建议使用宏了,因为宏有如下缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查
因此在C++中采取的解决办法是定义常量用 const 或者 enum,定义短小函数用inline。

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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