第一讲 | 解锁C++编程能力:基础语法解析

C++入门基础

  • 一、C++的第一个程序
  • 二、命名空间
  • 三、C++输入&输出
  • 四、缺省参数/默认参数
  • 五、函数重载
  • 六、引用
    • 1.引用的特性
    • 2.引用的使用
      • 引用做返回值场景
    • 3.const引用
      • 只有指针和引用涉及权限放大、缩小的问题,普通变量没有
    • 4.指针和引用的关系
  • 七、inline
  • 八、nullptr

一、C++的第一个程序

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

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

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

//test.cpp
#include <iostream>
using namespace std;
int main()
{cout << "hello world!\n" << endl;return 0;
}

二、命名空间

  1. 命名空间的定义:需要用到关键字namespace,后面加上命名空间的名字,一般开发中是用项目名字做命名空间名,然后加上一对{}即可,{}里面是命名空间的成员,命名空间中可以定义函数/类型/变量等。

namespace的价值:

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

C++引入namespace就是为了更好的解决C语言项目类似下面程序这样的命名冲突的问题。

//test.cpp
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{printf("%d\n", rand);return 0;
}

在这里插入图片描述

报错原因:全局变量rand和头文件<stdlib.h>中的rand()函数命名冲突

  1. namespace本质:定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量、函数、类,所以下面的rand不在冲突了。

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

  • 编译器编译查找逻辑/规则前提:编译中任一变量/函数都要能找到它们的声明和定义,说明出处(在哪一个域),否则编译器会报错。

  • 编译器编译时语法查找一个变量/函数/类型出处(声明或定义)的查找逻辑/规则

  1. 先局部再全局
  2. 指定域查找
  1. namespace只能定义在全局,当然他还可以嵌套定义。

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

  3. 为了防止发生命名冲突,C++标准库都放在一个叫std(standard)的命名空间中。

  4. 编译查找一个变量的声明/定义时,默认只会在函数局部或者全局查找(先局部再全局),不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
    指定命名空间访问,项目中推荐这种方式。(费劲,安全)
    using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。(折中方法)
    using展开命名空间中全部成员,也就是说不需要指定命名空间了,相当于把命名空间里的全部成员扔到全局里,或者理解为先到函数局部找,再到全局找,最后到命名空间里找。这时候若全局域里定义了一个同名变量/函数/类型,会有命名冲突的风险。项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。(不费劲,不安全)

域作用限定符/作用域操作符::

  1. 左边什么都不加,找全局
  2. 命名空间名字::命名空间中的成员

展开命名空间 ≠ 展开头文件

  1. 展开命名空间:影响编译器编译查找规则会去命名空间里找
  2. 展开头文件:将头文件里的内容copy过来。例如在源文件中包含头文件"xxxx.h",头文件里的内容会在这个源文件中展开。
#include <stdio.h>
//不同的域可以定义同名变量
int a = 0;
int main()
{int a = 1;//编译器编译查找规则,先局部再全局printf("%d\n", a);//::左边什么都不加,找全局printf("%d\n", ::a);return 0;
}

在这里插入图片描述


#include <stdio.h>
#include <stdlib.h>
// 1. 命名空间的定义,不同的域可以定义同名变量
namespace project
{//命名空间中可以定义变量/函数/类型int rand = 10;int ADD(int x, int y){return x + y;}struct Node {int data;struct Node* next;};
}
int main()
{//1.先局部再全局//默认访问的是全局的rand函数指针/地址printf("%p\n", rand);//2.指定域查找//指定project命名空间中的randprintf("%d\n", project::rand);return 0;
}

在这里插入图片描述

