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

C++初阶——入门

目录

1、C++发展历史

2、C++版本更新

3、C++参考文档

4、C++书籍推荐

5、C++的程序

6、命名空间

6.1 namespace的作用

6.2 namespace的定义

6.3 namespace的使用

7、C++输入&输出

8、缺省参数

9、函数重载

10、引用

10.1 引用的概念和定义

10.2 引用的特性

10.3 引用的使用

10.4 const引用

10.5 指针和引用的关系(面试高频考查点)

11、inline

12、nullptr


1、C++发展历史

C++的起源可以追溯到1979年,当时Bjarne Stroustrup(本贾尼·斯特劳斯特卢普,这个翻译的名字不同的地方可能有差异)在贝尔实验室从事计算机科学和软件工程的研究工作。面对项目中复杂的软件开发任务,特别是模拟和操作系统的开发工作,他感受到了现有语言(如C语言)在表达能力、可维护性和可扩展性方面的不足。

1983年,Bjarne Stroustrup在C语言的基础上添加了面向对象编程的特性,设计出了C++语言的雏形, 此时的C++已经有了类、封装、继承等核心概念,为后来的面向对象编程奠定了基础。这一年该语言被正式命名为C++。

在随后的几年中,C++在学术界和工业界的应用逐渐增多。一些大学和研究所开始将C++作为教学和研究的选语言,而一些公司也开始在产品开发中尝试使用C++。这一时期,C++的标准库和模板等特性也得到了进一步的完善和发展。

C++的标准化(使得 C++ 代码在不同的编译器下表现一致)工作于1989年开始,并成立了一个ANSI和ISO(International Standards Organization)国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第一个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。

在完成C++标准化的第一个草案后不久,STL(Standard Template Library)是惠普实验室开发的系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。在通过了标准化第一个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。

1997年11月14日,联合标准化委员会通过了该标准的最终草案。1998年,C++的ANSI/IS0标准被投入使用。

2、C++版本更新

C++98:这是第一个 ANSI/ISO 标准化的 C++ 版本,发布于 1998 年。它基于 Bjarne Stroustrup 于 1985 年创建的原始 C++ 设计,并加入了一些重要特性,如 STL(标准模板库)、异常处理、I/O Streams、命名空间和 RTTI(运行时类型识别)。

C++03:这个版本主要是对 C++98 的一些修正和改进,发布于 2003 年,并未引入新的语言特性,所以一般不把它当做重要版本,存在感也不强。

C++11:这是 C++ 历史上最重大的更新之一,有时被称为 C++0x(因为它原计划在 200x 年发布,一直跳票)。它引入了大量新特性,如自动类型推断(auto 关键字)、基于范围的 for 循环、Lambda 表达式、智能指针、并发支持、移动语义、nullptr 和更强大的模板功能等。

C++14:作为 C++11 的小幅度更新,C++14 引入了一些改进和新特性,包括泛型 Lambda 表达式、返回类型推导、二进制字面量、数字分隔符、弃用属性等。

C++17:这个版本进一步提升了 C++ 的功能和易用性,新功能不是很多,引入了结构化绑定、if constexpr、std::optional、std::variant、std::string_view、并行算法等特性。

C++20 是继 C++11 之后又一个重大更新,引入了概念(concepts)、范围库(ranges)、协程(coroutines)、模块(modules)、三元运算符的改进、constexpr 的增强、std::span 等新特性。

C++23 是 2023 年 7 月份刚确定下的新标准,目前能完整支持 C++23 的编译器基本没有。变化包括引入标准库的模块化支持、扩展 constexpr 、增加并行算法、ranges 扩展、this 推导、引入更多的属性和注解、增加 std::mdspan、std::generator 等新特性。

3、C++参考文档

不是C++官官方文档,标准也只更新到C++11,但是以头文件形式呈现,内容比较易看好懂

Reference - C++ Reference

