指针与引用、数组前缀区别

指针与引用、数组前缀区别

指针和引用是C++中的两个重要概念,它们都用于间接访问变量或对象的值,但它们在使用上有一些关键的区别:

1. 定义与语法

  • 指针:指针是一个变量,用于存储另一个变量的内存地址。可以通过*操作符访问指针所指向的值。指针的定义方式如下:
    1
    2
    int a = 10;
    int* p = &a; // p 是一个指针,指向 a 的地址
  • 引用:引用是一个变量的别名,一旦引用被创建,它就不能再指向其他变量。引用的定义方式如下:
    1
    2
    int a = 10;
    int& r = a; // r 是 a 的引用,即 r 和 a 指向相同的变量

2. 是否可以重新赋值

  • 指针:指针可以在程序的不同地方指向不同的变量或对象,即指针的值(所指向的地址)可以改变。
    1
    2
    int b = 20;
    p = &b; // 现在 p 指向 b 的地址
  • 引用:引用在初始化后无法改变,一旦引用一个变量,就不能再引用其他变量。
    1
    2
    int b = 20;
    r = b; // 这里不是改变引用,而是将 b 的值赋给 a(因为 r 是 a 的别名)

3. 空引用和空指针

  • 指针:指针可以是空指针(nullptrNULL),表示不指向任何有效地址。
    1
    int* p = nullptr; // p 是空指针
  • 引用:引用必须在定义时被初始化,不能是空引用,也不能被赋值为nullptr

4. 内存占用

  • 指针:指针本身占用内存,用于存储地址。
  • 引用:引用不占用额外的内存空间,实际上是被引用变量的一个别名。

5. 运算符重载

  • 指针:可以进行指针运算,例如增加或减少指针的值以访问数组中的不同元素。
    1
    p++; // 指针 p 移动到下一个元素
  • 引用:引用不能进行算术运算,因为它们只是变量的别名,而不是实际的内存地址。

6. 常见用途

  • 指针:常用于动态内存分配、数组操作、函数指针以及实现复杂的数据结构(如链表、树等)。
  • 引用:常用于函数参数传递、返回值优化以及简化操作符重载。

总结来说,指针更灵活但也更容易引发错误(如空指针、野指针等),而引用更安全、易于使用,但缺乏指针的灵活性。

指针与数组前缀区别

1
2
3
4
5
cCopy codeint arr[3] = {1, 2, 3};
int *p = arr; // p指向arr的首地址

p++; // p现在指向arr[1]
arr++; // 错误!数组名是常量,不能被重新赋值

在这个例子中,p++是允许的,因为指针p可以指向数组的下一个元素。但arr++会产生编译错误,因为数组名(数组前缀)是常量,不能改变其指向的地址。

总结来说,指针是一个独立的变量,数组前缀是数组的首地址,虽然两者都可以用来访问数组元素,但它们的使用方式和限制不同。

指针常见问题

理解 p++ 的行为

p++后置自增 操作符,它的特点是:

  • 先使用当前值:在表达式计算时,使用当前指针 p 的值。
  • 再自增:在使用完当前值后,将 p 的值增加 1,使其指向下一个元素。

*p++ 的解析与行为

当你编写 *p++ 时,整个表达式实际上是按照以下步骤进行的:

  1. 保存当前指针 p 的值:这时 p 还没有发生变化。
  2. 解引用当前指针 p 的值:即访问 p 当前指向的内存地址的值。
  3. **自增 p**:在解引用之后,p 自增 1,指向下一个内存地址。

由于 p++ 是后置的,所以它会先返回原始地址的值,然后再自增。因此,在 *p++ 表达式中,* 操作符使用的是 p 自增前的地址。

举例说明

假设我们有以下代码:

1
2
3
4
int arr[] = {10, 20, 30};
int *p = arr;

int value = *p++;

这个代码片段会按以下顺序执行:

  1. p 当前指向 arr[0],值为 10
  2. *p++ 先使用当前 p 指向的地址,即 arr[0],解引用并得到值 10
  3. 然后 p++ 执行,自增 p 的值,使 p 指向 arr[1],即值为 20 的位置。
  4. 最后,value 被赋值为 10,这是 p 自增前解引用获得的值。

为什么 value 是之前地址的值?

因为 p++ 是后置自增,它保证了在自增之前,p 原本指向的地址会被使用。自增操作是在使用完 p 当前值之后才进行的。因此,*p++ 在解引用时使用的是 p 自增之前的地址,这也是为什么 value 得到的是自增前的值。

二级指针

多级指针,指针的级数没有限制

指针作为函数返回值

image-20240815172343242

C++ 指针指向字符串 打印指针输出整个字符串

这是因为C++中的std::cout对指针有不同的输出行为:

  • char\*类型的指针:当cout遇到一个char*类型的指针时,它会将这个指针解释为一个指向字符串的指针,并输出指针所指向的整个字符串。
  • 其他类型的指针:对于非char*类型的指针,cout会输出指针的内存地址。

例如,如果你有一个整数指针

如何输出指针的地址

如果你想要输出char*指针的地址,而不是它指向的字符串,可以使用类型转换或直接打印指针的地址:

1
2
3
cpp
Copy code
std::cout << static_cast<const void*>(str) << std::endl;

或者使用%p格式化输出:

1
2
3
cpp
Copy code
printf("%p\n", static_cast<const void*>(str));

这两种方法都会输出指针str的内存地址,而不是它指向的字符串内容。

指针常量和指针

指针常量是指向某个地址的指针,并且这个指针不能再指向其他地址。换句话说,指针本身是一个常量,但它指向的值可以改变。

int* const ptr = &a;

位运算

左移 a<<1

右移 a>>1

按位与作用

1.清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相
与,结果为零。

2.取一个数的指定位
比如取数X=10101110的低4位,只需要找一个数Y,令Y的低4位为1,其余位为0,
即Y=0000 1111,然后将X与Y进行按位与算(X&Y=0000 1110)即可得到x的指定位。

3.判断奇偶
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if((a&1)
== 0)代替if(a%2 == 0)来判断a是不是偶数。

image-20240823105628023

对于负数的右移:因为负数在内存中是以补码形式存在的,所有首先根据负数的原码求出负数的补码(符号位不变,其余位按照原码取反加1),然后保证符号位不变,其余位向右移动到X位,在移动的过程中,高位补1.等移位完成以后,然后保持符号位不变,其余按位取反加1,得到移位后所对应数的原码。即为所求。

取反~是单目运算符 无法进行类似&=的运算

C++ new和malloc区别

https://blog.csdn.net/happyjacob/article/details/104484218

C++ 静态创建和动态创建的区别

在C++中类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* p=new A(),Ap=(A)malloc();静态建立一个类对象,是由编译器为对象在栈空间中分配内存,通过直接移动栈顶指针挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。动态建立类对象,是使用new运算符将对象建立在堆空间中,在栈中只保留了指向该对象的指针。栈是由编译器自动分配释放 ,存放函数的参数值,局部变量的值,对象的引用地址等。其操作方式类似于数据结构中的栈,通常都是被调用时处于存储空间中,调用完毕立即释放。堆中通常保存程序运行时动态创建的对象,C++堆中存放的对象需要由程序员分配释放,它存在程序运行的整个生命期,直到程序结束由OS释放。而在java中通常类的对象都分配在堆中,对象的回收由虚拟机的GC垃圾回收机制决定。
https://blog.csdn.net/error_again/article/details/112601202