02C++指针与引用

C++指针与引用

1.指针

————————基本与C语言一致

但是有以下问题需要注意:

long *p;
*p = 222;

——————这种情况,p的地址是不知道在哪里的,会导致野指针,可能会产生错误。

—————*p赋值222, 一个野指针值为222

因该这样写:

long a =222;
long *p;
p = &a;

2.new

在C语言中,已经有malloc来在堆上开辟内存,在C++中,可以继续使用,但是又更好的方式,new.

原理:与c语言基本一致,在堆上开辟内存,手动开辟的,需要手动回收,否则内存泄漏。现在堆上开辟一块内存,并返回地址.

这样,指针就有了地址,尽管没有数据。

int *p = (int*) malloc(sizeof(int));

C++中

int *p = new int;

3.delete

手动在堆上开辟的内存,需要手动回收。在C语言中,有free, 在C++中有delete。

delete p;

4.数组指针

——————与C语言基本一致,arr所占的空间的整个数组的空间,c/c++语言中,会打印全部数组的数据,在传参时才会退化为指针。

#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    int *arr = new int[3];
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    cout << arr[2] << endl;
    delete[] arr;
    system("pause");
    return 0;
}

5.const

const,在c语言与C++有很大差异,在C语言中,const int a = 10; a的值是可以发生改变,怎么做到的

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

int main(int argc, char const *argv[])
{
    const int a = 10;
    //改变a的值
    // a = 20; //报错,a指定了自身数据不能变化
    int *p;
    p = &a; //c++中报错
    *p = 20; //a的值发生变化
    printf("%d\n", a);
    system("pause");
    return 0;
}

但是,在c++中,const int a = 10; 指针指向的值和指针指向的地址都不能发生改变。

c/c++有一点是相同的,它们都只代表自身不能更改,如果其他修改,间接影响到了数值,则它们也会跟着改变

#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    // const int a = 10;//
    // int *p;
    // p = &a;
    int a = 10;
    const int* const b = &a;//代表b不能更改值也不能指向其他地址,但是a可以被其他指针修改数值
    int *p;
    p = &a;
    *p = 20;
    cout << *b << endl;
    system("pause");
    return 0;
}
1.const修饰常量
const int a = 10;

在c语言中,再次强调是可以被修改的,但是在C++中,a的值不能被修改,也不能被除const常量指针以外的指针指向

#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    const int a = 10;//
    const int *b = &a;//b常量指针可以指向a
    *b = 20;//报错,b不能修改值
    system("pause");
    return 0;
}

2.常量指针

常量指针:所指向地址对应的内存空间数值不可以被修改,但是指针所指向的地址是可以发生改变。

同样需要注意:常量指针,只代表了自身不能改变内存空间数值的变化,但是不代表其他指针不能影响到该内存空间数值变化。

可以指向任意地址,包括常量和非常量

#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    int a = 10;
    const int *b = &a; //a可以间接被其他指针影响
    int c = 20;
    b = &c;
    cout << *b << endl;
    system("pause");
    return 0;
}
    const int f = 30;
    const int *d = &f;
3.指针常量

指针常量:代表指针所指向的地址不能发生改变,但是指针所指向对应的内存空间数值可以发生改变

同样再次强调:指针常量,只是显示了指针所指向地址不能发生改变。

#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    int a = 30;
    int* const p = &a;
    int b = 40;
    // p = &b;//报错
    *p = 40;
    cout << a << endl;//40
    system("pause");
    return 0;
}

6.引用

1.引用介绍

引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。

注意:引用仅仅只是可以看作数据的一个别名,而不是真正的别名。实际上,引用也是占用空间的,并不比指针所占空间少。

引用的本质是一个指针常量

int a = 20;
int * const p = &a;

引用的目的:引用本质是一个常量指针,也就是为了保证安全,普通的指针,可以指向其他地址(一对多),甚至导致类型发生变化引发错误,而引用不会。

#include <iostream>
using namespace std;
void modifyData(int &a);
void modifyPointer(int *const b);
int main(int argc, char const *argv[])
{
    int a = 20;
    modifyData(a);
    cout << a << endl;

    int b = 10;
    modifyPointer(&b);
    cout << b << endl;
    system("pause");
    return 0;
}

void modifyData(int &a)
{
    a = 30;
}

void modifyPointer(int *const b)
{
    *b = 50;
}
2.引用常见问题

问题1:C++引用不能绑定到临时数据

答:引用的本质是一个指针,指针必须指向地址,而不是内存空间数值,否则就报错

#include <iostream>
using namespace std;
void changeData(int &a);
int main(int argc, char const *argv[])
{
    changeData(&30);//报错,30是内存空间数值,没有地址,存储在静态常量区
    system("pause");
    return 0;
}