是C++官方文档的英文版,更新到了最新的C++标准,但相比第一个不那么易看

https://en.cppreference.com/w/

是C++官方文档的中文版

https://zh.cppreference.com/w/cpp

4、C++书籍推荐

C++ Primer:主要讲解语法,经典的语法书籍,可以作为语法字典,非常好用。

STL源码剖析:主要从底层实现的角度结合STL源码,庖丁解牛式剖析STL的实现,是侯捷老师的经典之作。可以很好的帮助我们学习别人用语法是如何实现出高效简洁的数据结构和算法代码,如何使用泛型封装等。让我们不再坐井观天,闭门造车,适合中后期可以看。

Effctive C++:本书也是侯捷老师翻译的,本书有的一句评价,把C++程序员分为看过此书的和没看过此书的。本书主要讲了55个如何正高效使用C++的条款,建议中后期可以看一遍,工作1-2年后再看遍,相信会有不一样的收获。

5、C++的程序

C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以运行,C++中需要把定义文件代码后缀名改为.cpp,vs编译器看到是.cpp就会调用C++编译器编译,linux下要用g++编译,不再是gcc

// 兼容C语言
// test.cpp
#include<stdio.h>
int main()
{printf("hello world\n");return 0;
}

当然C++有一套自己的输入输出,严格说C++版本的hello world应该是这样写的

// test.cpp
// 这里的std cout看不懂,没关系,下面会依次讲解
#include<iostream>
using namespace std;int main()
{cout << "hello world\n" << endl;return 0;
}

6、命名空间

6.1 namespace的作用

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。

使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

c语言项目类似下面程序这样的命名冲突是普遍存在的问题,如:

#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”printf("%d\n", rand);return 0;
}

在#include <stdlib.h>中,有rand函数(int rand (void);),此时不知道是rand是变量名,函数名

6.2 namespace的定义

• 定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。命名空间中可以定义 变量 / 函数 / 类型 等。

• namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不在冲突了。

• C++中域有 函数局部域,全局域,命名空间域,类域。域影响的是编译时语法查找一个 变量 / 函数 / 类型 出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。

• namespace只能定义在全局,当然他还可以嵌套定义

• 项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。

C++标准库都放在一个叫std(standard)的命名空间中。

