C++中的指针
1.指针中的重点和难点
1.1指针内含信息
指针包含两部分信息:指向的值和类型信息。
指针的类型信息决定了这个指针指向的内存的字节数并解释这些字节 信息。一般指针变量类型要和它指向的数据的类型匹配。
同样的地址,因为指针的类型不同,对它指向的内存的解释就不同,得到的就是不同的数据。
#include <stdio.h>
int main() {
char array1[] = "abcdefghijklmnopqrs";
char * p1 = array1;
int * p2 = (int*)p1;
printf("%d\n", p1);
printf("%d\n", p2);
p1++;
p2++;
printf("%d\n", &array1);
printf("%d\n", p1);
printf("%d\n", p2);
return 0;
}
结果如下:
比较输出结果可以看出虽然p1和p2开始都是指向相同位置19921616,但是在都执行++操作后地址变化却不一样。这是因为字节的大小是一个字节,所以++后指针的值只增加一个字节。而int类型是四个字节存储,因此int类型指针++后是增加4个字节。
1.2void*指针
void*指针是一种特殊的指针类型,可用于存放任意对象的地址,但是丢失了类型信息。如果想要完整的提取指向的数据,必须对这个指针做出正确的类型转换,然后再解指针。因为编译器不允许直接对void*类型的指针做解指针的操作(提示非法的间接寻址)。
一般void*指针是用来和别的指针作比较,或者作为函数的输入或输出,或者赋值给另一个void对象。
1.3指针的算术运算
指针可以加上或者减去一个整数,指针的的这种运算的意义和通常的数值的加减运算的意义是不一样的,指针的运算室友单位的。
公式为:ptr+num = ptr + num*sizeof(*ptr);
#include <stdio.h>
int main() {
char c[] = "hello world";
int a[] = { 10,11,12 };
char *p1 = c;
int * p2 = a;
printf("%d\n", p1);
printf("%d\n", p2);
printf("%d\n", ++p1);
printf("%d\n", ++p2);
return 0;
}
结果为:
char类型存储时占一个字节,int类型存储时占4个字节,因此利用上面的公式char类型指针加的时候单位是1,而int是4.
1.4函数的指针
每一个函数本身也是一种程序数据,一个函数包含了多条执行语句,它被编译后,实质上是多条机器指令的合集。在程序载入到内存后,函数的机器指令存放在一个特定的逻辑区域:代码区。既然是存放在内存中,那么函数也是有自己的指针的。
其实函数名单独进行使用时就是这个函数的指针。
函数指针的示例:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
auto *function = add;
int result = function(1, 2);
printf("1 + 2 = %d", result);
return 0;
}
结果为:
1.5 const指针
const修饰的指针分为指针常量和常量指针,如何区分两者就看const修饰的是指针还是变量。如果修饰的是ptr,而ptr是指代变量的,因此这里是指针常量。如果修饰的是ptr,而ptr是指针,所以这里是常量指针。顾名思义,指针常量是一个常量,因此,改变其指代的值会报错,而改变指针的地址则不会。相反,常量指针是一个指针,因此如果改变不变指针的地址就会报错,而改变指针指代的值则不会报错。
例如:
#include <stdio.h>
int main() {
int a = 97;
int b = 98;
//这里const修饰的是p1,因此不能改变p1的值,而*p1则没有限制
int* const p1 = &a;
//这里const修饰的*p2,因此不能修改*p2的值,而p则没有限制
int const *p2 = &a;
*p1 = 98;//预编译通过,不会报错
p1 = &b;//预编译不通过,报错
*p2 = 98;//预编译不通过,报错
p2 = &b;//预编译通过,不会报错
}
2.C++智能指针
C++中的动态内存管理是通过new和delete两个操作符来完成的。new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针。delete调用时,销毁对象,并释放对象所在的内存。
C++中常见的内存问题大致有:
- 缓冲区溢出(buffer overrun)
- 空悬指针/野指针(dangling pointer/wild pointer)
- 重复释放(double delete)
- 内存泄漏(memory leak)
- 不配对的new[]/delete
- 内存碎片(memory fragmentation)
这时使用智能指针就可以有效缓解这类问题。对于编译器来说,智能指针实际上是一个栈对象,在栈对象生命周期即将结束时,执政之真通过西沟函数释放它管理的堆内存。所有只能指针都被重载了“operate->”操作符,直接返回对象的引用,用以操作对象。访问只能指针原来的方法则是‘.’操作符。
访问只能指针包含的裸指针则可以用get()函数。由于只能指针是一个对象,所以if(my_smart_object)永远为真。要判断智能指针的裸指针是否为空,需要这样判断:if(my_smart_object.get())。
只能指针包含reset()函数,如果不传参数(或者传递NULL),则智能指针会释放房钱管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
2.1 std::auto_ptr
std::auto_ptr属于STL,当然在namespace std中,包含头文件#include便可以使用。std::auto_ptr能够方便的管理单个堆内存对象。auto_ptr是C++98提供的解决方案,C++11已经摒弃,并提供了另外两种解决方案,unique_ptr和shared_ptr。然而虽然auto_ptr被摒弃,但它已使用多年,同时如果编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。
例如:
#include <stdio.h>
#include <memory>
#include <iostream>
using namespace std;
class Simple {
public:
Simple(int param = 0) {
number = param;
cout << "Simple:" << number << endl;
}
~Simple() {
cout << "~Simple:" << number << endl;
}
void PrintSomething() {
cout << "PrintSomething:" << info_extend.c_str() << endl;
}
string info_extend;
int number;
};
int main() {
auto_ptr<Simple> my_memory(new Simple(1));
if (my_memory.get()) {
my_memory->PrintSomething();
my_memory.get()->info_extend = "Addition";
my_memory->PrintSomething();
(*my_memory).info_extend += "other";
my_memory->PrintSomething();
}
}
结果如下:
在上面的代码中,我们使用了new操作符来创建一个对象指针,但是并没有使用delete来销毁对象指针,可以看出auto_ptr可以自动调用对象的析构函数。
但是要注意的是不能使用一个auto_ptr对象去给两一个auto_ptr对象赋值,即在使用auto_ptr的时候,绝对不能用“operator=”操作符。
2.2unique_ptr
比auto_ptr要安全,它通过设置所有权让对象的析构函数只执行一次。其他用法和auto_ptr一样。
还没结束,后面再写。
2.3shared_ptr
参考博客:
C++ 智能指针详解
C++指针详解