#include <stdio.h>
// 2. 命名空间可以嵌套定义
namespace pj
{int rand = 1;int ADD(int left, int right){return left + right;}namespace pj1{int rand = 2;int ADD(int left, int right){//若是这个函数里定义了变量,这个变量是局部变量,命名空间域影响的是这个函数, 不影响这个函数里的变量return (left + right) / 10;}}namespace pj2{int rand = 3;int ADD(int left, int right){return (left + right) * 10;}}
}
int main()
{printf("%d\n", pj::rand);printf("%d\n", pj::ADD(1, 2));printf("%d\n", pj::pj1::rand);printf("%d\n", pj::pj1::ADD(4, 5));printf("%d\n", pj::pj2::rand);printf("%d\n", pj::pj2::ADD(4, 5));return 0;
}

在这里插入图片描述

// 3. 项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{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 bit
{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++;}//...
}// Queue.h
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{typedef int QDataType;typedef struct QueueNode{int val;struct QueueNode* next;}QNode;typedef struct Queue{QNode* phead;QNode* ptail;int size;}Queue;void QueueInit(Queue* pq);void QueueDestroy(Queue* pq);// ⼊队列void QueuePush(Queue* pq, QDataType x);// 出队列void QueuePop(Queue* pq);QDataType QueueFront(Queue* pq);QDataType QueueBack(Queue* pq);bool QueueEmpty(Queue* pq);int QueueSize(Queue* pq);
}//Queue.cpp
#include"Queue.h"
namespace bit
{void QueueInit(Queue* pq){assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;}// ...
}//test.cpp
#include"Queue.h"
#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));// 调⽤bit namespace的bit::ST st2;printf("%d\n", sizeof(st2));bit::STInit(&st2, 10);bit::STPush(&st2, 1);bit::STPush(&st2, 2);return 0;
}
// 4. 默认只会在函数局部或者全局查找(先局部再全局),不会到命名空间里面去查找
#include <stdio.h>
namespace N
{int a = 1;int b = 2;
}
int main()
{//编译报错:C2065“a”: 未声明的标识符printf("%d\n", a);return 0;
}
// 5. 指定命名空间访问
#include <stdio.h>
namespace N
{int a = 1;int b = 2;
}
int main()
{printf("%d\n", N::a);return 0;
}

在这里插入图片描述

// 6. using将命名空间中某个成员展开
#include <stdio.h>
namespace N
{int a = 1;int b = 2;
}
using N::b;
int main()
{printf("%d\n", N::a);printf("%d\n", b);return 0;
}
// 7. 展开命名空间中全部成员
#include <stdio.h>
namespace N
{int a = 1;int b = 2;
}
using namespace N;
int main()
{printf("%d\n", a);printf("%d\n", b);return 0;
}

三、C++输入&输出

  1. <iostream>是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象

  2. std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流,本质是转成字符串输出。cin、cout分别支持连续的输入、输出。

  3. std::endl 是一个函数 end line,流插入输出时,相当于插入一个换行字符’\n’加刷新缓冲区。

  4. <<是流插入运算符,>>是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)流插入的意义:对象流到流对象cout里。

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

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

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

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

  9. 这里我们没有包含<stdio.h>,也可以使用printf和scanf,在包含<iostream>间接包含了。vs系列编译器是这样的,其他编译器可能会报错。


#include <iostream>
int main()
{std::cout << "hello world!\n";int a = 0;double b = 0.1;char c = 'x';std::cout << a << std::endl;std::cout << b << std::endl;std::cout << c << std::endl;return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';//endl << 后面可以一直加东西cout << a << " " << b << " " << c << endl << a << endl << b;//可以自动识别变量的类型cin >> a;cin >> b >> c;cout << a << endl << " " << b << endl << c << endl;return 0;
}

多次使用cincout,效率比C里的输入输出低,以下写法可以提高C++IO效率:

#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. 缺省参数:声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省参数和半缺省参数。(有些地方把缺省参数也叫默认参数)

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

//反例
void Func1(int a, int b = 10, int c)
{//...
}
void Func2(int a = 10, int b, int c)
{//...
}
  1. 调用带缺省参数的函数,C++规定必须从左到右依次给实参,不能跳跃给实参。

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


#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)
{cout << a << endl;
}
int main()
{Func(); // 没有传参时,使⽤参数的默认值Func(10); // 传参时,使⽤指定的实参return 0;
}
//C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
//带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
#include <iostream>
using namespace std;
//全缺省
void Func1(int a = 10, int b = 20, double c = 2.3)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}
//半缺省
void Func2(int a, int b = 5, int c = 56)
{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;
}

五、函数重载

C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同(重载条件),可以是参数个数不同或者参数类型不同(返回值不同不能作为重载条件,因为调用时也无法区分)。这样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 << endl;return left + right;
}
// 2、参数个数不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl << 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;
}
int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}

