解锁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;
}
二、命名空间
- 命名空间的定义:需要用到关键字
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()函数命名冲突
-
namespace
本质:定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量、函数、类,所以下面的rand不在冲突了。 -
C++中的域:函数局部域,全局域,命名空间域,类域。域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,也就是说,所有域都影响编译器查找规则。所以有了域隔离,名字冲突就解决了。函数局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
编译器编译查找逻辑/规则前提:编译中任一变量/函数都要能找到它们的声明和定义,说明出处(在哪一个域),否则编译器会报错。
编译器编译时语法查找一个变量/函数/类型出处(声明或定义)的查找逻辑/规则:
- 先局部再全局
- 指定域查找
-
namespace
只能定义在全局,当然他还可以嵌套定义。 -
项目工程中多文件中定义的同名
namespace
会认为是一个namespace
,不会冲突。 -
为了防止发生命名冲突,C++标准库都放在一个叫
std
(standard)的命名空间中。 -
编译查找一个变量的声明/定义时,默认只会在函数局部或者全局查找(先局部再全局),不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
• 指定命名空间访问,项目中推荐这种方式。(费劲,安全)
•using
将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。(折中方法)
•using
展开命名空间中全部成员,也就是说不需要指定命名空间了,相当于把命名空间里的全部成员扔到全局里,或者理解为先到函数局部找,再到全局找,最后到命名空间里找。这时候若全局域里定义了一个同名变量/函数/类型,会有命名冲突的风险。项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。(不费劲,不安全)
域作用限定符/作用域操作符
::
- 左边什么都不加,找全局
- 命名空间名字
::
命名空间中的成员
展开命名空间 ≠ 展开头文件
- 展开命名空间:影响编译器编译查找规则会去命名空间里找
- 展开头文件:将头文件里的内容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++输入&输出
-
<iostream>
是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。 -
std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流,本质是转成字符串输出。cin、cout分别支持连续的输入、输出。
-
std::endl
是一个函数 end line,流插入输出时,相当于插入一个换行字符’\n’加刷新缓冲区。 -
<<
是流插入运算符,>>
是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)流插入的意义:对象流到流对象cout里。 -
使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的支持自定义类型对象的输入输出(C语言里printf/scanf支持输入输出内置类型对象,但不支持输入输出自定义类型对象。C++里cin/cout支持输入输出任意类型的对象)。
-
IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,后面我们会有专门的一个章节来细讲IO流库。( IO流:自动识别变量的类型)
-
cout/cin/endl
等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式(3种)去用他们。 -
一般日常练习中我们可以
using namespace std
,实际项目开发中不建议using namespace std。 -
这里我们没有包含
<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;
}
多次使用cin
、cout
,效率比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;
}
四、缺省参数/默认参数
-
缺省参数:声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省参数和半缺省参数。(有些地方把缺省参数也叫默认参数)
-
全缺省就是给全部形参缺省值,半缺省就是给部分(可不是给一半)形参缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
//反例
void Func1(int a, int b = 10, int c)
{//...
}
void Func2(int a = 10, int b, int c)
{//...
}
-
调用带缺省参数的函数,C++规定必须从左到右依次给实参,不能跳跃给实参。
-
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#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.引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体,即引用初始化后不能改变指向
#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.引用的使用
- 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用别名时同时改变引用对象。
- 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
- 引用做返回值的场景相对比较复杂,我们在这里简单讲了一下场景,还有一些内容后续类和对象章节中会继续深入讲解。
- 引用和指针在实践中相辅相成,功能有重叠性(例如,形参的改变影响实参),但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。
- 一些主要用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个字节的小值暂存在寄存器里,大一点的值在内存中找一个区域存储起来)
哪些场景会出现临时对象呢?
- 函数传值返回
- 表达式运算,如a + b,表达式结果在临时对象里
- 类型转换
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++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅成,功能有重叠性,但是各有自己的特点,互相不可替代。
语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间。(底层实现上引用是开空间的,重点理解语法概念,底层作为了解)
类比鱼香肉丝:
- 语法概念:名字表达
- 底层:实际做这个食物,鱼香肉丝没有鱼
引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
指针很容易出现空指针和野指针的问题,引用很少出现(不代表没有空引用、野引用),引用使用起来相对更安全一些。
七、inline
用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
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;
}
相关文章:
解锁C++编程能力:基础语法解析
C入门基础 一、C的第一个程序二、命名空间三、C输入&输出四、缺省参数/默认参数五、函数重载六、引用1.引用的特性2.引用的使用引用做返回值场景 3.const引用只有指针和引用涉及权限放大、缩小的问题,普通变量没有 4.指针和引用的关系 七、inline八、nullptr 一…...
【Leetcode 每日一题】2680. 最大或值
问题背景 给你一个下标从 0 0 0 开始长度为 n n n 的整数数组 n u m s nums nums 和一个整数 k k k。每一次操作中,你可以选择一个数并将它乘 2 2 2。 你最多可以进行 k k k 次操作,请你返回 n u m s [ 0 ] ∣ n u m s [ 1 ] ∣ . . . ∣ n u m …...
YOLO魔改之SAM空间注意力模块
基于SAM注意力的YOLOv7改进算法详解(可用于工业检测方案) 一、应用场景说明 本改进算法适用于以下工业检测场景: 复杂背景下的微小目标检测(电子元件缺陷、PCB板焊点)密集目标重叠检测(传送带上的包裹分拣、人群计数)动态环境目标追踪(无人机巡检、自动驾…...
基于 TRIZ 理论的筏式养殖吊笼清洗装备设计研究
基于 TRIZ 理论的筏式养殖吊笼清洗装备设计研究 一、引言 筏式养殖在水产养殖业中占据重要地位,吊笼作为养殖贝类、藻类等生物的关键器具,其清洁程度直接影响养殖生物的健康与产量。传统的吊笼清洗方式多依赖人工,效率低下、劳动强度大且清洗…...
Day11 动态规划入门
动态规划 就是 : 给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后把子问题的答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法. 记忆化搜索 暴力dfs 记录答案 动态规划入门思…...
配置阿里云yum源
配置阿里云yum源 修改默认的yum仓库,把原有的移动到创建的目录里(踢出国外的yum源) # 切换到/ect/yum.repos.d/目录下 cd /etc/yum.repos.d/ # 新建repo目录 mkdir repo # 把原有的移动到创建的目录里 mv ./*.repo ./repo/配置yum源 # 找到…...
在Linux系统安装Ollama两种方法:自动安装和手动安装,并配置自启动服务
目录 一、命令自动安装 (一)使用命令行安装 (二)配置环境变量 (三)重新加载systemd配置并重启服务 二、手动安装 (一)下载本地文件 (二)解压并安…...
Python Django入门(创建应用程序)
在本章中,你将学习如何使用 Django(http://djangoproject.com/ )来开发一个名为“学习笔记”(Learning Log)的项目,这是一个在线日志系统,让你能够记录所学习的有关特定主题的知识。 我们将为这…...
HCIP-2 RSTP快速生成树
HCIP-2 RSTP快速生成树 STP的不足: 1.STP的端口角色过于简单不丰富,部署时不能很好的应用与较为复杂的网络环境中。 2.STP的迁移状态过于冗长,侦听、学习、阻塞状态下都是不转发业务流量。 3.STP的算法较为繁琐。 TCN TCA TC。 4.STP被动…...
软考-软件设计师-计算机网络
一、七层模型 中继器:信号会随着距离的增加而逐渐衰减,中继器可以接受一端的信息再将其原封不动的发给另一端,起到延长传输距离的作用; 集线器:多端口的中继器,所有端口公用一个冲突域; 网桥&…...
夸克网盘任务脚本——进阶自动版
脚本是用于自动管理和更新夸克云盘(Quark Cloud Drive)上的文件和目录的Python脚本。其主要功能包括自动下载、更新、重命名、删除文件和文件夹,以及处理和发送通知,可以在特定的时间间隔内运行,根据配置文件进行操作。 主要功能 1. Quark 类: __init__:初始化类,设置…...
squirrel语言全面介绍
Squirrel 是一种较新的程序设计语言,由意大利人 Alberto Demichelis 开发,其设计目标是成为一个强大的脚本工具,适用于游戏等对大小、内存带宽和实时性有要求的应用程序。以下是对 Squirrel 语言的全面介绍: 语言特性 动态类型&a…...
北京南文观点:品牌如何抢占AI 认知的 “黄金节点“
在算法主导的信息洪流中,品牌正在经历一场隐蔽的认知权争夺战,当用户向ChatGPT咨询"哪家新能源车企技术最可靠"时,AI调取的知识图谱数据源将直接决定品牌认知排序。南文乐园科技文化(北京)有限公司ÿ…...
使用Python在Word中创建、读取和删除列表 - 详解
目录 工具与设置 Python在Word中创建列表 使用默认样式创建有序(编号)列表 使用默认样式创建无序(项目符号)列表 创建多级列表 使用自定义样式创建列表 Python读取Word中的列表 Python从Word中删除列表 在Word中ÿ…...
分布式中间件:RabbitMQ确认消费机制
分布式中间件:RabbitMQ确认消费机制 在分布式系统中,消息队列是实现异步通信和系统解耦的重要组件。RabbitMQ 作为一款功能强大的消息队列中间件,提供了丰富的特性来保证消息的可靠传输和消费。其中,确认消费机制是确保消息被正确…...
Redis的大Key问题如何解决?
大家好,我是锋哥。今天分享关于【Redis的大Key问题如何解决?】面试题。希望对大家有帮助; Redis的大Key问题如何解决? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis的大Key问题指的是存储在Redis中的某些键(Key…...
日语学习-日语知识点小记-构建基础-JLPT-N4N5阶段(25):解释说明:という
日语学习-日语知识点小记-构建基础-JLPT-N4&N5阶段(25):解释说明:という 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)复习语法(2) 解释说明:という3、单词(1)日语单词(2)日语片假名单词4、相近词辨析5、单词辨析记录6、总结1、前言 (1)情况说明 …...
Windows10配置OpenJDK11
下载 # 华为OpenJDK镜像源 https://mirrors.huaweicloud.com/openjdk/11.0.2/解压 # 解压后至于C:\Dev\Env\Java\jdk-11.0.2目录下 https://mirrors.huaweicloud.com/openjdk/11.0.2/openjdk-11.0.2_windows-x64_bin.zip编译安装 # 以管理员身份运行 CMD命令提示符 并进入JD…...
Python实验:读写文本文件并添加行号
[实验目的] 熟练掌握内置函数open()的用法;熟练运用内置函数len()、max()、和enumerate();熟练运用字符串的strip()、ljust()和其它方法;熟练运用列表推导式。 [实验和内容] 1.编写一个程序demo.py,要求运行该程序后࿰…...
什么是 NDC 坐标?什么是世界坐标?
什么是 NDC 坐标(归一化设备坐标)? 定义 NDC(Normalized Device Coordinates) 是三维图形渲染管线中的中间坐标系统,范围为 [-1, 1](x、y、z 轴均为此范围)。它是设备无关的标准化…...
25年护网二面
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
《鸟哥的Linux私房菜基础篇》---5 vim 程序编辑器
目录 一、vim程序编辑器的简介 二、命令模式快捷键(默认模式) 1、光标移动 2、编辑操作 3、搜索与替换 三、插入模式快捷键 四、底行模式快捷键(按:进入) 五、高级技巧 1、分屏操作 2、多文件编辑 3、可视化…...
Day21:在排序数组中查找数字
某班级考试成绩按非严格递增顺序记录于整数数组 scores,请返回目标成绩 target 的出现次数。 示例 1: 输入: scores [2, 2, 3, 4, 4, 4, 5, 6, 6, 8], target 4 输出: 3 示例 2: 输入: scores [1, 2, 3, 5, 7, 9], target 6 输出: 0 …...
Android音视频多媒体开源库基础大全
从事音视频开发工作,需要了解哪些常见的开源库,从应用到底软系统,整理了九大类,这里一次帮你总结完。 包含了应用层的MediaRecorder、surfaceView,以及常见音视频处理库FFmpeg和OpenCV,还有视频渲染和音频…...
ManiWAV:通过野外的音频-视频数据学习机器人操作
24年6月来自斯坦福大学、哥伦比亚大学和 TRI 的论文“ManiWAV: Learning Robot Manipulation from In-the-Wild Audio-Visual Data”。 音频信号通过接触为机器人交互和物体属性提供丰富的信息。这些信息可以简化接触丰富的机器人操作技能学习,尤其是当视觉信息本身…...
传感器研习社:Swift Navigation与意法半导体(STMicroelectronics)合作 共同推出端到端GNSS汽车自动驾驶解决方案
自动驾驶系统单纯依赖感知传感器进行定位在遇到恶劣天气或缺乏车道标线的道路场景时很容易失效。此外,由于激光雷达(LiDAR)、视觉等传感器的成本高昂以及将众多不同组件整合为统一系统的复杂性,都可能增加产品研发成本或延迟产品上…...
Java 二维数组元素降序排序(非冒泡排序)
说明:每次比较出最大值后,把最大值设置为最小值-1,再次比较该数组; 创建Object b[][] new Object[N][2];来存储String和Int两种类型数据到同一个数组里 package com.MyJava;import java.util.Scanner;public class Test {public…...
梦回杭州...
她对我说,烟雨中的西湖更别有情趣,我也怀着对‘人间天堂’的憧憬踏上了向往之旅。第一次亲密接触没有感觉中那么好,现在想起来是那时的人和心情都没能安静下来,去慢慢品味它的美。 六下杭州,亲历每一片风景,…...
Spring Boot整合Apache BookKeeper教程
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Spring Boot整合Apache BookKeeper教程 1. 简介 Apache BookKeeper 是一个高性能、持久化的分布式日志存储系统,适用于需要强一致性和高吞吐量的…...
C++项目——内存池
C项目——内存池 前置知识 std::allocator c中所有stl容器都有自己的allocator类用于分配和回收空间,例如vector类中push_back函数的实现方式: template <class T> void Vector<T>::push_back(const T& t) { // are we out of space…...
【设计模式】SOLID 设计原则概述
SOLID 是面向对象设计中的五大原则,不管什么面向对象的语言, 这个准则都很重要,如果你没听说过,赶紧先学一下。它可以提高代码的可维护性、可扩展性和可读性,使代码更加健壮、易于测试和扩展。SOLID 代表以下五个设计原…...
Deepseek-r1:14b+ScraperAPI实现联网本地大模型回答
文章目录 前言一、Deekseek本地部署二、SerpAPI1.什么是SerpAPI?2.如何使用SerpAPI进行Web搜索 三、实现Deepseek-r1:14bScraperAPI实现联网本地大模型回答1. Code 前言 我需要对本地的Deepseek-r1:14b进行提问,我发现它对于实时的问题,或者…...
DHCP工作原理
DHCP报文类型 DHCP Discover 客户端广播发送DHCP discover报文消息, 客户端通过UDP68端口向网络上发送DHCP discover数据包(包含MAC地址和计算机名等信息).源为0.0.0.0, 目的为255.255.255.255 discover等待时间默认为1秒, 1秒内没有得到回应, 客户机会将这一广播包重新发送4次…...
JVM常见面试总结
JVM(Java虚拟机)是Java程序运行的核心,掌握JVM相关知识对于Java开发者至关重要。以下是JVM常见的面试问题总结: 1. JVM内存模型 问题:JVM的内存结构分为哪些部分? 答案: 方法区(Met…...
博客系统自动化测试报告
1.项目背景 基于SSM框架实现的个人博客系统,现有登录注销页面,博客列表页,博客内容页,博客编辑页面。登录即可查看自己曾经发表的博客,通过使用Selenium定位web元素、对获取到的元素进行操作等,对博客系统…...
如何选择合适的 AI 模型?(开源 vs 商业 API,应用场景分析)
1. 引言 在 AI 迅猛发展的今天,各类 AI 模型层出不穷,从开源模型(如 DeepSeek、Llama、Qwen)到商业 API(如 OpenAI 的 ChatGPT、Anthropic 的 Claude、Google Gemini),每种方案都有其优势与适用…...
目标检测20年(二)
没有看过(一)的可以看看笔者这篇文章: 目标检测20年(一)-CSDN博客 目录 3.2 目标检测数据集和指标 3.2.1 数据集 3.2.1.1 Pascal VOC 3.2.1.2 ILSVRC 3.2.1.3 MS-COCO 3.2.1..4 Open Images 3.2.2 指标 3.3 目…...
【linux】统信操作系统修改默认编辑模式从nano改为vim
统信操作系统修改默认编辑模式从nano改为vim 适用命令update-alternatives --config editor rootuos-PC:~# update-alternatives --config editor 有 3 个候选项可用于替换 editor (提供 /usr/bin/editor)。选择 路径 优先级 状态 ---------------------…...
在Fedora-Workstation-Live-x86_64-41-1.4中使用最新版本firefox和腾讯翻译插件让英文网页显示中文翻译
在Fedora-Workstation-Live-x86_64-41-1.4中使用最新版本firefox和腾讯翻译插件让英文网页显示中文翻译 应用——系统工具——终端 suozhangfedora:~$ rpm -aq | grep firefox firefox-131.0.2-1.fc41.x86_64 firefox-langpacks-131.0.2-1.fc41.x86_64 fedora41系统自身安装有f…...
集成学习(下):Stacking集成方法
一、Stacking的元学习革命 1.1 概念 Stacking(堆叠法) 是一种集成学习技术,通过组合多个基学习器(base learner)的预测结果,并利用一个元模型(meta-model)进行二次训练,…...
知道自己鼠标在某个竖直平面上的经纬度信息在这个竖直的平面上的实时坐标
鼠标放上去就开启map.on(mars3d.EventType.mouseMove,结合以下方法实现 callback: function (e) {// 经纬度const mpt LngLatPoint.fromCartesian(e.cartesian)const ptNew proj4Trans([mpt.lng, mpt.lat], "EPSG:4326", CRS.CGCS2000_GK_Zone_3)const …...
【技术简析】触觉智能RK3506 Linux星闪网关开发板:重新定义工业物联新标杆
在工业智能化与物联网深度融合的今天,深圳触觉智能推出首款搭载瑞芯微RK3506芯片的Linux星闪网关开发板,为大家技术解析。 RK3506-国产芯的硬核实力 作为瑞芯微2024年第四季度推出的入门级工业芯片平台,RK3506以三核Cortex-A7(1.…...
GLB文件介绍
GLB文件是由支持glTF(GL Transmission Format)标准的软件或工具生成的。glTF是一种开放的3D模型传输格式,而GLB是其二进制版本,通常用于嵌入纹理和模型数据。以下是常见的生成GLB文件的软件和工具: 1. 3D建模软件 • …...
树莓集团数字产业布局:商业智慧的多维呈现
树莓集团在数字产业的布局展现其前瞻性的商业智慧,通过多维度的战略部署,构建一个 শক্তিশালী且富有活力的数字生态系统。 全国产业园布局:构建数字产业生态链 树莓集团通过在全国范围内建设产业园,有效整合资源&#x…...
“智改数转”新风口,物联网如何重构制造业竞争力?
一、政策背景 为深化制造业智能化改造、数字化转型、网络化联接,江苏省制定了《江苏省深化制造业智能化改造数字化转型网络化联接三年行动计划(2025-2027年)》,提出到2027年,全省制造业企业设备更新、工艺…...
代码随想录第55期训练营第八天|LeetCode344.反转字符串、541.反转字符串II、卡码网:54.替换数字
前言 这是我参加的第二次训练营!!!爽!这次我将更加细致的写清每一道难题,不仅是提升自己,也希望我自己的写的文章对读者有一定的帮助! 打卡代码随想录算法训练营第55期第八天(づ&a…...
c++ XML库用法
在C中,处理XML文件的读写操作可以通过多种库来实现。以下是几个常用且简洁的库: 1. TinyXML-2 简介: TinyXML-2 是一个轻量级的C XML解析库,易于使用且性能良好。特点: 简单易用,API直观。内存占用小,适合嵌入…...
力扣算法Hot100——128. 最长连续序列
题目要求时间复杂度为O(n),因此不能使用两次循环匹配。 首先使用 HashSet 去重,并且 HashSet 查找一个数的复杂度为O(1)外循环还是遍历set集合,里面一重循环需要添加判断,这样才不会达到O( n 2 n^2 n2)判断是否进入最长序列查找循…...
spring-tx笔记
编程式事务与声明式事务的理解 补充:什么是事务? 事务是一个重要概念,尤其在数据库管理系统中。事务是指一组操作。,这些操作要么全部成功执行,要么全部不执行,确保数据的一致性和完整性 编程式事务 编…...
VulnHub-Web-Machine-N7通关攻略
一、信息收集 第一步:确定靶机IP为192.168.0.107 第二步:扫描后台及开放端口 第三步:进行敏感目录及文件扫描 http://192.168.0.107/index.html (CODE:200|SIZE:1620) http://192.168.0.107/server-status (CODE:403|SIZ…...