#include <stdio.h>
#include <stdlib.h>// 1. 正常的命名空间定义
// Lzc是命名空间的名字,⼀般开发中是用项目名字做命名空间名
// 日常练习可以用自己的名字命名
namespace Lzc
{// 命名空间中可以定义变量/函数/类型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}int main()
{printf("%p\n", rand);// 打印rand函数的地址printf("%d\n", Lzc::rand);// 打印Lzc命名空间里的rand变量return 0;
}//2. 命名空间可以嵌套
namespace S
{namespace Lzc{int rand = 1;int Add(int left, int right){return left + right;}}namespace Ysy{int rand = 2;int Add(int left, int right){return (left + right) * 10;}}
}int main()
{printf("%d\n", S::Lzc::rand);// 1printf("%d\n", S::Ysy::rand);// 2printf("%d\n", S::Lzc::Add(1, 2));// 3printf("%d\n", S::Ysy::Add(1, 2));// 30return 0;
}//3. 多文件中可以定义同名namespace,他们会默认合并到⼀起,就像同一个namespace一样
// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace Lzc
{typedef int STDataType;typedef struct Stack{STDataType* a;int top;int capacity;}ST;void STInit(ST* ps, int n);void STDestroy(ST* ps);void STPush(ST* ps, STDataType x);void STPop(ST* ps);STDataType STTop(ST* ps);int STSize(ST* ps);bool STEmpty(ST* ps);
}// Stack.cpp
#include"Stack.h"
namespace Lzc
{void STInit(ST* ps, int n){assert(ps);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;}// 栈顶void STPush(ST* ps, STDataType x){assert(ps);// 满了, 扩容if (ps->top == ps->capacity){printf("扩容\n");int newcapacity = ps->capacity == 0 ? 4 : ps->capacity* 2;STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;}//...
}// test.cpp
#include"Stack.h"
// 全局定义了一份单独的Stack
typedef struct Stack
{int a[10];int top;
}ST;
void STInit(ST* ps) {}
void STPush(ST* ps, int x) {}int main()
{// 调用全局的ST st1;STInit(&st1);STPush(&st1, 1);STPush(&st1, 2);printf("%d\n", sizeof(st1));// 调用S namespace的Lzc::ST st2;printf("%d\n", sizeof(st2));Lzc::STInit(&st2, 4);Lzc::STPush(&st2, 1);Lzc::STPush(&st2, 2);return 0;
}

6.3 namespace的使用

编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。我们要使用命名空间中定义的变量/函数,有三种方式:

指定命名空间访问项目中推荐这种方式。

using将命名空间中某个成员展开项目中经常访问的不存在冲突的成员推荐这种方式。

using展开命名空间中全部成员项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。

#include<stdio.h>namespace Lzc
{int a = 0;int b = 1;
}int main()
{// 编译报错:error C2065: “a”: 未声明的标识符printf("%d\n", a);return 0;
}//1. 指定命名空间访问
int main()
{printf("%d\n", Lzc::a);return 0;
}//2. using将命名空间中某个成员展开
using Lzc::b;
int main()
{printf("%d\n", Lzc::a);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);return 0;
}//3. 展开命名空间中全部成员
using namespace Lzc;
int main()
{printf("%d\n", a);printf("%d\n", b);return 0;
}

7、C++输入&输出

• 是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。

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

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

• std::endl 是一个函数,流插入输出时,相当于插如一个换行字符加刷新缓冲区。

现在的阶段,std::endl可以理解为就是"\n"

• >是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)

• 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是 C++的流能更好的支持自定义类型对象的输入输出

• IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,后面我们会有专门的一个章节来细节IO流库。

• cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要 通过命名空间的使用方式去用他们。

注意:这里不是说std里有iostream,那为什么还要引入头文件<iostream>,

在C++标准库中,iostream是一个命名空间,包含了处理输入输出操作的主要类和函数,如cincout等。虽然这些功能已经包含在库中,但是通过引入#include <iostream>头文件,程序员可以明确地告诉编译器他们想要使用这部分功能,并使得编译器能在程序中定位到这些相关的声明和定义。

头文件不仅提供类和函数的原型,还可以包含一些预处理器指令(如宏定义),以及可能导致链接阶段错误的其他信息。此外,通过引用头文件,我们能享受到编译器的依赖管理和类型检查,提高代码的可读性和维护性。

所以,即使库内部已经有了这些内容,每使用一次iostream里的功能都需要包含这个头文件,这是一种编程约定和组织方式。

一般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std。

• VS系列编译器没有包含<stdio.h>,也可以使用printf和scanf,因为在<iostream>里间接包含了。其他编译器可能会报错,就加上<stdio.h>。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << " " << c << endl;std::cout << a << " " << b << " " << c << std::endl;scanf("%d%lf", &a, &b);printf("%d %lf\n", a, b);// 可以自动识别变量的类型cin >> a;cin >> b >> c;cout << a << endl;cout << b << " " << c << endl;return 0;
}
#include<iostream>
using namespace std;
int main()
{// 在io需求⽐较⾼的地方,如部分大量输入的竞赛题中,加上以下3行代码// 可以提高C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}
  1. ios_base::sync_with_stdio(false):取消标准输入输出缓冲区(stdio)与C库的标准输入输出缓冲区之间的同步。这可以避免在处理大量数据时由于等待I/O操作完成而导致的性能瓶颈。

  2. cin.tie(nullptr)cout.tie(nullptr):取消 cin 和 cout 对齐到同一线程的同步。当有多线程同时读写控制台输出时,这可以避免因为线程间的同步而产生的额外开销。

在需要频繁输入输出、并且对速度有较高要求的情况下,例如编程竞赛或大数据处理等场景,加入这些代码有助于提升程序运行效率。然而,在日常开发中,如果不需要处理这种高并发场景,保持默认设置通常是更安全的选择。

8、缺省参数

• 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)