void changeData(int &a)
{
    a = 30;
}

问题2:编译器会为const引用创建临时变量。

将常引用绑定到临时数据时,

const int &A;

编译器会为临时数据创建一个新的、无名的临时变量,并将临时数据放入临时变量中,然后再将引用绑定到临时变量。

简单说:创建一个新的变量,引用是绑定在临时变量上,非原来数据。

#include <iostream>
using namespace std;
void changeData(int &a);
void changeData2(const int &a);
void changeData3(const int const *b);
int main(int argc, char const *argv[])
{
    // changeData(&30);//报错
    changeData2(50);//不报错,原因是const int&a;50字面常量,数值被复制了,const int&a创建了新的地址
    // changeData3(100);//报错
    system("pause");
    return 0;
}

void changeData(int &a)
{
    a = 30;
}

void changeData2(const int &a)//创建一个新的变量,引用是绑定在临时变量上,非原来数据。本质与changeData3不同
{
    cout << a << endl;
}

void changeData3(const int const *b)//本质是一个不能改变地址和修改内存空间的指针
{

}

——————通过上面案例:const int &A; 本质上先经过一道复制,产生临时数据,然后将引用绑定到临时变量上。

这样会导致什么问题了?? (纯个人猜想,非确定答案)

答:const int &A;经过复制,则不仅仅占用空间的问题,而且还存在经过复制的数据,地址会发生改变,引用地不是原来地地址,导致根本无法修改原来地数据。

所以,定性,引用,非基本类型时,一般只能用 int &A;而不是const int &A; const int &A完全可以用const int const *b;去代替

基本类型占用空间比较少,就算被复制,也没什么;但是如果是数组,复制地话,内存会急剧上升,毕竟形参是在寄存器地,寄存器也就10M~16M的内存空间。

另外:编译器只有在必要的时候才会创建临时变量。(有必要,代表需要转换,或者计算的时候,以及常量字面值无地址情况)

上面的解答只对了一半,但是在开发过程中,很容易引发不必要的错误。个人人为,需要在一开始就避免这种问题。

#include <iostream>
using namespace std;
void swapData(int &a, int &b);
void swapr(const int &a, const int &b);
int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;
    swapData(a, b);
    cout << a << b << endl;

    double c = 20.103;
    float d = 10.109;
    swapr(c, d);
    cout << c << endl;//20.103
    cout << d << endl;//10.109
    system("pause");
    return 0;
}

void swapData(int &a, int &b) //可以交换
{
    int temp = a;
    a = b;
    b = temp;
}

// void swapData2(const int &a, const int &b)//编译器不会为a创建临时变量
// {
//     int temp = a;
//     a = b;//报错
//     b = temp;
// }

void swapr(const int &a, const int &b)
{
    cout << a << endl;//20
    cout << b << endl;//10
}
3.C++ const引用与转换类型

具体作用,假设实参的参数类型与引用参数不匹配,但可以转换为引用类型,程序将创建一个正常类型的临时变量,使用转化后的实参值来初始化它,然后传递一个指向该临时变量的引用

简单说:参数不匹配时,编译器就会有必要创建正常类型的临时变量,将原来的值转换然后赋值给临时变量。

上面的例子已经说明

void swapr(const int &a, const int &b)
{
    cout << a << endl;//20
    cout << b << endl;//10
}

7.数组的遍历技巧

遍历,我们在C语言中下,学到了一种方式,在C++中,依旧可以使用,但在C++中,有其他遍历数组的方式,我们来学习下。

1.c语言方式
#include <iostream>
using namespace std;
void trave02(int *arr, int length);
int main(int argc, char const *argv[])
{
    int arr[] = {1, 2, 3, 4, 5};
    trave02(arr, sizeof(arr) / sizeof(arr[0]));
    for (auto value : arr) // arr不会退化为指针,指向的空间是整个内存空间,而不是首地址的内存空间,所以可以使用
    {
        cout << value << endl;
    }
    system("pause");
    return 0;
}

void trave02(int *arr, int length) //这里arr会退化为指针,4字节或8字节,首地址,所以需要length
{
    for (int i = 0; i < length; i++)
    {
        *arr = 0;
        arr++;
    }
}

// void travese03(int arr[])
// {
//     for (int *p = begin(arr); p != end(arr); p++) //这样传是错误的,arr已经退化为指针,根本没有结束标志
//     {
//           *p = 0;
//     }
// }

void trave04(int *begin, int *end)
{
    for (int *p = begin; p != end; ++p)
    {
       *p = 0;     
    }
}
2.C++方式
    for (auto value : arr) // arr不会退化为指针,指向的空间是整个内存空间,而不是首地址的内存空间,所以可以使用
    {
        cout << value << endl;
    }

