当前位置: 首页 > article >正文

C++学习之路,从0到精通的征途:入门基础

目录

一.C++的第一个程序

二.命名空间

1.namespace的价值

2.命名空间的定义

3.命名空间使用

三.C++的输入与输出

1.<iostream>

2.流

3.std(standard)

四.缺省参数 

1.缺省参数的定义

 2.全缺省/半缺省

3.声明与定义

​五.函数重载

1.参数个数不同

2.参数类型不同

3.参数类型顺序不同

函数返回值不同是否能作为重载条件?

带有缺省参数的函数能构成重载吗?

六.引用

1.引用的概念和定义

2.引用的特性  

2.1引用在定义是必须初始化

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

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

3.引用的使用

3.1引用传参

3.2引用作返回值

4.const引用

5.指针和引用的关系

七.inline

 1.inline的定义与概念

2.inline的特性

3.inline与宏函数的比较

八.nullptr

九.总结 


一.C++的第一个程序

        第一个程序当然是“Hello World”,不过这里我们使用C++的语法进行实现。

        在实现前,我们创建的文件必须得是.cpp文件,编译器在识别后缀为.cpp后才会调用C++编译器,这里后缀改为.c文件是会报错的。

#include<iostream>
using namespace std;

int main()
{
	cout << "Hello World" << endl;
	return 0;
}

运行结果:

        这里对于代码有疑问是正常的,接下来我们来逐一讲解。

二.命名空间

1.namespace的价值

        在C语言中如果一个声明的变量名刚好与其他变量名重复或者与函数名等重复,则会出现报错,例如下面这段代码:

#include<stdio.h>
#include<stdlib.h>

int rand = 0;

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

报错片段:

        可以看到报错语句为重定义:以前的定义是函数。

        在这里我们期望rand是int类型,但rand是函数类型,这说明由于stdlib.h中rand()的存在,编译器将rand识别为了函数,在C语言中如果要解决这样的问题只能再取一个其他变量名,但在C++中我们可以用命名空间去解决命名冲突的问题。

2.命名空间的定义

        定义命名空间,需要用到关键字namespace,后面跟命名空间的名字,这里用mySpace举例,再跟一对大括号{},{}中可以定义命名空间的成员,成员可以是变量/函数/类型等。

代码演示:

namespace mySpace
{
	int rand = 0;

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

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

        namespace本质是定义一个域,在C语言中由于不用域中可以定义同名变量,namespace也类似与这种特性,namespace定义的域与全局域各自独立,其通过域隔离解决了同名冲突的问题,且命名空间不会影响变量的生命周期。

        命名空间只能定义在全局,同时namespace也可以嵌套定义。

代码演示:

namespace mySpace
{
	namespace s1
	{
		int rand = 0;
		int a = 1;
	}

	namespace s2
	{
		int rand = 10;
		int a = 11;
	}
}
	printf("%d\n", mySpace::s1::a);
	printf("%d\n", mySpace::s2::a);

        在调用的过程中,我们使用域作用限制符::,它能在编译时通过指定域的形式进行查找。

        如第一条printf语句,就是访问MySpace中命名空间s1中的整型变量a,打印值为1。

运行结果:

·        在项目目工程文件中,同名的namespace会认为是一个namespace不会冲突:

·        我们再定义一个头文件,并创建一个同名namespace。

        

        在包含头文件后可以看到,程序并未报错,并成功输出b的值,这说明编译器认为两个namespace并不冲突,为同一个命名空间。

3.命名空间使用

         编译器的查找规则为: 1.先局部再全局        2.指定域中查找

        所以如果直接查找命名空间中的变量名而并未指定,则会报错,参考下面程序:

        如果要正确使用命名空间中定义的变量/函数,有三种方式:

        1.运用域作用限制符,指定命名空间访问,项目中推荐使用。

        2.使用using将命名空间中某个频繁使用的变量/函数展开,项目中不推荐使用。

        这样就可以让上面的程序不报错了,但由于将命名空间中的a展开,可能会有同名冲突的风险。
        3.用using展开命名空间中的所有成员项目中不推荐,冲突风险大,日常练习时方便推荐使用。

三.C++的输入与输出

1.<iostream>

        C++中的输入与输出不再使用<stdio.h>,而是用<iostream>,iostream是Input Output Stream 的缩写,库中定义了标准的标准的输入输出对象,其中包含<iostream>也会间接包含<stdio.h>,所以也能使用printf和scanf,不同编译器可能会报错。

2.流

        std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。

        std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流。