• 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值

• 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。

函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定函数声明处给缺省 值。(考虑到函数声明和定义处的缺省值不一致等问题)

#include <iostream>
#include <assert.h>using namespace std;
void Func(int a = 0)
{cout << a << endl;
}int main()
{Func(); // 没有传参时,使用参数的默认值 0Func(10); // 传参时,使用指定的实参 10return 0;
}
#include <iostream>using namespace std;// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}// 半缺省
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}int main()
{Func1();Func1(1);Func1(1, 2);Func1(1, 2, 3);Func2(100);Func2(100, 200);Func2(100, 200, 300);return 0;
}
// Stack.h
#include <iostream>
#include <assert.h>using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST* ps, int n = 4);// Stack.cpp
#include"Stack.h"// 缺省参数不能声明和定义同时给
void STInit(ST* ps, int n)
{assert(ps && n > 0);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;
}// test.cpp
#include"Stack.h"int main()
{ST s1;STInit(&s1);// 确定要插入1000个数据,初始化时就开好,避免扩容ST s2;STInit(&s2, 1000);return 0;
}

9、函数重载

C++支持在同一作用域出现同名函数,但是要求这些同名函数的形参不同,可以是

参数个数不同,参数类型不同,参数类型顺序不同

注意:返回值类型不同不能作为重载条件,因为调用时也无法区分

C语言是不支持同一作用域中出现同名函数的

#include<iostream>using namespace std;// 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、参数个数不同
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}// 3、参数类型顺序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}// 返回值不同不能作为重载条件,因为调用时也无法区分
//void fxx()
//{}
//
//int fxx()
//{
// return 0;
//}// 下面两个函数构成重载
// f1()调用时,会报错,存在歧义,编译器不知道调用谁
void f1()
{cout << "f()" << endl;
}void f1(int a = 10)
{cout << "f(int a)" << endl;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}

10、引用

10.1 引用的概念和定义

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

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

#include<iostream>
using namespace std;
int main()
{int a = 0;// 引用:b和c是a的别名int& b = a;int& c = a;// 也可以给别名b取别名,d相当于还是a的别名int& d = b;++d;// 这里取地址我们看到是⼀样的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

10.2 引用的特性

• 引用在定义时必须初始化

一个变量可以有多个引用

C++规定引用不能改变指向,引用一旦引用一个实体,再不能引用其他实体

#include<iostream>using namespace std;int main()
{int a = 10;// 编译报错:“ra”: 必须初始化引⽤//int& ra;int& b = a;int c = 20;// 这里并不是让b引用c,因为C++规定引用不能改变指向// 这里是⼀个赋值,即 a = 20;b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}

10.3 引用的使用

• 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率改变引用别名时同时改变引用对象

引用传参跟指针传参功能是类似的,引用传参相对更方便一些

• 引用返回值的场景相对比较复杂,我们在这里简单讲了一下场景,还有一些内容后续类和对象章节中会继续深入讲解。

引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代(如:在链表里的next,如果用引用,因为不能改变其指向,所以不能改变next,只能用指针)。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的区别就是是,C++引用定义后不能改变指向, Java的引用可以改变指向。

• 一些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,目的是简化程序,避开复杂的指针。

#include<iostream>using namespace std;void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 0, y = 1;cout << x << " " << y << endl;Swap(x, y);cout << x << " " << y << endl;return 0;
}
#include<iostream>using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST& rs, int n = 4)
{rs.a = (STDataType*)malloc(n * sizeof(STDataType));rs.top = 0;rs.capacity = n;
}// 栈顶
void STPush(ST& rs, STDataType x)
{// 满了, 扩容if (rs.top == rs.capacity){printf("扩容\n");int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}rs.a = tmp;rs.capacity = newcapacity;}rs.a[rs.top] = x;rs.top++;
}// int STTop(ST& rs)
int& STTop(ST& rs)
{return rs.a[rs.top - 1];
}int main()
{ST st1;STInit(st1);STPush(st1, 1);STPush(st1, 2);cout << STTop(st1) << endl;// 2STTop(st1) += 10;// 返回了rs.a[rs.top - 1]的别名,可以进行修改cout << STTop(st1) << endl;// 12return 0;
}
#include<iostream>
#include<assert.h>using namespace std;
typedef struct SeqList
{int a[10];int size;
}SLT;void SeqPushBack(SLT& sl, int x)
{}typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, * PNode;// 指针变量也可以取别名,这里LTNode*& phead就是给指针变量取别名
// 这样就不需要用⼆级指针了,相对而言简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));assert(newnode);newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{//...}
}int main()
{PNode plist = NULL;ListPushBack(plist, 1);return 0;
}