注意:这种方式,只适合arr没有退化为指针的情况,指针指向的是整个内存空间。

3.C++ begin end方式
#include <iostream>
using namespace std;
void trave05(int *begin, int *end);
int main(int argc, char const *argv[])
{
    int arr[] = {1, 2, 3, 4, 5};
    trave05(begin(arr), end(arr));
    for (auto value : arr) // arr不会退化为指针,指向的空间是整个内存空间,而不是首地址的内存空间,所以可以使用
    {
        cout << value << endl;
    }
    system("pause");
    return 0;
}

void trave05(int *begin, int *end)
{
    for (int *p = begin; p != end; ++p)
    {
        *p = 0;
    }
}
4.案例数组排序,冒泡法
#include <iostream>
using namespace std;
void bubble_sort(int *begin, int *end);
void bubble_sort2(int *begin, int *end);
int main(int argc, char const *argv[])
{
    int arr[] = {6, 2, 3, 5, 4, 7};
    bubble_sort(begin(arr), end(arr));
    for (auto value : arr) // arr不会退化为指针,指向的空间是整个内存空间,而不是首地址的内存空间,所以可以使用
    {
        cout << value << endl;
    }

    int arr2[] = {6, 2, 3, 5, 4, 7};
    bubble_sort2(begin(arr2), end(arr2));
    for (auto value : arr2)
    {
        cout << value << endl;
    }
    system("pause");
    return 0;
}

void bubble_sort(int *begin, int *end)
{
    for (int *p1 = begin; p1 != end; ++p1)
    {
        for (int *p2 = begin; p2 != end - 1; ++p2)
        {
            if (*p2 > *(p2 + 1))
            {
                int temp = *p2;
                *p2 = *(p2 + 1);
                *(p2 + 1) = temp;
            }
        }
    }
}

void bubble_sort2(int *begin, int *end)
{
    for (auto p = begin; p != end; ++p)
    {
        for (auto p2 = begin; p2 != end - 1; ++p2)
        {
            if (*p2 < *(p2 + 1))
            {
                int temp = *p2;
                *p2 = *(p2 + 1);
                *(p2 + 1) = temp;
            }
        }
    }
}

auto:在C++中,使用较少,原因是,类型不直观,有时不容易分辨是int还是int*,导致使用时不怎么好用,但是在其他语言中,使用较多,比如python/go/kotlin等

8.字符串

1.字符串拼接
   string str1 = "runoob";
   string str2 = "google";
   string str3;
   str3 = str1 + str2;

字符串拼接,与java一致,都会创建一个新的字符串。c语言中,字符串数组也是如此。二者本质含义没有什么不同。

2.长度
str.size();
str.length();
3.字符串截取
#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    string name = "helloworld";
    string sub = name.substr(0,4);
    cout << sub << endl;
    system("pause");
    return 0;
}
4.字符串查找
#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    string str = "helloworld";
    //在字符串str1中查看是否有字符o,如果有则返回字符o在字符串str1中的位置,否则返回npos
    cout << str.find('o') << endl;
    //从第五个位置开始查找
    cout << str.find('o', 5) << endl;
    //从第五个位置从后往前查找
    cout << str.rfind('o', 5) << endl;

    string str2 = "China";
    //将字符串str1中第6个字符开始连续5个字符替换成str2的内容
    str.replace(6, 5, str2);
    cout << str << endl;
    system("pause");
    return 0;
}
5.字符串末尾添加和插入
    string str3 = "bushi";
    str3.append("你好");
	insert();// 为插入
6.作为参数传递

​ 作为参数传递有两种形式,值传递和引用,至于

1.引用
#include <iostream>
using namespace std;
void func(string &s);
void func2(string &s);
int main(int argc, char const *argv[])
{
    string str = "helloworld";
    func(str);
    cout << str << endl;//welloworld

    func2(str);
    cout << str << endl;//不是你的
    system("pause");
    return 0;
}

void func(string &s) //字符串引用
{
    s.replace(0, 1, "w");
}

void func2(string &s) //字符串引用
{
    s = "不是你的";//s指向的内存空间发生了扩容,这个在java中是不可以的
}

引用是指针常量,地址不能发生改变,但是内存空间数据值可以发生改变。

2.注意下面重点:

1.在C++中,string是可以动态扩容的,在java中不行,注意是不行,通过上面案例,我们可以知道,c++ string内存空间数值动态扩容了,地址空间没有发生改变。所以引用是没有问题的。