在这里插入图片描述

//返回值不同不能作为重载条件,因为调⽤时也⽆法区分
void fxx()
{}int fxx()
{return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;
// 下⾯两个函数构成重载
// f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁
void f1()
{cout << "f()" << endl;
}
void f1(int a = 10)
{cout << "f(int a)" << endl;
}int main()
{f1();return 0;
}

在这里插入图片描述

六、引用

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

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

C++中为了避免引入太多的运算符,会复用C语言的一些符号,比如前面的<< 和 >>,这里引用也和取地址使用了同一个符号&,大家注意使用方法角度区分就可以。

#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 = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;cout << "d = " << d << endl << endl;cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

在这里插入图片描述
在这里插入图片描述

1.引用的特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体,即引用初始化后不能改变指向
#include<iostream>
using namespace std;
int main()
{int a = 10;// 编译报错:“ra”: 必须初始化引⽤//int& ra;int& b = a;int c = 20;// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,// 这⾥是⼀个赋值b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}

在这里插入图片描述

2.引用的使用

  1. 引用在实践中主要是于引用传参引用做返回值中减少拷贝提高效率和改变引用别名时同时改变引用对象。
  2. 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
  3. 引用做返回值的场景相对比较复杂,我们在这里简单讲了一下场景,还有一些内容后续类和对象章节中会继续深入讲解。
  4. 引用和指针在实践中相辅相成,功能有重叠性(例如,形参的改变影响实参),但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。
  5. 一些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,目的是简化程序,避开复杂的指针。

//引用和指针在大部分功能上是有重叠的部分的,例如,形参的改变可以影响实参
//引用,形参就是实参的别名
#include <iostream>
using namespace std;
void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}
void Swap(int* px, int* py)
{int tmp = *px;*px = *py;*py = tmp;
}
int main()
{int x = 0, y = 1;cout << x << " " << y << endl << endl;Swap(x, y);cout << x << " " << y << endl;x = 0, y = 1;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;
}
int main()
{ST st;STInit(st);return 0;
}

//使用引用的基本场景
//大部分书籍上还有这样写的
//单链表的尾插函数参数是二级指针,用引用的话会降低理解难度,避开二级指针
#include <stdio.h>
#include <stdlib.h>
typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, *PNode;//相当于
//typedef struct ListNode LTNode;
//typedef struct ListNode* PNode;void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{//...}
}
int main()
{PNode plist = NULL;ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);return 0;
}

PNode是结构体指针类型struct ListNode*的别名,LTNode是结构体类型struct ListNode的别名。等价于typedef struct ListNode LTNode;typedef struct ListNode* PNode;

有typedef,定义的是类型;没有typedef,定义的是变量,LTNode是结构体变量,PNode是结构体指针变量:

struct ListNode {int val;struct ListNode* next;
}LTNode, *PNode;

C++的引用和C里的指针是相辅相成的,前面的示例中引用替代了指针,但是以下场景引用就替代不了。

定义链表的结构时,不能去引用下一个结点,必须用指针。例如,将其中一个结点销毁了,引用的指向就改变了。其次,引用必须初始化,也没有空引用的概念,尾结点指向空,实现不了空引用。像数据结构中的链表、树就不能用引用。
在这里插入图片描述


引用理论上是没有空间的开销的,但是引用的底层是指针,实际上是有空间开销的,这个在以后的学习中会讲解。


引用做返回值场景

#include <iostream>
#include <assert.h>
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)
{assert(rs.top > 0);return rs.a[rs.top - 1];
}
int main()
{ST st;STInit(st);STPush(st, 1);STPush(st, 2);STPush(st, 3);STPush(st, 4);cout << STTop(st) << endl;STTop(st) += 10;cout << STTop(st) << endl;return 0;
}

在理解传引用返回之前先理解传值返回,传值返回编译会报错,传值返回不是将这个值进行返回,而是在这儿拷贝生成一个临时对象,用这个临时对象去做下面表达式的返回值,但是这个临时对象具有常性(也就是一个右值),和const一样,不能被修改。