10.4 const引用

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

• 不需要注意的是类似 int& rb = a * 3; double d = 12.34; int& rd = d; 这样一些场景下a * 3的结果保存在一个临时对象中, int& rd = d 也是类似,在类型转换中会产生 存储中间值的临时对象,也就是,rb和rd引用的都是临时对象,而C++规定临时对象具有常性(只能读,不能改),所以这里就触发了权限放大,必须要用const引用才可以。

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

• C++中的临时对象除了上述的 表达式求值,类型转换,还有以下两种:

     1. 函数返回值:当你调用一个函数并且它的返回类型不是引用或指针时,会生成一个临时对象来存储返回值。

int x = getSomeValue(); // getSomeValue() 返回一个新的整数,x 是对该临时对象的引用

    2. 参数传递:在函数参数列表中,如果传递的是非引用类型的值,也会创建临时对象来存储实际传入的数据。

void func(int temp) { // temp 是对一个临时变量的引用 }

func(42); // 内部创建了一个临时变量来作为func的参数

int main()
{const int a = 10;// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”// 这里的引用是对a访问权限的放⼤//int& ra = a;// 这样才可以const int& ra = a;// 编译报错:error C3892: “ra”: 不能给常量赋值//ra++;// 这里的引用是对b访问权限的缩⼩int b = 20;const int& rb = b;// 编译报错:error C3892: “rb”: 不能给常量赋值//rb++;return 0;
}
#include<iostream>using namespace std;int main()
{int a = 10;const int& ra = 30;// 编译报错: “初始化”: 无法从“int”转换为“int &”// 临时对象具有常性// int& rb = a * 3;const int& rb = a * 3;double d = 12.34;// 编译报错:“初始化”: ⽆法从“double”转换为“int &”// 临时对象具有常性// int& rd = d;const int& rd = d;return 0;
}

10.5 指针和引用的关系(面试高频考查点)

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代(如:在链表里的next,如果用引用,因为不能改变其指向,所以不能改变next,只能用指针)。

• 语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间。

• 引用在定义时必须初始化,指针可以不初始化。

• C++规定引用不能改变指向,引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。

• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。

sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)

指针很容易出现空指针和野指针的问题,引用很少出现,相对更安全一些。

11、inline

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

• inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略

• C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调 试,C++设计了inline目的就是替代C的宏函数。

• vs编译器 debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置⼀下 以下两个地方。

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