2.在java中,string是对象,都会有自己的地址(注意常量都会默认有地址,这一点与C++极其不同),由于string不能扩容,每次赋值字符串,从C++的角度,就是string* p指针指向了其他对象的地址,根本不是修改内存空间。
简单说:Java分为基本类型和其他对象。基本类型,都是地址和数据值传递拷贝(地址空间和内存空间中开辟一片内存)。 其他类型,都是地址值拷贝(地址空间开辟一个指针4字节或8字节的空间),非引用,

重申我们在理解java中形参的错误:在java中没有引用,是的,没有引用,都是值传递,只是基本类型数据值拷贝,其他类型是地址拷贝,非引用,引用是指针常量,java中其他类型作为形参时,是可以改变指针指向的

所以可以得出结论:java除了基本类型,赋值是修改内存空间值,其他类型,都是改变指针的指向。如下图所示

java基本类型传递拷贝,(内存空间开辟同样大小的内存),下面只是抽象,2应该存储在常量池中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IlGqcUde-1625408110232)(G:\markdown\C++\C++基础\值传递.png)]

java非基本类型,传参

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2lIyPwM-1625408110237)(G:\markdown\C++\C++基础\非基本类型传递.png)]

3.值传递
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    string str = "helloworld";
    func(str);
    cout << str << endl;
    system("pause");
    return 0;
}

void func(string s) //字符串拷贝,内存地址和空间的完全拷贝,占用内存
{
    s = "你好";
}
4.指针值传递
#include <iostream>
using namespace std;
void func3(string *s);
void func(string s);
void func4(string *s);
int main(int argc, char const *argv[])
{
    string str = "helloworld";
    func(str);
    cout << str << endl;//helloworld

    func3(&str);
    cout << str << endl;//hello world
    func4(&str);
    cout << str << endl;//buyaojin
    system("pause");
    return 0;
}

void func(string s) //字符串拷贝,内存地址和空间的完全拷贝
{
    s = "你好";
}

void func3(string *s)//字符串值传递,拷贝一份地址,占4字节或8字节
{
    string* str2 = new string();
    *str2 = "nihao";
    s = str2; //s指向str2地址,只是改变了s的地址指向,str地址没有发生变化
}

void func4(string *s)
{
    *s = "buyaojin";
}

9.理解const在C/C++中不同

第5节,说到,在C++中,const int a = 10; a 不能被const意外的指针指向a的地址。看报错原因是,int* 指针不能指向 const int *指针。那么我们强转试试

C++中:

#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
    const int a = 10; //
    int *b = (int *)&a;
    *b = 20;
    cout << &a << endl;
    cout << b << endl;
    cout << a << endl; //10,并没有被改变
    system("pause");
    return 0;
}

0x61fe14
0x61fe14
10

值没有被改变。

C中:

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

int main(int argc, char const *argv[])
{
    const int a = 10;
    //改变a的值
    // a = 20; //报错,a指定了自身数据不能变化
    int *p;
    p = &a;
    *p = 20; //a的值发生变化
    printf("%d\n", a);//20
    system("pause");
    return 0;
}

20,值发生改变。根编译器有关,有的是10,有的是20.

20
000000000061fe14
000000000061fe14
我们发现一个问题:为什么地址一样, 但是最后的数值却不一样。

直接给出结论,汇编分析比较复杂:

1.对const常量取地址时,编译器会进行内存分配,并将常量转换为立即数存入内存,而不是存入记录在常量表中的地址

2.使用常量数据时,直接从常量池中去拿,并将其替换,这部分没有内存分配,也跟曾经创建的常量的内存地址无关。

所以,在开发中,const int a = 10;修饰常量,请勿修改里面的值

)&a;
*b = 20;
cout << &a << endl;
cout << b << endl;
cout << a << endl; //10,并没有被改变
system(“pause”);
return 0;
}


0x61fe14
0x61fe14
10

值没有被改变。 

C中:

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

int main(int argc, char const *argv[])
{
    const int a = 10;
    //改变a的值
    // a = 20; //报错,a指定了自身数据不能变化
    int *p;
    p = &a;
    *p = 20; //a的值发生变化
    printf("%d\n", a);//20
    system("pause");
    return 0;
}

20,值发生改变。根编译器有关,有的是10,有的是20.

20
000000000061fe14
000000000061fe14
我们发现一个问题:为什么地址一样, 但是最后的数值却不一样。

直接给出结论,汇编分析比较复杂:

1.对const常量取地址时,编译器会进行内存分配,并将常量转换为立即数存入内存,而不是存入记录在常量表中的地址

2.使用常量数据时,直接从常量池中去拿,并将其替换,这部分没有内存分配,也跟曾经创建的常量的内存地址无关。

所以,在开发中,const int a = 10;修饰常量,请勿修改里面的值

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值