【C++学习】基础语法(一)

本文介绍了C++语言的基础知识,包括它的诞生背景作为C语言的扩展,支持面向对象编程。C++的命名空间解决了全局作用域中的命名冲突问题,而函数重载允许同名函数根据参数列表不同进行区分。此外,还讨论了引用的概念,它是已有变量的别名,不占用额外内存。

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

1、背景知识

1.1 什么是C++

  C语言是结构化和模块化的语言,长用于处理较小规模的程序;对于规模较大、问题复杂的程序,则需要高度的抽象和建模,此时C语言不合适处理这类问题。为了解决此类影响软件的问题,20世纪80年代,计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。

  1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言的渊源关系,命名为C++。因此C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计

1.2 C++的发展历史

  1979年,贝尔实验室的本贾尼等人试图分析Unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes。

C++创始人:本贾尼

  下图为C++在不同时间的历史版本:

1.3 C++的使用广泛度

  下图数据来源于TIOBE编程语言社区2023年1月4日的最新排行榜,在30多年的发展中,C/C++排名一直稳居前五。

image-20230104201350733

  由此可以看出,C/C++、Java是目前比较主流的程序语言,尤其是C++语言掌握者适合在操作系统及大型软件开发、服务器端开发、游戏开发、嵌入式和物联网、数字图像处理、人工智能、分布式应用等领域工作。

2、基础语法

2.1 "::"域作用限定符

  域作用限定符表示作用域,和所属关系。下列代码中,coutendl属于std, 而std是C++标准库中函数或者对象

int main()
{
	std::cout << "Hello World" << std::endl;
	std::cout << "Hello World" << "\n";
	return 0;
}

  endl表示换行,类似于C语言中的’\n’,cout表示类似于C语言中printf的格式化输出;

调试结果:

image-20230104205905249

2.2 命名空间

  在C/C++中,大量存在着变量、函数、类,这些都存在于全局作用域中,会导致名字冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或者名字污染namespace关键字的出现就是针对这种问题。

下图中,在只包含标准输入输出库stdio中,全局变量rand能够正常使用:

image-20230104212155984

  但加入了标准库函数头文件后,全局变量rand就被“重定义”报错了,这是因为在stdlib中又随机函数rand,与下图的全局变量rand命名冲突了。

image-20230104212541176

例如,下列代码中,全局域和局部域中整型变量a的名字都相同,C语言中局部域无法调用全局域变量a

int a = 1;

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

void f2()
{
	int a = 1;
}

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

调试结果:

image-20230104214726172

C++在局部域的输出函数中添加域作用限定符,使得局部域中也能调用全局域的变量。

image-20230104214818029

上图中,::的前面空格表示在全局中查找,不能在其他函数中查找。

2.2.1 命名空间的定义

  在C++中,定义命名空间需要使用关键字namespace,后面跟着命名空间的名字,然后接一对花括号{},{}中为命名空间的成员。

  1. 一般命名空间的定义事例:
namespace joes
{
    int rand = 10;
    int Add(int left,int right)
    {
        return left + right;
    }
    struct Node
    {
        struct Node* next;
        int val;
    }
}
  1. 命名空间的嵌套事例:
namespace P1
{
    int a;
    int b;
    int Add(int left,int right)
    {
        return left + right;
    }
    namespace P2
    {
        int c;
        int cp;
        int Sub(int left,int right)
        {
            return left - right;
        }
    }
}
  1. 命名空间的合并:

AA.h头文件

namespace joes
{
    int Add(int* a,int* b)
    {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
}

BB.h头文件

namespace joes
{
    int Add(int& x,int& y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
}

同一个工程中允许存在多个相同名称的命名空间,如上例中的joes,编译器最后会合成同一个命名空间中joes

2.2.2 命名空间的使用

主函数:

int main()
{
	struct AList::Node node1;
	struct BQueue::Node node2;
	AList::min++;
	BQueue::min++;
	return 0;
}

List.h头文件:

namespace AList
{
	struct Node
	{
		struct Node* prev;
		struct Node* next;
		int val;
	};
	int min = 1;
}

Queue.h头文件:

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

	struct Queue
	{
		struct Node* head;
		struct Node* tail;
	};
	int min = 0;
}

2.3 C++的输入与输出

  当我们在学C语言时,编写的第一个程序为输出"Hello World":

#include<stdio.h>//C语言的标准输入输出库
int main()
{
    printf("Hello Word\n");
    return 0;
}

而Python亦较为简洁:

print("Hello World")

那么C++是如何实现的?

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

image-20230106213650709

注意:

  1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含头文件以及命名空间使用方法使用std;
  2. **<<是流插入运算符,>>**是流提取运算符;
  3. C++的输入输出不需要像printfscanf那样手动控制格式,C++的coutcin能够自动识别变量类型;
  4. 在日常学习中,建议直接using namespace std即可;但using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +using std::cout展开常用的库对象/类型等方式。

2.4 缺省参数

2.4.1 缺省参数概念

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

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

int main()
{
	Func();//没有传参数时,使用参数的默认值0
	Func(1);//传参数时,使用指定的实参1
	return 0;
}

调试结果:

image-20230107194041408

2.4.2 缺省参数分类

  1. 全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
	cout << "c= " << c << endl;
	cout << endl;
}

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

调试结果:

image-20230107195044938

注意:使用缺省值必须从右往左连续使用。

  1. 半缺省参数
void Func(int a, int b = 20, int c = 30)
{
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;
	cout << "c= " << c << endl;
	cout << endl;
}

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

调试结果:

image-20230107195713944

注意:半缺省必须从右往左连续缺省。

2.4.3 缺省参数的实际应用

  例如数据结构中,栈的建立,具体详见我的上篇博文【数据结构】超详细——动态栈

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

void StackInit(struct Stack* ps,int defaultCapacity = 4)
{
	ps->a = (int*)malloc(sizeof(int) * defaultCapacity);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = defaultCapacity;
}

int main()
{
	Stack st1;//最多要存100个数据时
	StackInit(&st1, 100);

	Stack st2;//不知道有多少数据时
	StackInit(&st2);
	return 0;
}

此外,缺省参数不能在函数声明和定义中同时出现。

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

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

下面为头文件:

void Func(int a = 10);

调试结果:

image-20230107200909447

出现错误是由于程序的头文件中,函数Func声明和C++程序中Func的定义都使用了缺省参数。修改声明后:

void Func(int a);

调试结果:

image-20230107201251729

修改后程序正常执行,亦可以修改函数的定义文件,删去缺省参数。

image-20230107201627231

2.5 函数重载

  自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载。

2.5.1 函数重载概念

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

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

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

调试结果:

image-20230107203232291

类型顺序不同:

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

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

int main()
{
	f(10, 'j');
	f('u', 20);
	return 0;
}

调试结果:

image-20230107203641810

上例中,函数名相同,形式参数列表的类型顺序不同以及类型不同。

2.5.2 C++支持函数重载的原理–名字修饰(name Mangling)

  C/C++语言中,一个程序要运行起来,需要经历以下几个阶段:预处理编译汇编链接

image-20230107221321674

预处理:文件展开,条件编译,宏替换,去注释等;

编译:C++语言汇编语言;

汇编:将编写的代码翻译成二进制目标文件;

链接:将形成的.obj文件和库文件合并,形成可执行程序。

根据上图,编写具如下程序:

image-20230107213908231

对程序进行预处理:

image-20230107214000809

对程序进行编译:

image-20230107214030257

对程序进行汇编:

image-20230107214105341

对程序进行连接:

image-20230107214129718

调试结果:

image-20230107214148622

采用C++语言编写的test.cpp程序,g++编译的结果可知,函数名的修饰发生变化,编译器将函数参数类型信息添加到修改后的名字中,g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。

image-20230107214951667

而函数重载时,g++编译后函数修饰名具体为:

image-20230107223339729

而在linux环境中,采用gcc编译C语言程序后,函数名字的修饰没有发生改变:

image-20230107220953011

注意

如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

2.6 引用

2.6.1 引用概念

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

例如:林允儿,人称“允儿”,江湖人称“小鹿”。

林允儿

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

例如下列代码中,变量ki的别名,m亦是i的别名,而nk的别名:

int main()
{
	int i = 0;
	int& k = i;//引用

	int j = i;

	cout <<"i的地址:"<< & i << endl;
	cout <<"j的地址:"<< & j << endl;
	cout <<"k的地址:"<< & k << endl;

	++k;
	++j;

	int& m = i;
	int& n = k;
	++n;

	return 0;
}

变量ijk的地址为:

image-20230106211314747

由此可知,引用不在内存中开辟新的空间。此外,引用类型必须和引用实体是同种类型的。

2.6.2 引用特性

  1. 引用在定义时必须初始化;
  2. 一个变量可以有多个引用;
  3. 引用一旦引用一个实体,再不能引用其他实体。
int main()
{
	int i = 11;
	int* p = &i;
	int*& rp = p;
	return 0;
}

若引用定义时未初始化,则会出现下图的报错提醒:

image-20230106212836139

在上述代码行中,iprp引用。

2.6.3 常引用

#include<iostream>
using std::cout;
using std::endl;

void TestConst()
{
	const int a = 10;
	const int& ra = a;	
	const int& b = 10;
}

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

调试结果:

image-20230107224358630

由此可见,代码执行正常。但常见下面错误:

void TestConst()
{
	const int a = 10;
	int& ra = a; 
}

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

调试结果:

image-20230107224613018

这是由于变量a是常量,引用时遗落了常量类型关键字const

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值