【c++篇】:初识c++--编程新手的快速入门之道(二)

余辉zmh 2024-10-21 13:05:01 阅读 98

文章目录

前言一.引用1.引用的概念2.引用的特性3.引用的使用场景4.常引用5.引用和指针的区别

二.内联函数1.C语言的宏函数2.内联函数的概念3.内联函数的特性

三.auto关键字1.`auto`的定义2.`auto`的使用规则3.`auto`不能推导的场景

四.基于范围的for循环1.范围for的语法2.范围for的使用条件

五.指针空值nullptr

前言

在上一篇文章中讲解了部分c++入门知识点,这篇文章将继续讲解剩下的知识点

一.引用

1.引用的概念

**引用(<code>&)**不是新创建一个变量,而是为变量取别名。引用时共用一块空间,编译器不会新开辟空间。如图所示,b是a的别名,c是b的别名,它们的空间地址一样

using namespace std;

int main() { -- -->

int a = 1;

int& b = a;

int& c = b;

cout << &a << endl;

cout << &b << endl;

cout << &c << endl;

return 0;

}

在这里插入图片描述

C语言中用指针对变量进行修改,而c++可以直接用引用进行修改。

<code>using namespace std;

int main() { -- -->

int a = 1;

int& b = a;

int& c = b;

cout << a << " ";

cout << b << " ";

cout << c << " ";

cout << endl;

b++;

cout << a << " ";

cout << b << " ";

cout << c << " ";

cout << endl;

c++;

cout << a << " ";

cout << b << " ";

cout << c << " ";

cout << endl;

return 0;

}

在这里插入图片描述

2.引用的特性

引用时必须初始化

<code>int a=0;

int &b; //错误

int &b=a; //正确

一个变量可以有多个引用

int a=0;

int &b=a;

int &c=a;

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

int a=0;

int c=0;

int &b=a;

int &b=c; //错误

3.引用的使用场景

做参数

我们之前用C语言写交换两个数的函数时需要用到指针传参,而c++中可以直接用引用做参数。比如下面的代码中,形参a,b就是实参x,y的别名(也就是引用),形参a,b交换,实参x,y也会交换。引用做参数可以提高函数调用的效率。

using namespace std;

//用指针做参数

void Swap1(int*a,int*b){ -- -->

int t=*a;

*a=*b;

*b=t;

}

//用引用做参数

void Swap2(int&a,int&b){

int t=a;

a=b;

b=t;

}

int main(){

int x=10,y=20;

Swap1(&x,&y);

cout<<x<<" "<<y<<endl;

x=10,y=20;

Swap2(x,y);

cout<<x<<" "<<y<<endl;

return 0;

}

在这里插入图片描述

做返回值

函数传值返回时,会生成一个临时变量用来拷贝返回值,而引用做返回值时,不用生成临时变量,减少了拷贝,提高了效率

<code>int& Count(){ -- -->

static int n=0;

n++;

return n;

}

int main(){

int ret=Count();

return 0;

}

上面这段代码,返回的n在静态区,所以函数调用结束时,n依然还在。而下面这段代码则是引用做返回值时的错误用例:

int& Count(){

int n=0;

n++;

return n;

}

int main(){

int ret=Count();

return 0;

}

不同的是下面的n是局部变量,当函数调用结束时,栈帧销毁,如果没有清理栈帧,ret可能是正确值,如果清理栈帧,ret就会是一个随机值。而如果接受也是引用时,那么ret就一定是随机值。比如:

int& Count(){

int n=0;

n++;

return n;

}

int main(){

//ret是n的别名

int& ret=Count();

return 0;

}

总结:

基本任何场景都可以用引用传参。谨慎使用引用做返回值,出了函数作用域,对象不在就不能用引用返回,还在就可以用引用返回。

4.常引用