        std::endl 是⼀个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区。现在可以简单理解为换行符。  

        <<是流插入运算符,>>是流提取运算符。(C语言中用这两个运算符做位运算左移/右移)

示例:

        C++中的输入输出不再需要像C语言那样手动指定格式,流可以自动识别变量类型,也能更好的支持自定义类型的输入输出。

例如:

3.std(standard)

        C++的标准库放在一个为std的命名空间中,cout/cin/endl都属于标准库,所以需要通过命名空间std去使用他们。   

        与命名空间的使用方式相同:

        1.域作用操作符,指定命名空间访问

	std::cin >> a;
	std::cout << a << std::endl;

        2.using展开频繁使用的成员

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

int main()
{
	int a = 0;
	std::cin >> a;
	cout << a << endl;

	return 0;
}

        3.using展开命名空间,项目中不推荐使用,推荐日常练习

using namespace std;

四.缺省参数 

1.缺省参数的定义

        缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。调用该函数时,如果并未传输指定的实参,则直接采用缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)

如图:

 2.全缺省/半缺省

        全缺省就是所有形参给缺省值,示例:

        半缺省就是部分下形参给缺省值,示例:

        由于这里是半缺省,a没有缺省值,所以在调用函数时必须传至少一个参数。                

        同时C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

报错片段:

        带有缺省参数的函数调用,传参时不可跳跃,必须从左到右依次给实参。

3.声明与定义

        带有缺省参数的函数在声明和定义分离时,不能同时给缺省参数,规定必须在声明中出现。

五.函数重载

        C++中支持在同一作用域下同名函数的出现,但必须这些函数的形参个数不同,形参类型不同,或形参类型顺序不同,这样可以让函数调用呈现多态行为,更加灵活。

1.参数个数不同

2.参数类型不同

3.参数类型顺序不同

函数返回值不同是否能作为重载条件?

        由于函数名和参数相同,在调用函数时无法根据返回值的不同来区分到底要调用哪个函数,所以会报错,显示函数f重定义

        所以函数返回值不同并不能作为重载条件。

带有缺省参数的函数能构成重载吗?

        可以看到报错语句为对重载函数的调用不明确,说明带有缺省参数的函数构成重载,但在函数调用时,编译器不知道调用的是哪个函数,存在歧义,所以会导致报错。

六.引用

1.引用的概念和定义

        引用不是定义一个新变量,而是给已有的变量取一个别名,编译器不会为引用变量开辟空间,引用变量与被引用的变量使用同一块内存空间。

类型& 引用别名 = 引用对象

	int a = 0;

	int& b = a;
	int& c = a;

        b,c是变量a的别名。这里引用符号&与取地址符号相同,根据用法区分即可。

    int& d = b;

        也可以给别名b取别名为d,所以d也是a的别名         

      由于b,c,d是a的别名,四者同用一块内存空间,在++d后,a,b,c,d的值全部自增一,并且从运行结果可以看出四个变量的地址相同,共用一块空间。 

2.引用的特性  

2.1引用在定义是必须初始化

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

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

3.引用的使用

        引用在实际操作中主要用来作引用传参引用作返回值,从而减少拷贝,提高效率,在改变引用对象的同时改变被引用对象。

3.1引用传参

        引用传参指针传参类似,但理解和使用更加简单和方便,以Swap函数举例:

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

运行结果:

        可以看到a,b交换了值,但与指针不同的是函数体里无需解引用操作,因为引用对象与被引用对象使用的是同一块内存空间,直接交换即可。

3.2引用作返回值

        为了更好的举例,在这里我们添加数据结构栈的头文件和源文件,代码详细参考:栈:数据结构中的“时间管理大师” 

        可以看到在让栈顶加10的操作中出现了报错,报错提示为StackTop(&st)不是左值,说明此时栈顶不可被修改,为右值

分析:

              

        在函数返回时,操作系统会将返回值储存在一个临时对象中,再将临时对象中的值返回,因为这个临时对象具有常性,所以栈顶不能被修改。那如何才能让这个操作合法呢?

        在这里我们将引用作返回值,并将参数优化为引用参数

// 获取栈顶元素 
STDataType& StackTop(Stack& ps)
{
	assert(ps._top > 0);
	return ps._a[ps._top - 1];
}

        此时函数栈帧销毁后,返回对象top并不是存在临时对象中,即函数栈帧销毁对返回其引用并无影响,故此时栈顶可修改。

        为了更深刻的理解引用作返回值的情况,再举一个示例:

        这里可以看到,虽然编译器并未报错,但是返回值并未加10,由于ret存在于函数栈帧中,出函数后,ret也销毁了,所以返回引用结果出现问题。

        对于引用返回,函数栈帧结束后,如果返回对象在函数栈帧中,返回引用就会出现问题,如果返回对象不在函数栈帧中,可以正常返回其引用。

