int a = 112, b = -1;
float c = 3.14;
int *d = &a;
float *e = &c;
指针定义的基本形式:指针本身就是一个变量,其符合变量定义的基本形式,它存储的是值的地址。对类型T,T*是"到T的指针"类型,一个类型为T*的变量能保存一个类型T的对象的地址.
通过指针访问它所指向的地址的过程称为间接访问或者引用指针;
这个用于执行间接访问操作符是单目操作符*; cout << *d << endl;
#include
using namespace std;
int main()
{
int a = 112, b = -1;
float c = 3.14f;
int* d = &a;
float* e = &c;
cout << d << endl; cout << e << endl;
cout << (*d) << endl; cout << (*e) << endl;
return 0;
}
关于变量、地址和指针变量:
一个变量的三个重要信息:
指针变量是一个专门用来记录变量的地址的变量;通过指针变量可以间接的访问另一个变量的值。
如过是数组,数组地址是不可改变的,但是数组值是可以改变的
指针地址是可变的,指针值是否可改变取决于所指的区间的存储区域是否可变
#include
using namespace std;
int main()
{
// T*: 注意*在定义和间接访问上的作用
//int i = 4; int* iP = &i; cout << (*iP) << endl;
//double d = 3.14; double* dP = &d; cout << (*dP) << endl;
//char c = 'a'; char* cP = &c; cout << (*cP) << endl;
// array of pointers和a pointer to an array
int c[4] = { 0x80000000, 0xFFFFFFFF, 0x00000000, 0x7FFFFFFF };
int* a[4]; // array of pointers 指针的数组
int(*b)[4]; // a pointer to an array 数组的指针
b = &c; // 注意:这里数组个数得匹配
// 将数组c中元素赋给数组a
for (unsigned int i = 0; i<4; i++)
{
a[i] = &(c[i]);
}
// 输出看下结果
cout << *(a[0]) << endl; // -2147483648
cout << (*b)[3] << endl; // 2147483647
return 0;
}
概念:
一般说法,编译器为其单独分配了一块存储空间,可以取其地址的,左值可以放在赋值运算符左边;
右值指的是数据本身,不能取到自身地址,右值只能放在赋值运算符右边。
具体分析:
左值最常见的情况如函数和数据成员的名字;
右值是没有标识符、不可取地址的表达式,一般称之为临时对象。
T是一个泛型,泛指任何一种类型
指针的数组与数组的指针
指针的数组 T *t[] 数组的指针 T(*t)[]
const pointer和pointer to const
#include
using namespace std;
unsigned int MAX_LEN = 11;
int main()
{
char strHelloworld[] = { "helloworld" };
char const* pStr1 = "helloworld"; // const char*
char* const pStr2 = strHelloworld;
char const* const pStr3 = "helloworld"; // const char* const
pStr1 = strHelloworld;
//pStr2 = strHelloworld; // pStr2不可改
//pStr3 = strHelloworld; // pStr3不可改
unsigned int len = strnlen_s(pStr2, MAX_LEN);
cout << len << endl;
for (unsigned int index = 0; index < len; ++index)
{
//pStr1[index] += 1; // pStr1里的值不可改
pStr2[index] += 1;
//pStr3[index] += 1; // pStr3里的值不可改
}
return 0;
}
指向指针的指针
例子:
int a = 123;
int *b = &a;
int **c = &b;
未初始化和非法指针(野指针)
例子:int *a; // a的指向不明,可能会导致程序崩溃。
*a = 12;
最坏的情况是定位到了一个可访问的地址,无意间修改了它,这样的错误难以捕捉,引发的错误可能与原先用于操作的代码完全不相干。
NULL指针
一个特殊的指针变量,表示不指向任何东西 int *a = NULL;
杜绝野指针:
指向一堆垃圾内存的指针。if等判断对它们不起作用,因为没有设置为NULL
一般有三种情况:
使用注意事项:
#include
using namespace std;
int main()
{
// 指针的指针
int a = 123;
int* b = &a;
int** c = &b;
// NULL 的使用
int* pA = NULL;
pA = &a;
if (pA != NULL) // 判断NULL指针
{
cout << (*pA) << endl;
}
pA = NULL; // pA不用时,置为NULL
return 0;
}
#include
using namespace std;
int main()
{
char ch = 'a';
// &操作符
//&ch = 97; // &ch左值不合法
char* cp = &ch; // &ch右值
//&cp = 97; // &cp左值不合法
char** cpp = &cp; // &cp右值
// *操作符
*cp = 'a'; // *cp左值取变量ch位置
char ch2 = *cp; // *cp右值取变量ch存储的值
//*cp + 1 = 'a'; // *cp+1左值不合法的位置
ch2 = *cp + 1; // *cp+1右值取到的字符做ASCII码+1操作
*(cp + 1) = 'a'; // *(cp+1)左值语法上合法,取ch后面位置
ch2 = *(cp + 1); // *(cp+1)右值语法上合法,取ch后面位置的值
return 0;
}
int main()
{
char ch = 'a';
char* cp = &ch;
// ++,--操作符
char* cp2 = ++cp;
char* cp3 = cp++;
char* cp4 = --cp;
char* cp5 = cp--;
// ++ 左值
//++cp2 = 97;
//cp2++ = 97;
// *++, ++*
*++cp2 = 98;
char ch3 = *++cp2;
*cp2++ = 98;
char ch4 = *cp2++;
// ++++, ----操作符等
int a = 1, b = 2, c, d;
//c = a++b; // error
c = a++ + b;
//d = a++++b; // error
char ch5 = ++*++cp;
return 0;
}
#include
int a = 0; //(GVAR)全局初始化区
int* p1; //(bss)全局未初始化区
int main() //(text)代码区
{
int b=1; //(stack)栈区变量
char s[] = "abc"; //(stack)栈区变量
int*p2=NULL; //(stack)栈区变量
char *p3 = "123456"; //123456\0在常量区, p3在(stack)栈区
static int c = 0; //(GVAR)全局(静态)初始化区
p1 = new int(10); //(heap)堆区变量
p2 = new int(20); //(heap)堆区变量
char* p4 = new char[7]; //(heap)堆区变量
strcpy_s(p4, 7, "123456"); //(text)代码区
//(text)代码区
if (p1 != NULL)
{
delete p1;
p1 = NULL;
}
if (p2 != NULL)
{
delete p2;
p2 = NULL;
}
if (p4 != NULL)
{
delete[ ] p4;
p4 = NULL;
}
//(text)代码区
return 0; //(text)代码区
}
从高地址到低地址
栈空间 - 从高到低
堆空间 - 从低到高
动态分配资源 – 堆(heap)
内存分配和回收原则:
程序通常需要牵涉到三个内存管理器操作:
这个回收策略需要实现实时性、额外开销等各方面的平衡,很难有统一和高效的做法;
C++做了1, 2 两件事;java做了1,3两件事。
栈和堆中的变量对比:
栈区 | 堆区 | |
---|---|---|
作用域 | 函数体内,语句块{}作用域 | 整个程序范围内,由 new malloc开始 , delete free结束 |
编译期间大小确定 | 由变量大小范围确定 | 变量大小范围不确定,需要运行时确定 |
大小范围 | windows默认是1M linux是8M或者10M(可调整) | 所有系统的堆内存空间上限是接近内存(虚拟内存)的总大小的(一部分被OS占用) |
内存分配方式 | 地址由高到低 | 地址由低到高 |
内容是否可变 | 可变 | 可变 |
全局静态存储区和常量存储区的变量对比:
全局经验存储区 | 常量存储区 | |
---|---|---|
存储内容 | 全局变量,静态变量 | 常量 |
编译期间大小是否确定 | 确定 | 确定 |
内容是否可变 | 可变 | 不可变 |
指的是程序中已经分配的堆内存由于某种原因程序未释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
使用指针是非常危险的行为,可能存在空指针,野指针的问题,并且可能造成内存泄漏问题。可指针又非常高效,所以我们希望以更安全的方式来使用指针。
unique_ptr、share_ptr、weak_ptr和C++11中已经废弃的auto_ptr(C++17中删除)
由new expression获得对象,在auto_ptr对象销毁时,它所管理的对象也会被自动delete掉
所有权转移:不小心把它传递给另外的智能指针,原来的指针就不再拥有这个对象了,在拷贝/赋值的过程中,会直接剥夺指针对内存的控制权,转交给新对象,然后再将原对象置为nullptr
#include
#include
#include
using namespace std;
int main()
{
{// 确定auto_ptr失效的范围
// 对int使用
auto_ptr<int> pI(new int(10));
cout << *pI << endl; // 10
// auto_ptr C++ 17中移除 拥有严格对象所有权语义的智能指针
// auto_ptr原理:在拷贝 / 赋值过程中,直接剥夺原对象对内存的控制权,转交给新对象,
// 然后再将原对象指针置为nullptr(早期:NULL)。这种做法也叫管理权转移。
// 他的缺点不言而喻,当我们再次去访问原对象时,程序就会报错,所以auto_ptr可以说实现的不好,
// 很多企业在其库内也是要求不准使用auto_ptr。
auto_ptr<string> languages[5] = {
auto_ptr<string>(new string("C")),
auto_ptr<string>(new string("Java")),
auto_ptr<string>(new string("C++")),
auto_ptr<string>(new string("Python")),
auto_ptr<string>(new string("Rust"))
};
cout << "There are some computer languages here first time: \n";
for (int i = 0; i < 5; ++i)
{
cout << *languages[i] << endl;
}
auto_ptr<string> pC;
pC = languages[2]; // languges[2] loses ownership. 将所有权从languges[2]转让给pC,
//此时languges[2]不再引用该字符串从而变成空指针
cout << "There are some computer languages here second time: \n";
for (int i = 0; i < 2; ++i)
{
cout << *languages[i] << endl;
}
cout << "The winner is " << *pC << endl;
//cout << "There are some computer languages here third time: \n";
//for (int i = 0; i < 5; ++i)
//{
// cout << *languages[i] << endl;
//}
}
return 0;
}
unique_ptr是专属版本,所以unique_ptr管理的内存,只能被一个对象持有,不支持复制和赋值
移动语义:unique_ptr禁止了拷贝语义,但有时我们也需要能够转移所有权,于是提供了移动语义,即可以使用std::move()进行控制所有权的转移。
#include
#include
using namespace std;
int main()
{
// 在这个范围之外,unique_ptr被释放
{
auto i = unique_ptr<int>(new int(10));
cout << *i << endl;
}
// unique_ptr
auto w = std::make_unique<int>(10);
cout << *(w.get()) << endl; // 10
//auto w2 = w; // 编译错误如果想要把 w 复制给 w2, 是不可以的。
// 因为复制从语义上来说,两个对象将共享同一块内存。
// unique_ptr 只支持移动语义, 即如下
auto w2 = std::move(w); // w2 获得内存所有权,w 此时等于 nullptr
cout << ((w.get() != nullptr) ? (*w.get()) : -1) << endl; // -1
cout << ((w2.get() != nullptr) ? (*w2.get()) : -1) << endl; // 10
return 0;
}
share_ptr通过一个引用计数共享一个对象。
share_ptr是为了解决auto_ptr在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销。
当引用计数为0的时候,该对象没有被使用,可以进行析构。
weak_ptr被设计为与share_ptr共同工作,用一种观察者模式工作。
作用是协助share_ptr工作,可获得资源的观测权,像旁观者那样观测资源使用情况。观察者意味着weak_ptr只对share_ptr进行引用,而不改变其引用计数,当被观察的share_ptr失效后相应的weak_ptr也会失效。
#include
#include
using namespace std;
int main()
{
shared_ptr
//{
// //shared_ptr 代表的是共享所有权,即多个 shared_ptr 可以共享同一块内存。
// auto wA = shared_ptr(new int(20));
// {
// auto wA2 = wA;
// cout << ((wA2.get() != nullptr) ? (*wA2.get()) : -1) << endl; // 20
// cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl; // 20
// cout << wA2.use_count() << endl; // 2
// cout << wA.use_count() << endl; // 2
// }
// //cout << wA2.use_count() << endl;
// cout << wA.use_count() << endl; // 1
// cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl; // 20
// //shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,
// // 引用计数会 + 1。当一个 shared_ptr 离开作用域时,引用计数会 - 1。
// // 当引用计数为 0 的时候,则 delete 内存。
//}
// move 语法
auto wAA = std::make_shared<int>(30);
auto wAA2 = std::move(wAA); // 此时 wAA 等于 nullptr,wAA2.use_count() 等于 1
cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl; // -1
cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl; // 30
cout << wAA.use_count() << endl; // 0
cout << wAA2.use_count() << endl; // 1
//将 wAA 对象 move 给 wAA2,意味着 wAA 放弃了对内存的所有权和管理,此时 wAA对象等于 nullptr。
//而 wAA2 获得了对象所有权,但因为此时 wAA 已不再持有对象,因此 wAA2 的引用计数为 1。
return 0;
}
#include
#include
#include
using namespace std;
struct B;
struct A {
shared_ptr<B> pb;
~A()
{
cout << "~A()" << endl;
}
};
struct B {
shared_ptr<A> pa;
~B()
{
cout << "~B()" << endl;
}
};
// pa 和 pb 存在着循环引用,根据 shared_ptr 引用计数的原理,pa 和 pb 都无法被正常的释放。
// weak_ptr 是为了解决 shared_ptr 双向引用的问题。
struct BW;
struct AW
{
shared_ptr<BW> pb;
~AW()
{
cout << "~AW()" << endl;
}
};
struct BW
{
weak_ptr<AW> pa;
~BW()
{
cout << "~BW()" << endl;
}
};
void Test()
{
cout << "Test shared_ptr and shared_ptr: " << endl;
shared_ptr<A> tA(new A()); // 1
shared_ptr<B> tB(new B()); // 1
cout << tA.use_count() << endl;
cout << tB.use_count() << endl;
tA->pb = tB;
tB->pa = tA;
cout << tA.use_count() << endl; // 2
cout << tB.use_count() << endl; // 2
}
void Test2()
{
cout << "Test weak_ptr and shared_ptr: " << endl;
shared_ptr<AW> tA(new AW());
shared_ptr<BW> tB(new BW());
cout << tA.use_count() << endl; // 1
cout << tB.use_count() << endl; // 1
tA->pb = tB;
tB->pa = tA;
cout << tA.use_count() << endl; // 1
cout << tB.use_count() << endl; // 2
}
int main()
{
Test();
Test2();
return 0;
}
引用是什么?变量的别名,是一种特殊的指针,不允许修改的指针。
使用指针有哪些坑:
使用引用则可以:
有了指针为什么还要有引用?
为了支持运算符重载。
有了引用为什么还需要指针?
为了兼容C语言。
#include
#include
using namespace std;
// 编写一个函数,输入两个int型变量a,b
// 实现在函数内部将a,b的值进行交换。
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void swap2(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
//int x = 1, x2 = 3;
//int& rx = x;
//rx = 2;
//cout << x << endl;
//cout << rx << endl;
//rx = x2;
//cout << x << endl;
//cout << rx << endl;
// 交换变量的测试
int a = 3, b = 4;
swap(a, b);
assert(a == 4 && b == 3);
a = 3, b = 4;
swap2(&a, &b);
assert(a == 4 && b == 3);
return 0;
}
补充:(Effective C++)