int main(){

const int a=0;

int& b=a; //不可以,权限不能放大

const int c=0;

int d=c; //可以,c拷贝给d,权限平移,d的改变不影响c

int x=0;

const int& y=x;//可以,权限缩小,如果x改变,y也会改变,但不能通过y来修改x

double dd=1.11;

int ii=dd; //可以,借助int临时变量

const int& rii=dd;//不可以,临时变量具有常性

}

5.引用和指针的区别

引用概念上定义一个变量的别名,指针存储一个变量地址引用在定义时必须初始化,指针没有要求引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体没有NULL引用,但有NULL指针在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小有多级指针,但是没有多级引用访问实体方式不同,指针需要显式解引用,引用编译器自己处理引用比指针使用起来相对更安全

二.内联函数

1.C语言的宏函数

我们首先来看下面这段代码:

int Add(int x,int y){

return x+y;

}

int main(){

for(int i=0;i<10000;i++){

cout<<Add(i,i+1)<<endl;

}

return 0;

}

在调用函数时,每调用一次函数,就要在栈区创建和销毁一次空间,而我们上面这一段代码,频繁地调用函数,就会频繁地建立栈帧,大大降低了效率,为了解决这一情况,C语言用宏函数来替换:

#define Add(x,y) ((x)+(y))

利用宏函数来替换,不需要建立栈帧,大大提高调用效率。但缺点就是写起来较为复杂,容易出错,可读性差并且不能调试。

而在c++中为优化这一点,增加了新的内联函数inline

2.内联函数的概念

inline修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没用调用函数建立栈帧,提升程序运行的效率。

还是上面的这一段代码:

//在函数类型前加上inline关键字

inline int Add(int x,int y){

return x+y;

}

int main(){

for(int i=0;i<10000;i++){

cout<<Add(i,i+1)<<endl;

}

return 0;

}

3.内联函数的特性

inline是一种以空间换时间的做法,编译器将函数当成内联函数处理时,在编译阶段,会展开整个函数体来替换函数调用。少了调用开销,大大提高程序运行效率。内联函数只适合函数规模较小的(也就是函数不是很长),不是递归且频繁调用的函数用inline修饰。比如函数fun()有50行代码,如果fun()不是内联函数时,10000个位置调用函数,合计共10000+50行,如果fun()是内联函数,合计共10000*50行。这就会使目标文件变大。内联函数不建议声明和定义分离,分离会导致链接错误,因为inline被展开,就没有函数地址,链接找不到。

//fun.h

#include<iostream>

using namespace std;

inline void fun(int i);

//fun.cpp

#include"fun.h"

void fun(int i) {

cout << i << endl;

}

//test.cpp

#include"fun.h"

int main() {

int x = 10;

fun(10);

return 0;

}

在这里插入图片描述

最好声明和定义放在一起:

<code>//fun.h

#include<iostream>

using namespace std;

void fun(int i) { -- -->

cout << i << endl;

}

//test.cpp

#include"fun.h"

int main() {

int x = 10;

fun(10);

return 0;

}

三.auto关键字

1.auto的定义

在C语言的时候我们知道typedef关键字可以用来给一些类型取别名,对于一些较长的类型名时使用起来会很方便,比如,std::map<std::string,std::string>::iterator 是一个类型,但是该类型太长了,特别容易写错,于是可以通过typedef给类型取别名

typedef std::map<std::string, std::string> Map;

使用typedef确实可以简便代码,但是也会遇到一些特殊情况:

typedef char*pstring

int main(){

const pstring p1;

const pstring *p2;

return 0;

}

在这里插入图片描述

c++为了解决这一情况将<code>auto赋予了新的含义。

c++11中,auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

int TestAuto()

{ -- -->

return 10;

}

int main()

{

int a = 10;

auto b = a;

auto c = 'a';

auto d = TestAuto();

cout << typeid(b).name() << endl;

cout << typeid(c).name() << endl;

cout << typeid(d).name() << endl;

return 0;

}

在这里插入图片描述