#include<iostream>using namespace std;inline int Add(int x, int y)
{int ret = x + y;ret += 1;ret += 1;ret += 1;return ret;
}
int main()
{// 可以通过汇编观察程序是否展开// 有call Add语句就是没有展开,没有就是展开了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}
// F.h
#include <iostream>using namespace std;inline void f(int i);// F.cpp
#include "F.h"void f(int i)
{cout << i << endl;
}// main.cpp
#include "F.h"int main()
{// 链接错误:无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)f(10);return 0;
}

12、nullptr

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

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

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

在C语言中,由于类型检查不严格,类型都可以转换,f(NULL),两个重载函数都可以调用,报错

在C++中,本想通过f(NULL)调用指针版本的 f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL); 调用会报错(类型检查严格)。

• C++11中引入了nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型

nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

#include<iostream>using namespace std;void f(int x)
{cout << "f(int x)" << endl;
}void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0); // f(int x)f(NULL); // f(int x)f((int*)NULL); // 强制转换 f(int* ptr)// 编译报错:error C2665: “f”: 2 个重载函数中没有一个匹配的参数类型// f((void*)NULL);f(nullptr); //f(int* ptr)return 0;
}

相关文章:

C++初阶——入门

目录 1、C发展历史 2、C版本更新 3、C参考文档 4、C书籍推荐 5、C的程序 6、命名空间 6.1 namespace的作用 6.2 namespace的定义 6.3 namespace的使用 7、C输入&输出 8、缺省参数 9、函数重载 10、引用 10.1 引用的概念和定义 10.2 引用的特性 10.3 引用的使…...

Java基于SSM微信小程序物流仓库管理系统设计与实现(源码+lw+数据库+讲解等)

选题背景 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…...

82.【C语言】数据结构之顺序表的初始化和销毁

目录 1.线性表 2.分类 1.静态顺序表&#xff1a;使用定长数组存储元素 代码示例(写入Seqlist.h中) 2.动态顺序表:使用与动态内存管理有关的函数 代码示例(写入Seqlist.h中) 补:数据管理的四个需求:增改删查 3.操作顺序表 1.初始化顺序表 1.不开辟空间 2.开辟空间 1…...

java-推荐一个控制台输出颜色ANSI字符的类

java-推荐一个控制台输出颜色ANSI字符的类 背景代码调用输出 背景 这个类是来自hive的一段代码&#xff0c;大家可以参考一下&#xff0c;这个类名是ColorBuffer 代码 /** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreem…...

关于定义结构体别名时 是否加*

在C语言中&#xff0c;使用typedef来定义结构体类型及其指针的别名时&#xff0c;Node和LinkList的声明方式有所不同&#xff0c;这是因为你对它们的目的和用途有不同的设定。 首先&#xff0c;看一下你的代码&#xff1a; typedef struct { int data; int lenght; // 注意&am…...

成语积累学习

识文断字&#xff1a;有一点文化知识 雨后春笋&#xff1a;春雨过后快速生长的竹笋&#xff1b;比喻大量涌现的新生事物 味同嚼蜡&#xff1a;如同咀嚼白蜡一样&#xff0c;毫无味道。形容文章或言辞枯燥乏味。 差强人意&#xff1a;大体上让人满意 八面玲珑&#xff1a;处…...

基于Java的茶叶商城设计与实现(源码+定制+开发)茶叶电商系统开发、茶叶电商平台开发、茶叶在线销售平台设计与开发

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

桥接、NAT和仅主机三种网络模式对虚拟机IP地址分配的影响

在虚拟机中&#xff0c;桥接、NAT和仅主机&#xff08;Host-Only&#xff09;这三种网络模式会给虚拟机带来不同的IP地址分配方式及相应的网络连接特性&#xff0c;从而产生不同的影响&#xff0c;具体如下&#xff1a; 桥接模式 IP地址分配特点&#xff1a;在桥接模式下&…...

音乐播放器-0.专栏介绍​