传引用返回的返回对象是返回对象的别名,在这里直接就是栈中的对象,传引用返回本质就是可以修改返回对象。

总的来说,传值返回与传引用返回的区别:传值返回返回的是返回对象的拷贝,传引用返回返回的是返回对象的别名。

在这里插入图片描述

左值、右值都是一个表达式,可以取地址的就是左值,不能取地址的就是右值。下面的a、ptr就是一个表达式,叫做变量表达式,所以变量也是一个表达式。a、ptr、*ptr都是左值,都可以取地址。常见的字面量、常量、临时对象、匿名对象都是右值。大多情况下,左值能修改,右值不能被修改。

在这里插入图片描述

不是任何场景下都能用引用值返回的,例如:野引用,不过大多数不会报错,但是这会很危险。

在这里插入图片描述
ret就不在了,func(1, 2)就成为了野引用。

下面也一样:
在这里插入图片描述

func()、STTop()两个函数为什么会有这样的区别呢?

STTop()函数结束,函数栈帧销毁,返回对象不在STTop()栈帧中,因为数组这块空间是malloc出来的,数组中的数据是在堆上,所以返回它的引用没问题。
func()函数结束,函数栈帧销毁,返回对象在func()栈帧中,返回它的引用有问题。


3.const引用

必须const引用一个const对象,下面的a是const int类型。const引用也可以引用普通对象,还可以直接const引用字面量常量,因为对对象的访问权限在引用过程中可以缩小,但是不能放大。

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

临时对象:编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象,C++中把这个未命名的对象叫做临时对象。(4/8个字节的小值暂存在寄存器里,大一点的值在内存中找一个区域存储起来)

哪些场景会出现临时对象呢?

  1. 函数传值返回
  2. 表达式运算,如a + b,表达式结果在临时对象里
  3. 类型转换

int main()
{
可以引用一个const对象,但是必须用const引用。const int a = 10;// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”// 这⾥的引⽤是对a访问权限的放⼤//int& ra = a;// 这样才可以const int& ra = a;// 编译报错:error C3892: “ra”: 不能给常量赋值// const限制变量不能被修改//ra++;//a++;const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。// 这⾥的引⽤是对b访问权限的缩⼩int b = 20;const int& rb = b;// 编译报错:error C3892: “rb”: 不能给常量赋值//rb++;//rb只能读不能修改b++;//但是对b没有影响还可以直接const引用字面量常量const int& rc = 30;return 0;
}

//对a、字面量访问权限放大的示例及改进
//void func(int& x)
//{
//
//}
//改进:
void func(const int& rx)
{}
int main()
{const int a = 20;func(a);func(10);return 0;
}

在这里插入图片描述

//rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。
int main()
{int a = 10;//int& ra = a * 3;const int& ra = a * 3;double d = 12.34;//int& rd = d;const int& rd = d;return 0;
}

只有指针和引用涉及权限放大、缩小的问题,普通变量没有

在这里插入图片描述

4.指针和引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅成,功能有重叠性,但是各有自己的特点,互相不可替代。

语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间。(底层实现上引用是开空间的,重点理解语法概念,底层作为了解)

类比鱼香肉丝:

  1. 语法概念:名字表达
  2. 底层:实际做这个食物,鱼香肉丝没有鱼

引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。

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

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

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

指针很容易出现空指针和野指针的问题,引用很少出现(不代表没有空引用、野引用),引用使用起来相对更安全一些。

七、inline

用inline修饰的函数叫做内联函数。但是inline对于编译器而言只是一个建议,编译时这个函数是否能成为内联函数取决于C++编译器,成为内联函数的话,编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。

使用inline关键字的函数会被编译器在调用处展开?不一定,因为inline只是一种建议,需要看此函数是否能够成为内联函数。

头文件中可以包含inline函数的声明?inline函数不支持声明和定义分离开,因为编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的。

可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数?inline函数会在调用的地方展开,所以符号表中不会有inline函数的符号名,不存在链接冲突。

递归函数也都可以成为inline函数?比较长的函数、递归函数就算定义为inline,也会被编译器忽略,故错误。

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

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

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

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

八、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)调用指针版本的f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。C里本想通过f(NULL)调用指针版本的f(int*)函数,但是NULL被定义成((void *)0),没法匹配给整型指针类型的指针,调用都会报错。

