5. 指针 1. 指针的引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # include <iostream> 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 ; } OUT: 0xa1fe04 0xa1fe00 112 3.14
2. 左值和右值 一般说法,编译器为其单独分配了一块存储空间,可以取其地址的,左值可以放在赋值运算符左边;
右值是数据本身,不能取到其自身地址,右值只能赋值运算右边。
具体来说:
左值最常见的情况如函数和数据成员的名字;
右值是没有标识符、不可以取地址的表达式,一般称之为“临时对象”
&a
是允许的,而&(b+c)
不能通过编译,因此a是一个左值,(b+c)是一个右值
3. 指针的数组、数组的指针
指针的数组 array of pointers : T* t[]
数组的指针 a pointer to an array : T (*t) []
[]
优先级比较高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int main () { 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; } -OUT: 4 3.14 a int main () { int c[4 ] = {0x40000000 , 0x4FFFFFFF , 0x00000000 , 0x7FFFFFFF }; int * a[4 ]; int (*b) [4 ]; b = &c; for (unsigned int i = 0 ; i<4 ; i++) { a[i] = &(c[i]); } cout << *(a[0 ]) << endl; cout << *(b) [3 ] << endl; return 0 ; } -OUT: 1073741824 2147483647
4. const pointer 与 pointer to const 1 2 3 4 5 6 7 8 9 10 11 12 char strHelloworld[] = {"helloworld" }; char const *pStr1 = "helloworld" ;const char *pStr1 = "helloworld" ; char * const pStr2 = "helloworld" ;char const * const pStr3 = "helloworld" ;pStr1 = strHelloworld;
关于const
修饰部分:
看左侧最近的部分
如果左侧没有,则看右侧
5. 指针的指针 1 2 3 int a = 12 ;int * b = &a;int ** c = &b;
*
操作符具有从右向左的结合性, **
相当于*(*c)
,必须从里向外逐层求值,*c
是c指向的位置,即b;**c
相当于 *b
,得到变量a的值。
6. NULL int* a = NULL
一个特殊的指针变量,表示不指向任何东西
它给了一种方法,来表示特定的指针目前未指向任何东西
使用注意事项:
对于一个指针,如果已经知道江北初始化为什么样的地址,那么请赋给它这个地址值,否则把它设为NULL
在对一个指针进行间接引用前,请先判断这个指针的值是否为NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int a = 123 ; int *b = &a; int **c = &b; int *pA = NULL ; pA = &a; if (pA != NULL ){ cout << (*pA) << endl; } pA = NULL ; return 0 ; } OUT: 123
杜绝“ 野 ”指针
指向“垃圾”内存的指针。if判断对其不起作用,因为不为NULL。
一般有三种情况:
指针变量没有初始化
已经释放不用的指针没有置NULL,如delete和free之后的指针
指针操作超越了变量的作用范围
Note: 没有初始化的,不用的或者超出范围的指针请把值置为NULL。
7. 指针相关操作符 & 与 *
的比较:
左值取地址,右值取地址里的内容。
左值和右值??
8. ++ 与 - - 操作符
++ i
: 先++, 再赋值给i
i++
: 先赋值给i, 再++
++i和i++区别???
1 2 3 int a = 1 ; b = 2 ; c;c = a+++b; d = a++++b;
9. 内存分析 1.栈和队列
栈区(stack)——由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap)—— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
全局区(静态区)(static)——,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
文字常量区 ——常量字符串就是放在这里的。 程序结束后由系统释放
程序代码区——存放函数体的二进制代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <string> int a = 0 ; int * p1; int main () { int b=1 ; char s[] = "abc" ; int *p2=NULL ; char *p3 = "123456" ; static int c = 0 ; p1 = new int (10 ); p2 = new int (20 ); char * p4 = new char [7 ]; strcpy_s (p4, 7 , "123456" ); if (p1 != NULL ) { delete p1; p1 = NULL ; } if (p2 != NULL ) { delete p2; p2 = NULL ; } if (p4 != NULL ) { delete [ ] p4; p4 = NULL ; } return 0 ; }
堆heap——动态分配资源 :
从现代语言的编程角度看,使用堆,或者说使用内存分配是很自然的;
动态内存带来了不确定性:内存分配耗时要多久?失败了怎么办?在实时性比较高的场合,如嵌入式控制器和电信设备。
一般地,当我们在堆上分配内存时,很多语言使用new这样的关键字,有些语言则是隐式分配。在c++中new的对应词是delete,因为c++是可以让程序员完全结果内存的分配释放的。
2. 资源管理方案——RAII Resource Acquisition Is Initialization:
C++所特有的资源管理方式。主流语言中,C++是唯一一个依赖RAII做资源管理的
RAII依托栈和析构函数,来对所有资源——包括堆内存在内进行管理。
RAII有些比较成熟的智能指针代表:std::auto_ptr
和boost::shared_ptr
3. C++ 中栈和堆的对比
stack
heap
作用域
函数体内,语句块{}作用域;
整个程序范围内,由new,malloc开始,delete, free结束
编译期间大小确定
变量大小范围确定
变量大小范围不确定,需要运行期确定
大小范围
win:1M,linux默认8M或10M,可以用ulimit -s查询
所有系统的堆空间大小是接近内存(虚拟内存)的总大小的(一部分被OS占用)
内存分配方式
地址由高到低减少
地址由低到高增加
内容是否可变
可变
可变
4. 内存泄漏 定义 :
程序中已动态分配的堆内存由于某种原因程序未释放或无法释放 ,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果 。
原因和排查方式 :
内存泄漏主要发生在堆内存分配方式中,即“配置内存后,所有指向该内存的指针都遗失了”。若缺乏语言的垃圾回收机制,这样的内存就无法归还系统。
因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判别和诊断。
10. C++的智能指针 C++中推出了四种常用的智能指针:
unique_ptr
shared_ptr
weak_ptr
: C++ 11中已经被遗弃
auto_ptr
:C++ 17中正式删除。
从应用方面来分析这几种智能指针:
应用场景
性能分析
1. auto_ptr 由new expression 获得对象,在auto_ptr 对象销毁是,其所管理的对象也会被自动delete掉。
所有权转移 :不小心把它传递给另外的智能指针,原来的指针就不在拥有这个对象了。转交给新对象,然后再将原对象指针置为nullptr。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <string> #include <iostream> #include <memory> using namespace std;int main () { { auto_ptr<int > pl (new int (10 )) ; cout << *pl << endl; 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 ("GO" )) }; cout << "There are some computer languagues here: \n" ; for (int i=0 ; i < 2 ; ++i){ cout << *languages[i] <<endl; } auto_ptr<string> pC; pC = languages[2 ]; cout << "There are some computer languagues here: \n" ; for (int i=0 ; i < 2 ; ++i){ cout << *languages[i] <<endl; } cout << "The winner is " << *pC << endl; } return 0 ; } —————————————————————————————— 10 There are some computer languagues here: C Java There are some computer languagues here: C Java The winner is C++
2. unique_ptr unique_ptr
:是专属所有权,所以unique_ptr
管理的内存,只能被一个对象持有,不支持复制和赋值。
移动语义:unique_ptr
禁止了拷贝语义,但有时我们也需要能够转移所有权,用std::move()
进行控制所有权的转移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <memory> using namespace std; int main () { auto w = std::make_unique <int >(10 ); cout << *(w.get ()) << endl; auto w2 = std::move (w); cout << ((w.get () != nullptr )? (*w.get ()):-1 ) << endl; cout << ((w2.get () != nullptr )?(*w2.get ()):-1 ) << endl; return 0 ; } —————————————————————————————————————— 10 -1 10
cout << ((w.get() != nullptr)? (*w.get()):-1) << endl
三目运算符, (w.get() != nullptr)?
成立吗?成立取:前面,否则取:后面。
3. shared_ptr shared_ptr
通过一个引用计数共享一个对象。
shared_ptr
是为了解决auto_ptr
在对象所有权上的局限性,在使用引用计数的机制 上提供了可以共享所有权的智能指针,当然也需要额外的开销。
当引用计数为0时,该对象没有被使用,可以进行析构 。
4. weak_ptr weak_ptr
被设计为与 shared_ptr
共同工作,用一种观察这模式工作。
作用:协作 shared_ptr
工作,可获得资源的观测权,像旁观者那样观测资源使用情况。
意味着 weak_ptr
只对 shared_ptr
进行引用,而不改变其引用计数,当被观察的shared_ptr
失效后,相应的 weak_ptr
也相应失效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <memory> using namespace std; int main () { { auto wA = shared_ptr <int >(new int (20 )); { auto wA2 = wA; cout << ((wA2.get () != nullptr )? (*wA2.get ()):-1 ) << endl; cout << ((wA.get () != nullptr )? (*wA.get ()):-1 ) << endl; cout << wA2.use_count () << endl; cout << wA.use_count () << endl; } cout << wA.use_count () << endl; } auto wAA = std::make_shared <int >(30 ); auto wAA2 = std::move (wAA); cout << ((wAA.get () != nullptr )? (*wAA.get ()):-1 ) << endl; cout << ((wAA2.get () != nullptr )? (*wAA2.get ()):-1 ) << endl; cout << wAA.use_count () << endl; cout << wAA2.use_count () << endl; return 0 ; } ---------------------------------------- 20 20 2 2 1 -1 30 0 1
11. C++的引用 1.引用 引用: 是一种特殊的指针,不允许修改的指针。
使用指针的坑:
空指针
野指针
不知不觉改变了指针的值,却继续引用
引用:
不存在空引用
必须初始化
一个引用永远指向它初始化的对象
2. 基本使用 引用:可以认为是指定变量的别名,使用是可以认为是变量本身。
int x = 1;
int &rx = x;
这两句中&是引用,注意与指针的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <assert.h> using namespace std;void swap (int &a, int &b) { int temp; temp = a; a = b; b = temp; } void swap2 (int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } 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 ); swap2 (&a, &b); assert (a==3 &&b==4 ); swap (rx, x); cout << x << endl; cout << rx << endl; rx = x2 + 1 ; swap (rx, x); cout << x << endl; cout << rx << endl; }
3. C++的引用的作用
有了指针为什么还要引用?——为了支持运算符重载;
有了引用为什么还需要指针?——为了兼容c语言