1.简介 本专栏使用Qt QWidget作为显示界面&#xff0c;你将会学习到以下内容&#xff1a; 1.大量ui美化的实例。 2.各种复杂ui布局。 3.常见显示效果实现。 4.大量QSS实例。 5.Qt音频播放&#xff0c;音乐歌词文件加载&#xff0c;展示。 6.播放器界面换肤。 相信学习了本专栏…...

单月变现3W!AI助力沙雕图文爆红小绿书,12篇阅读量破10万+!

最近有没有小伙伴注意到&#xff0c;在各大社交平台上&#xff0c;那些温馨治愈、搞笑沙雕的图文内容&#xff0c;能吸引大量的目光和流量&#xff0c;不久前&#xff0c;我也曾分享过这类内容&#xff0c;比如让人眼前一亮的人间清醒老奶奶&#xff0c;她的图文就属于这类流行…...

C语言复习第4章 数组

目录 一、一维数组的创建和初始化1.1数组的创建1.2 变长数组1.3 数组的初始化1.4 全局数组默认初始化为01.5 区分两种字符数组1.6 用sizeof计算数组元素个数1.7 如何访问数组元素1.8 一维数组在内存中的存储(连续存储)1.9 访问数组元素的另一种方式:指针变量1.10 数组越界是运行…...

大数据研究实训室建设方案

一、概述 本方案旨在提出一套全面的大数据研究实训室建设策略&#xff0c;旨在为学生打造一个集理论学习与实践操作于一体的高端教育环境。实训室将专注于培养学生在大数据处理、分析及应用领域的专业技能&#xff0c;通过先进的设施配置、科学的课程体系和实用的实训模式&…...

Unity3D 观察者模式

Unity3D 泛型事件系统 观察者模式 观察者模式是一种行为设计模式&#xff0c;通过订阅机制&#xff0c;可以让对象触发事件时&#xff0c;通知多个其他对象。 在游戏逻辑中&#xff0c;UI 界面通常会监听一些事件&#xff0c;当数据层发生变化时&#xff0c;通过触发事件&am…...

vue从0开始的项目搭建(含环境配置)

一、环境准备 下载node.js 检查node.js版本 替换npm下载源 1.下载node.js: Node.js — 在任何地方运行 JavaScript (nodejs.org) 2.查看版本: windowsr输入cmd进入输入node -v命令查看版本号是否出现确认是否安装 2.替换npm下载源: npm config set registry https://reg…...

力扣61~65题

题61&#xff08;中等&#xff09;&#xff1a; 分析&#xff1a; python代码&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def rot…...

API接口开发流程与指南

API&#xff08;应用程序编程接口&#xff09;是现代软件开发中不可或缺的一部分&#xff0c;它允许不同的软件应用之间进行交互和数据交换。无论是调用第三方服务、集成内部系统还是开发微服务架构&#xff0c;API都扮演着关键角色。本文将为你提供一个API接口入门的详解&…...

如何在Android中进行日志打印和调试?

在Android开发中&#xff0c;日志打印和调试是开发者定位问题、优化性能和提升应用质量的重要手段。以下将详细阐述如何在Android中进行日志打印和调试&#xff0c;包括日志工具的使用、调试技巧以及实践中的最佳实践。 一、日志工具的使用 1. Log类 Android中的日志工具类是…...

Linux基本使用和程序部署

文章目录 一. Linux背景Linux发行版 二. Linux环境搭建Linux常见命令lspwdcdtouchcatmkdirrmcpmvtailvimgreppsnetstat管道 三. 搭建java部署环境安装jdk安装mysql部署Web项目到Linux 一. Linux背景 1969−1970年,⻉尔实验室的DennisRitchie和KenTompson开发了Unix操作系统. 他…...

照片编辑成动态视频用什么软件好

在数字时代&#xff0c;让照片动起来确实已成为一种流行的潮流和趋势。如今&#xff0c;市面上涌现出众多软件&#xff0c;它们不仅配备了丰富多样的动态效果和特效&#xff0c;还支持用户进行个性化的编辑和创作。无论你是希望将家庭合影转化为充满温情的动画&#xff0c;还是…...