解决办法:在C++里nullptr就可以解决上述缺陷。

C++11中引入nullptr,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(int)f(0);// C++里本想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f(NULL);// 强制转换也行,但就是怪f((int*)NULL);// 编译报错:error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型// f((void*)NULL);//解决办法:nullptr,只能被隐式地转换为指针类型,而不能被转换为整数类型f(nullptr);return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39710.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【颠覆性缓存架构】Caffeine双引擎缓存实战:CPU和内存双优化,命中率提升到92%,内存减少75%

千万级QPS验证&#xff01;Caffeine智能双缓存实现 92%命中率&#xff0c;内存减少75% 摘要&#xff1a; 本文揭秘千万级流量场景下的缓存革命性方案&#xff01;基于Caffeine打造智能双模式缓存系统&#xff0c;通过冷热数据分离存储与精准资源分配策略&#xff0c;实现CPU利…

JVM 03

今天是2025/03/24 15:21 day 11 总路线请移步主页Java大纲相关文章 今天进行JVM 5,6 个模块的归纳 首先是JVM的相关内容概括的思维导图 5. 优化技术 JVM通过多种优化技术提升程序执行效率&#xff0c;核心围绕热点代码检测和编译优化实现动态性能提升。 热点代码检测 JVM…

wordpress-网站百宝箱插件

含置顶,网页宠物, 哀悼, 禁止复制, 禁止查看源码, 弹幕, WP优化,媒体分类,预加载,定时发布,在线客服, 留言板, 手机客服, 网站背景, 公告, 跑马灯, 水印, 分享, 打赏, 海报图, 广告,数据库管理,图片加载特效。等综合功能插件

Git 钩子:特定操作脚本

Git 钩子 在特定 Git 操作发生时自动触发的脚本&#xff1b; 可以从提交规范、代码质量、自动化流程、分支管理、安全性检查等多个方面进行配置&#xff0c;帮助团队提高开发效率和代码质量&#xff1b; 本地 记录提交检验 commit-msg 修改&#xff1a;\test\.git\hooks\c…

职坐标:互联网行业职业发展路径解析

内容概要 当前&#xff0c;互联网行业正以指数级速度重塑全球产业格局。数据显示&#xff0c;我国互联网市场规模在2019年上半年实现17.9%的同比增速&#xff0c;而随着工业互联网、5G等前沿技术的加速落地&#xff0c;这一增长趋势仍在强化。工信部近期发布的《新型信息基础设…

红数码影视(RED Digital Cinema)存储卡格式化后的恢复方法

红数码影视(RED Digital Cinema)的摄像机可以生成两种RAW级高清视频文件&#xff0c;一种是R3D&#xff0c;一种是MOV。其中MOV属于苹果(apple)公司的QT视频封装结构&#xff0c;使用的视频编码是Apple ProRes;而R3D则是RED公司自创的RAW视频文件&#xff0c;这种文件解码需要使…

Gitee上库常用git命令

Gitee上库常用git命令 1、Fork 项目2、个人仓库修改3、追加提交4、创建PR5、多笔commit合一 1、Fork 项目 2、个人仓库修改 git add . // -s 表示自动添加邮箱签名信息&#xff0c;-m表示其后跟随commit描述 git commit -sm “add transition freeze” git push origin [目标…

阿里开源的免费数据集成工具——DataX

企业里真实的数据流转是什么样子的呢&#xff1f; 左侧描述了一个企业真实的样子&#xff0c;我们总是需要把数据从一个地方搬到另一个地方&#xff0c;最后就是搬来搬去搬成了一张张解不开的网。 右侧则表达了使用DataX为中心实现数据的同步。 什么是DataX DataX是一个异构…

SpringBoot学习笔记(主)

文章目录 SpringBoot概述自动装配&#xff08;部分&#xff09;概述原理简述相关解释源码位置EnableAutoConfigurationAutoConfigurationImportSelector 配置文件yaml语法单双引号列表多行字符串 配置文件的位置和加载顺序配置文件取值运行jar包 Springboot整合springmvc自动管…

python多线程和多进程的区别有哪些

