PART 1:指针的概念
计算机体系中的内存存储层次:
- 寄存器
- L1缓存
- L2缓存
- 主内存
- 本地存储
- 远程存储(分布式,web server)
C++ 中内存单元内容与地址:
- 内存由很多的内存单元组成。这些内存单元用于存放各种类型的数据。
- 计算机对内存的每个内存单元都进行了编号,这个编号叫做内存地址,地址决定了内存单元在内存中的地址。
- 记住这些内容单元的地址不方便(对于人是不现实的),于是C++语言的编译器让我们可以通过名字来访问这些内存的地址。
- 这些访问地址的方法就是平时用的变量,如
1 2 3 4 |
int a = 112, b = -1; float c =3.14; int* d = &a; float* e = &c; |
以上的这些变量在内存中都对应了一个位置并且在这些位置上存储了数据,这些地址每次程序跑的时候都不一样,因此我们需要变量名来定义这个地址。
对于指针来说存储的就是所指向的变量的位置,及d和e如果直接取值的话取出来的是一个地址,这两个地址分别是a和 c,这样的变量叫做指针变量。
指针的定义和间接访问操作:
- 指针定义的基本形式:指针本身就是一个变量,其符合变量定义的基本形式,它存储的是值的地址。对类型T,T*是“到T的指针”类型,一个类型为T*的变量能保存一个类型T的对象(指的是广义的C++的类型)的地址。如: int a = 112; int* d = &a; (*指针 &去地址符)即在指针定义的时候把a的地址取出来赋值给指针。
- 通过一个指针访问它所指向的地址的过程称为间接访问,或者引用指针,这个用于执行简介访问的操作符号*,如: count << (*d) << endl,
EXAMPLE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main() { int a = 122, b = -1; float c = 3.14f; int* d = &a; // 将a的地址赋值给指针变量d float* e = &c; // 将c的地址赋值给指针变量e cout << d << endl; //001AF954 // a的地址 cout << e << endl; //001AF93C // c的地址 cout << (*d) << endl; // 122 a的值 cout << (*e) << endl; //3.14 e的值 return 0; } |
关于变量,地址和指针变量小结:
一个变量有三个重要的信息:
- 变量的地址位置;
- 变量所存的信息;
- 变量的类型;
指针变量是一个专门用来记录变量的地址的变量;通过指针变量可以简介的访问另一个变量的值(要注意另一个变量的值可能是另一个指针,这个叫做多级指针
PART2: C++ 原生指针
左值和右值:
概念:一般说法,编译器为其单独分配了一块存储空间,可以取其地址的,左值可以放在赋值运算符左边;右值指的是数据本身,不能取其自身地址,右值只能赋值运算右边;
具体分析:
左值最常见的情况如函数和数据成员的名字;右值是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。
比如: a=b+c; &a是允许的操作符,而&(b+c)不能通过编译,因此a是一个左值,而 (b+c)是一个右值;
C++中的原始指针种类:
1.一般类型指针T*
T是一个泛型,泛指任何一种类型; 如:
1 2 3 4 5 6 7 8 9 |
int i = 4; int *iP = &i; // 这里的*是指针的定义 cout << (*iP) << endl; // 4 double d = 3.14; double* dP = &d; cout << (*dP) << endl; // 3.14 char c = 'a'; char* cP = &c; cout << (*cP) << endl; // a |
2.指针的数组,与数组的指针
指针的数组 T* t[ ] 仍然是一个数组,里面的每一个值都是一个指针
数组的指针 T(*t) [ ] 指针指向的是一个数组,数组中的每一个指针是一个值
1 2 |
int* a[4]; // 指针的数组,array of pinter每个元素都是指针 int(*b)[4]; // 数组的指针,a pointer to an array每个元素都是一个值,这个必须()因为[]优先级更高 |
3. const pointer 与 pointer to const
关于const修饰部分:
- 看左侧最近的部分
- 如果左侧没有,则看右侧
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 |
#include <iostream> using namespace std; unsigned int MAX_LEN = 11; int main() { char strHelloWorld[] = { "helloworld" }; // const左侧是char 所以修饰的就是char // 这个指针指向的内容是可变的,即指向的地址是可变的 // 但是指向的空间存储的区域里面的内容是不可改变的 char const *pStr1 = "helloworld"; // 左侧的修饰的是指针 // 因此指向的区域指向不允许发生变化 char* const pStr2 = strHelloWorld; // pStr3指向的地址一旦指向不允许改变 // 其指向空间的内容也不允许改变 char const * const pStr3 = "helloworld"; pStr1 = strHelloWorld; //pStr2 = strHelloworld; // pStr2不可修改 //pStr3 = strHelloworld; // pStr3不可修改 // 对数组进行遍历 unsigned int len = strnlen_s(pStr2, MAX_LEN); cout << len << endl; // 10 for (unsigned int index = 0; index < len; ++index) { // pStr1[index] += 1; // pStr1不可修改 pStr2[index] += 1; // pStr3[index] += 1; //pStr3不可以修改 } } |
4. 指向指针的指针
*操作符具有从右向左的结合性:
**这个表达式相当于*(*c),必须从里向外逐层求值;
*c得到的是c指向的位置,即b;
**c相当于*b,得到标量a的值
表达式 表达式的值
a 12
b &a
*b a, 12
c &b
*c b,&a
**c *b,a,12
5. 指向指针的指针
& 与 *操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> 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; } |
++与–操作符
char* cp2 = ++cp;
char* cp3 = cp++;
++的优先级高于间接引用*的优先级
*++cp *cp++
6.一些其他的指针
- 野指针
未初始化和非法的指针:
1 2 3 4 5 6 7 8 9 |
#include <iostream> using namespace std; int main() { int *a; *a = 12; // 野指针 } |
可以看到在一块空间区域中有一个指针变量,这个变量是一个左值,但没有初始化,也没有做赋值操作就间接的应用它,相当于我知道有一个空间但是我不知道这个空间在哪,我就对这个空间做一个赋值操作。这个时候就出现一个问题,这个a这里指向哪里?运气好的话定义到一个非法的地址,程序会出错,从而终止;最坏的情况,定位到一个可以访问的地址,无意间修改了它,这样的错误可能与原先用于操作的代码完全不相干,不会报任何错误如何发生的怎么发生的很难追踪到问题出现的源头。因此用指针进行间接访问之前一定要非常小心,确保它已经初始化并被恰当的赋值。
在使用指针的过程中要杜绝野指针,即指向垃圾内存的指针。if等判断对它们不起作用,因为没有置为NULL;
一般有三种情况:
- 指针变量没有初始化;
- 已经释放不用的指针没有置为NULL,如delete和free之后的指针;
- 指针操作超越了变量的作用范围;
指针使用的注意事项:没有初始化的,不用的或者超出范围的指针请把值置为NULL。
- NULL指针
一个特殊的指针变量,表示不指向任何动int *a = NULL,一般用于之前有一个指针,现在使用这个指针了就让他指向NULL,即不指向任何地址,它给了一种方法,来表示特定的指针目前未指向任何东西。要注意,对于一个指针,如果已经知道将被初始化为什么地址,那么请赋予它这个地址值,否则请把ta设置为NULL;在对一个指针进行间接引用前,请先判断这个指针是否为NULL
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 = 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; } |
7.关于++++, —-等运算符
编译器分解成符号的方法是:一个字符一个字符的读入,如果该字符可能组成一个符号,那么读入下一个字符,一直到读入的字符不再能组成一个有意义的符号。这个过程称为“贪心法”。
例如:
int a = 1, b=2; c;
c = a+++b; //相当于a++ +b 所以C=3
d = a++++b; // 相当于a++ ++b, error,因为两者直接没有任何运算
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 |
#include <iostream> using namespace std; 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; char ch5 = ++*++cp; return 0; } |
PART3: C++ 程序的存储区域划分
栈的特点:先进后出
队列的特点:先进先出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> #include <string> using namespace std; int a = 0; // GAVR 全局初始化区 int* p1; // bass 全局未初始化区 int main() { int b=1; // stack 栈区变量 char s[] = "abc"; // stack 栈区变量 int*p2 = NULL; // stack 栈区变量 const 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代码区 return 0; // test代码区 } |
C++内存分为以下的5种:
- 栈区(Stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限,其内存增长方式按内存地址由高到低方向生长,属于动态分配内存。
- 堆区(Heap):堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大,一般由malloc、new创建,属于动态分配内存。每个线程都有自己的栈,但是堆空间都是公用的。
- bss段:通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了,属于静态分配内存。
- data段(又叫GVAR):有的地方叫GVAR(global value),用来存放程序中已经初始化的非零全局变量。其中data有分为可读写区域和只读区域。读写区域(RW):存放非零全局变量、静态变量,只读区域(RO):存放常量
- text段:也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为只读区域,并且为共享的,当有多个相同进程(Process)存在时,共用同一个text段。