JavaWeb合集-SpringBoot项目配套知识

四、SpringBoot项目配套知识 1、Springboot项目的创建 2、HTTP 概念: Hyper Text Transfer Protocol,超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 2.1 request 请求协议 浏览器向服务器发送请求的规则&#xff08;get、post等&#xff09;。 2.1.1 请…...

Electron入门笔记

Electron入门笔记 ElectronElectron 是什么Electron流程模型创建第一个Electron项目配置自动重启主进程和渲染进程通信打包应用 Electron Electron 是什么 跨平台的桌面应用开发框架使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium和 Node.js Electro…...

python 不相交集简介(并查集算法)【Introduction to Disjoint Set (Union-Find Algorithm)】

什么是不相交集数据结构&#xff1f; 如果两个集合没有任何共同元素&#xff0c;则它们被称为不相交集&#xff0c;集合的交集为空集。 存储不重叠或不相交元素子集的数据结构称为不相交集合数据结构。不相交集合数据结构支持以下操作&#xff1a; 1、将新集合添加到不相交集合…...

23种设计模式之工厂方法模式

文章目录 1. 简介2. 代码2.1 抽象类&#xff1a;Course.java2.2 产品A&#xff1a;JavaCourse.java2.3 产品B&#xff1a;PythonCourse.java2.4 工厂抽象类&#xff1a;CourseFactory.java2.5 产品A的工厂A&#xff1a;JavaCourseFactory.java2.6 产品B的工厂B&#xff1a;PyCo…...

Redis——事务

文章目录 Redis 事务Redis 的事务和 MySQL 事务的区别:事务操作MULTIEXECDISCARDWATCHUNWATCHwatch的实现原理 总结 Redis 事务 什么是事务 Redis 的事务和 MySQL 的事务 概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行 Redis 的事务和 MySQL 事务的区别:…...

Redis非关系型数据库操作命令大全

以下是 Redis 的常用操作命令大全&#xff0c;涵盖了键值操作、字符串、哈希、列表、集合、有序集合、发布/订阅、事务等多个方面的操作。 1. 通用键命令 命令说明SET key value设置指定 key 的值GET key获取指定 key 的值DEL key删除指定的 keyEXISTS key检查 key 是否存在E…...

基于SpringBoot+Vue+uniapp微信小程序的澡堂预订的微信小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…...

Linux mips架构链接库函数调用plt表汇编代码分析

linux调用共享库中的函数时通过plt表和got表实现位置无关代码&#xff0c;过程中涉及到lazy binding&#xff0c;即在第一调用外部函数时解析被调用的函数地址并将地址写入到got表&#xff0c;后续调用则不需要解析函数地址。这一部分和硬件架构有关&#xff0c;具体的是和cpu指…...

python 作业1

任务1: python为主的工作是很少的 学习的python的优势在于制作工具&#xff0c;制作合适的工具可以提高我们在工作中的工作效率的工具 提高我们的竞争优势。 任务2: 不换行 换行 任务3: 安装pycharm 进入相应网站Download PyCharm: The Python IDE for data science and we…...

Apache 出现 “403 forbidden“ 排查方法

1、检查运行 Apache 进程的用户没有对目录具备读取权限 如果该用户没有对 Directory 指定的目录具备适当的读取权限&#xff0c;就会导致 403 错误。 ​​例如&#xff1a;使用用户apache启动Apache进程&#xff0c;但是apache用户对 Directory 指定的目录没有读取权限 2、检查…...

vue video播放m3u8监控视频

很关键的问题 vite创建的项目不需要import ‘videojs-contrib-hls’ 导入就报错 直接添加如下代码即可 html5: {vhs: {overrideNative: true},nativeVideoTracks: false,nativeAudioTracks: false,nativeTextTracks: false} 下面是完整组件示例 <template><div>…...