4.const引用

        可以引用一个const对象,但引用也必须为const,对于普通对象也可以进行const引用,因为对象的访问权限在引用过程中只可以缩小,不能放大。

        在类型转换中的const引用:

        由于在类型转换时,操作系统会创建临时对象,临时对象具有常性,所以在转换时需要用const引用接收。

临时对象:

        所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象

        临时对象在以下三种常用情况下会创建:

        1. 函数传值返回

        2.表达式运算

        3.类型转换

5.指针和引用的关系

        指针与引用在实践中相辅相成,在功能上有重叠性,但都有各自的特点,互相不可替代。

七.inline

 1.inline的定义与概念

        被inline修饰的函数叫做内联函数,在编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数时就不需要创建函数栈帧了,提高了效率。

2.inline的特性

        inline适用于频繁调用的短小函数,对于递归函数,代码量较多的函数加上inline也会被编译器忽略。

        通过该段代码的汇编可以看到,在调用内联函数Swap时,并没有call指令,说明Swap函数在调用中已展开,根据右边的汇编代码可以进一步验证。

        inline不建议声明和定义分离到两个文件,分离会导致链接错误,因为inline被展开,就没有函数地址,链接时会报错。

3.inline与宏函数的比较

        先来看一段宏函数:

        对于这个宏函数,为了巩固理解,需要回答下面三个问题: 

        1.为什么要加分号?

        宏函数是在预处理时,将语句整体替换到调用处,如果加了分号,分号也会一并调换。

        2.为什么加外面的括号?

        宏函数在与其他式子作运算时需要考虑优先级的问题,加了外面的括号,保证宏函数优先级更高。

        3.为什么加里面的括号?

        a,b各自为一个表达式,加了里面的括号,保证表达式的优先运算。

        从这三个问题可以看出宏函数的实现很复杂,且很容易出错,不方便调试,所以C++中设计inline的目的就是为了替代宏函数,在提高效率的同时,更容易实现,不过与宏不同的是,inline的展开是在编译而不是预处理阶段,使其更容易调试。

八.nullptr

        NULL实际是一个,在传统的C头文件(stddef.h)中,可以看到如下代码:

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

        所以在C++中NULL可能会被定义为字面常量0,在下面这种情况下,与程序的初衷违背:

        这里func(NULL)的目的是调用func(int* ptr),但却由于NULL被定义成了数字常量0,调用成了func(int x),所以如果要调用func(int* ptr),还得对NULL强转类型:

        但强转类型只能让NULL强转一种类型,无法应对变量的多种类型,所以C++在这里引入了一个关键字:nullptr它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

示例:

九.总结 

        C++ 的这些基础内容,是踏上这门强大编程语言之旅的重要一步。从第一个程序的 “Hello, World!” 开始,我们逐步揭开了命名空间、输入输出、缺省参数、函数重载、引用、inline函数以及 nullptr 等关键概念的神秘面纱。这些知识相互关联,共同构成了 C++ 编程的基石。


http://www.kler.cn/a/573365.html

相关文章:

  • yum修改阿里云
  • C#—csv文件格式操作实例【在winform表格中操作csv】
  • 【文献阅读】Efficient Prompting Methods for Large Language Models: A Survey
  • 设计模式(7)——SOLID原则之接口隔离原则
  • Kotlin中的数字
  • React - Hooks - useRef
  • 物联网感应层设备的通信协议及数据上传路径详解
  • html+js 轮播图
  • [项目]基于FreeRTOS的STM32四轴飞行器: 二.项目搭建及移植FreeRTOS
  • 在 Apache Tomcat 中,部署和删除项目
  • 物联网感知层常用感应设备
  • Milvus安装linux操作步骤
  • 初识Qt · Qt的基本认识和基本项目代码解释
  • 【含文档+PPT+源码】基于SpringBoot+Vue的个性化健身助手系统
  • 【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器- 2.1 Chrome架构:仅仅打开了1个页面,为什么有4个进程?
  • MySQL数据迁移——实战锻炼
  • 高频 SQL 50 题(基础版)_1667. 修复表中的名字
  • QT-绘画事件
  • Leetcode 1477. 找两个和为目标值且不重叠的子数组 前缀和+DP
  • pnpm add和pnpm install指定包名安装的区别