python多线程和多进程的区别有七种&#xff1a; 1、多线程可以共享全局变量&#xff0c;多进程不能。 2、多线程中&#xff0c;所有子线程的进程号相同&#xff1b;多进程中&#xff0c;不同的子进程进程号不同。 3、线程共享内存空间&#xff1b;进程的内存是独立的。 4、同一…

docker 安装部署 canal

1 mysql 安装 1.1 拉取镜像 docker pull mysql:8.4.41.2 创建挂载目录 mkdir -p /user/lzl/tool/docker/mysql/mysql_8.4.4/home/confmkdir -p /user/lzl/tool/docker/mysql/mysql_8.4.4/home/datamkdir -p /user/lzl/tool/docker/mysql/mysql_8.4.4/home/log1.3 编辑配置文…

基于SpringBoot的图书借阅小程序+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

ElasticSearch快速入门--实现分词搜索

分词题目搜索 使用Elasticsearch实现题目数据的存储和分词搜索&#xff0c;需要将数据库的数据同步到 Elasticsearch。 ElasticSearch入门 ElasticSearch&#xff08;简称ES&#xff09;是一个开源的分布式搜索和数据分析引擎&#xff0c;用Java开发并且是当前最流行的开源的…

debug - 安装.msi时,为所有用户安装程序

文章目录 debug - 安装.msi时&#xff0c;为所有用户安装程序概述笔记试试在目标.msi后面直接加参数的测试 备注备注END debug - 安装.msi时&#xff0c;为所有用户安装程序 概述 为了测试&#xff0c;装了一个test.msi. 安装时&#xff0c;只有安装路径的选择&#xff0c;没…

Skyeye 云智能制造办公系统 VUE 版本 v3.15.14 发布

Skyeye 云智能制造&#xff0c;采用 Springboot winUI 的低代码平台、移动端采用 UNI-APP。包含 30 多个应用模块、50 多种电子流程&#xff0c;CRM、PM、ERP、MES、ADM、EHR、笔记、知识库、项目、门店、商城、财务、多班次考勤、薪资、招聘、云售后、论坛、公告、问卷、报表…

深度学习PyTorch之动态计算图可视化 - 使用 torchviz 生成计算图

序号系列文章1深度学习训练中GPU内存管理2深度学习PyTorch之数据加载DataLoader3深度学习 PyTorch 中 18 种数据增强策略与实现4深度学习pytorch之简单方法自定义9类卷积即插即用5深度学习PyTorch之13种模型精度评估公式及调用方法6深度学习pytorch之4种归一化方法&#xff08;…

ZW3D二次开发_非模板表单_输入框类控件_逐字符回调

ZW3D的非模板表单的控件中有一些输入框类的控件&#xff0c;比如“ZsCc::LineEditBtn”,"ZsCc::LineEditEx"等&#xff0c;按照“ZW3D二次开发_非模板表单_控件_添加回调-CSDN博客”介绍的方法添加函数命令时&#xff0c;发现输入框在用户输入字符时不能动态地触发回…

Mysql--日志(错误日志、二进制日志、查询日志、慢查询日志)

四种日志对比总结 日志类型作用记录内容特点常见用途错误日志记录 MySQL 运行过程中的错误、警告及启动、关闭信息MySQL 系统错误、故障信息、警告等较少占用磁盘空间故障排查、系统监控二进制日志记录所有更改数据库数据的操作及事务执行情况DML、DDL 操作&#xff0c;不记录…

AI对软件工程(software engineering)的影响在哪些方面?

AI对软件工程&#xff08;software engineering&#xff09;的影响是全方位且深远的&#xff0c;它不仅改变了传统开发流程&#xff0c;还重新定义了工程师的角色和软件系统的构建方式。以下是AI影响软件工程的核心维度&#xff1a; 一、开发流程的智能化重构 需求工程革命 • …

ElementPlus 快速入门

目录 前言 为什么要学习 ElementPlus&#xff1f; 正文 步骤 1 创建 一个工程化的vue 项目 ​2 安装 element-Plus :Form 表单 | Element Plus 1 点击 当前界面的指南 2 点击左边菜单栏上的安装&#xff0c;选择包管理器 3 运行该命令 demo(案例1 &#xff09; 步骤 …