2.<code>auto的使用规则

使用auto时,一定要初始化,在编译阶段编译器需要根据初始化表达式来推到实际类型,然后将auto替换为变量的实际类型。

auto声明指针类型时,autoauto*没有区别,但是auto声明引用类型时必须加上&

int main() { -- -->

int a = 10;

auto b = &a;

auto* c = &a;

auto& d = a;

cout << typeid(b).name() << endl;

cout << typeid(c).name() << endl;

cout << typeid(d).name() << endl;

*b = 20;

*c = 30;

d = 40;

return 0;

}

在这里插入图片描述

在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器会报错。

<code>void Testauto(){ -- -->

auto a=1,b=2;

auto c=1,d=1.1;

}

在这里插入图片描述

3.<code>auto不能推导的场景

auto不能作为函数的参数

void TestAuto(auto a){ -- -->

cout<<a<<endl;

}

在这里插入图片描述

<code>**auto不能直接用来声明数组**

void TestAuto(){ -- -->

int a[]={ 1,2,3};

auto b[]={ 4,5,6};

}

在这里插入图片描述

四.基于范围的for循环

1.范围for的语法

在C语言或者c++98中,我们如果要遍历一个数组,通常会按照以下方式进行

<code>using namespace std;

int main() { -- -->

int array[] = { 1,2,3,4,5,6 };

for (int i = 0; i < sizeof(array) / sizeof(int); i++) {

array[i] *= 2;

}

for (int i = 0; i < sizeof(array) / sizeof(int); i++) {

cout << array[i] << " ";

}

cout << endl;

return 0;

}

而在之后的c++11更新中,引入了基于范围的for循环,对于一个有范围的集合,在遍历时可以自动识别循环范围。for循环后的括号由:分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

using namespace std;

int main() {

int array[] = { 1,2,3,4,5,6 };

for (auto& e : array) {

//每一项都乘以2

e *= 2;

}

for (auto& e : array) {

cout << e << " ";

}

cout << endl;

return 0;

}

在这里插入图片描述

和普通的for循环一样,范围for循环也可以用continue结束本次循环,也可以用break直接结束整个循环。

2.范围for的使用条件

for循环迭代的范围必须是确定的。

对于一个数组,就是数组第一个元素和最后一个元素的范围;在之后学到类时,还有关于类的范围。

下面这一段代码就是错误的,因为for的范围不确定,数组名作为参数传过来,我们只知道数组第一个元素,而不确定最后一个。

<code>using namespace std;

void TestFor(int array[]){ -- -->

for(auto e:array){

cout<<e<<endl;

}

}

int main(){

int array[]={ 1,2,3,4,5,6};

TestFor(array);

return 0;

}

在这里插入图片描述

五.指针空值nullptr

在C语言的时候我们知道<code>NULL表示空指针,但实际上NULL是一个宏,NULL被定义为字面常量0或者无类型指针(void*)的常量。在C语言的头文件(stddef.h)中可以看到以下代码:

#ifndef NULL

#ifdef _cplusplus

#define NULL 0

#else

#define NULL ((void*)0)

#endif

#endif

在使用NULL不可避免的会遇到一些特殊情况,比如:

void f(int) { -- -->

cout << "f(int)" << endl;

}

void f(int*) {

cout << "f(int*)" << endl;

}

int main() {

f(0);

f(NULL);

return 0;

}

在这里插入图片描述

因为<code>NULL被定义为0,因此程序默认调用了第一个函数void f(int),如果要使NULL按照指针方式使用,必须1强制转换为`((void*)0)。

为了解决这个麻烦,在c++11时,引入了新关键字nullptr表示指针空值,在使用时,不需要包含头文件

在上面的代码中加上这句,就会得到以下结果:

f(nullptr);

在这里插入图片描述

在c++11中,<code>sizeof(nulllptr)与sizeof((void*)0)所占的字节数相同

以上就是关于c++入门部分的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。