1.第一部分,关于指针的基础
指针的定义语法
int * a=a;
(int*是一种数据类型)
指针内部储存着的是一块地址
可以借用指针的解引用操作,通过指针直接去访问一个变量
指针指向变量(存入地址)的时候,要在前面加入& 表示这个变量的地址,不加则代表直接对对应地址所在的变量进行修改(通过指针访问地址空间)
指针在输出的时候,*p代表解引用,直接代表那个被指向的空间
而单纯输出名字,只是输出指针下面储存的十六进制数字(地址字符串)
//1,关于指针的基础
typedef int* pointNeedle;
//pointNeedle a = &2;指针类型存的是地址,你不能拿没有开辟空间的东西来调戏他..
int a = 12;
pointNeedle a1 = &a;//往指针类型里面存入数据的时候,注意村也是只能存地址
//在变量前面加上一个&就代表这个玩应的地址
//而输出的时候有两种情况
//第一个,就是单纯输出指针里面的数值,也就是存在的地址
//
//第二个,前面加上指针进行解引用,输出会直接按着地址导向指针指着的东西
//其中第一个应该叫做解引用操作,*p直接导向p中所储存的地址,操作*p就可以顺着地址操作a;
//(指针解引用对于地址对应空间的数据的修改)
cout << "a=" << a << endl;
*a1 = 123;
cout << "a=" << a << endl;
cout << "*ai=" << *a1 << endl;
2.关于空指针和野指针
//2.关于空指针
//空指针指的是仅仅初始化没有赋值,null的指针
//实际上底层指向的是为0的空间,但是无权进行访问,必须再付给指针名称一个可以操控的地址
int* assss;
//3.关于野指针
//野指针指的是指向了一块非程序员申请的,而且无权操控的空间
//语法是不会出任何问题的,但是就不行.....
int* asssss = (int*)0x1234;
(对了强调一个事情,包括空指针指向的0地址,0-255地址中都代表系统的空间,除非砸开硬盘,无权修改)
空指针和野指针的共同特点就素,都不是程序员申请的空间,无法调用,因此尽量不要出现这东西
另外,如果是new创建出来的堆空间元素,必须进行手动删除,在Java里面可以使用jvm自动删除,但是cpp不行,所以程序员必须手动删除,另外新建出来的对象可以使用指针进行接收,释放的时候不需要解引用
int * arr=new int(6);
delete arr;
另外注意一点,删除的时候如果是数组,那么一定要全部删除!\
比如这种方式 ---delete p[];
不然只会删除首地址
3.关于指针和数组;
指针可以用来遍历数组,其中指针每次自增(++操作),都是偏移和数据类型一致的字节,宏观来看,无论是啥数据类型都是跳一个位置;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* point = arr;//数组名其实就是第一个的地址
for (int i = 1; i <= 10; i++) {
cout << *point << endl;
//因为这里是整形,所以指针自动移动四个字节
//和迭代器Iteritor还不一样
point++;
}
(1)关于这里和迭代器的区别,迭代器是插在两个数据中间的,判断方法是hasnext.
而指针在这里直接锁定再地址上,可以理解为直接定位再数据自己的头上
(2)关于遍历数组的时候发生越界,不会报错但是会输出这个数字
//-858993460//这个其实不是地址,也不是null,是被自动填充成的数字
(3)指针指向数组的时候有个建议就是,要么直接指向数组的第一个元素,要摸作为参数的时候直接传入地址名字(因为多维数组是不能与int*这种指针类型相互匹配的)
4.关于指针/地址作为参数传入,然后再方法体内部操控数据
void swap(int* p, int* b) {
int temp = *p;
*p = *b;
*b = temp;
}
int a2 = 12;
int b2 = 13;
swap(&a2, &b2);
cout << "a=" << a2 << endl;
cout << "b=" << b2 << endl;
//输出为13和12,顺序已经颠倒了
方法传入的是指针/地址,把ab两个数据的地址传入进去,就可以在sawp内部修改数值了
在swap方法内,对两个传入的地址进行解引用,就可以操作数值了
将变量的地址传入方法,让方法中和外面共用一个地址进行数据操作.java中只能通过封装,这里可以用指针调用基本数据类型,java基本做不到这一点.
(深刻理解一下解引用)!!!!!!
5.关于const修饰指针的几种情况
//1.先构建一个常量指针
//常量指针的特点,可以修改指向,但是不能修改指向的数值
//记忆方法:const修饰的是*,对于解引用进行限制,地址可以随意更换
//但是无法利用例如"*a=12"的解引用操作进行修改
const int* p1 = &a;
//2.再构建出一个指针常量
//指针常量的特点是,可以对于数值进行修改,但是不能修改这个指针的指向
//记忆方法为:const修饰的是p2,直接限制了里面储存的十六位地址不能动,但是可以按照这个死路进行修改
int* const p2 = &a;
//第三种,同时对解引用和内部地址进行限制的方法
//现在好了,指向和地址都不能修改了
const int* const p3 = &a;
(一个使用示例:利用指针传输数组头位的地址,在方法中对数组进行修改)
(注:只要得到数组头位置的地址,就可以对数组整体进行修改,数组名其实就是数组第零位的地址,所以这里传入的只是数组名,方法里面使用的时候也是直接用名字,无需解引用即可需改)
void bub(int * arr) {
//整个好像是做不到的 int arr2[] = * arr;
//这里好像传入首位的地址就能使用整个数组
//大概是数组的特性?只要得到第一位的地址就可以改动整个数组
//而且我是试了一下,做不到打开整个数组指针类型好像........
for (int i = 0; i < 9; i++) {
for (int j = 0; j< 10 - 1 - i; j++) {
if (arr[j] >= arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
int arr2[10] = {2,3,4,5,6,7,8,9,10,1};
bub(arr2);//在传入地址的时候直接传入数组第一位的地址就行
//不用传入整个数组的地址...............................
int* point2 = arr2;
for (int i = 1; i <= 10; i++) {
cout <<* point2 << endl;//这里记得要在读入数据的时候进行解引用
point2++;
}
6.关于结构体指针;
利用结构体指针访问结构体中的某个属性的时候,可以不解引用,直接用指针名字->属性名字
就可以调用了.(->,,,,貌似vs里面会自己跳出来,作用类似.)
struct student {
string name;
};
struct student arry[3] = {
{"dijia"},
{"daina"},
{"gaiya"}
};
student* point12 = arry;
------------------------------------------------------------
- //cpp没有提供查询数组长度的方法,所以我们应该是用的是 |
- int longlonglong = sizeof(arry) / sizeof(arry[0]); |
------------------------------------------------------------
for (int i = 1; i <= longlonglong; i++) {
//使用 指针名字->属性名字 就可以利用指针调用相应的属性了
cout << point12->name << endl;
point12++;
}
(其中这里还有一个查询数组的方法,总字节数除以每个元素的字节数,不过对于太复杂的数组不一定管用)
7.关于指针类型的强制转化
空类型指针void*,可以指向任何的数据类型
void* s;
//但是使用并不是无限制的
s = (int*) & a;
//这个让指针可以理解整数,这里要强转,不然s作为void指针是无法识别地址的
(类型 *)起到的是对地址/指针进行类型强转的作用
----------------------------------------------------------------------------
比较常用的就是"(void *)"代表转化成一个纯地址的类型
效果类似取地址符号(&);
--------------------------------------------------------------------------
再举个例子,野指针进行赋值的时候
int* s=(int*)0x22 ;//野指针也要指明是地址,不能用数值赋值
不能给指针输入一个野地址,这里一开始会默认是一个十六进制数字,必须指明这是一个int*类型的指针
8.关于解引用和自增运算符号的先后和连用
(1)先说结论,运算顺序为"前后分开看"
(一) (前自增/解引用)
前自增和解引用,谁先接触point谁先起作用,依次向外发挥作用
如果没有解引用,那么效果就是地址偏移,如果解引用已经发生,那么效果就是数值变化
(二)关于(后自增)
如果没有括号保护,都是对地址进行偏移
(2)
//其实一共只有这三种情况-------------------------------------------------
// =*p++ 赋值,然后地址发生偏移,
// =++*p 先解引用,再进行自增(数值)
// =*++p 先自增(地址),再进行解引用
// =(++p)* =p++*没有这种乱七八糟的东西orz;
(3)关于多重连用的一个举例
int a1[] = {3,4};
int* ass = &a1[0];
int bb = *ass++;//举个例子这样的,输出结果应该是五
//先进行地址自增,然后解引用,再对对应地址的数值进行自增
//先偏移到4,然后4自增输出5
cout << "结果就是" <
9.关于三种特殊的指针迭代方式
char arr[] = {'1','2'};
char* pp= arr;
(1)关于*(p+i)的形式
for (int i = 0; i <= 1; i++) {
cout << *(pp + i) << endl;
}
(2)关于指针直接自增p++
for (int i = 0; i <= 1;i++ ) {
cout << *(pp ) << endl;
pp++;
}
另一种友好写法*p++(先输出数值,再地址偏移)(实用)
for (int i = 0; i <= 1;i++ ) {
cout << *pp++ << endl;
}
(3)使用指针作为角标,这个
for (int i = 0; i <= 1;i++ ) {
cout << pp[i] << endl;
}
这里要注意一件事情,指针作为角标的时候
例如arr= 1 2 3 4 5 6 7
*point=arr[1]
则point[1]其实就是arr[2];
结构如下
----------------------------------------------------
arr[0] |
1 2 3 4 5
point point[1] |
(point[0])
---------------------------------------------------|
10,关于字符串使用指针
字符串使用指针的方法为,直接指向字符串的第一个字符
char* point = &str[0];//好家伙能用这种方法进行指向一个字符串
至少我的编译器做不到直接指向字符串名字,一般直接指向首地址
另外补充一个重点,一旦输出的是数组首地址的解引用,那么就会输出整体的字符串/数组全部元素
11.关于指向指针的指针
定义方式
//指向指针的指针
int a = 1;
//int** p = && a;这种操作是错的,不能连续取地址
int* i =&a;
//只能接受一个指针的地址,也要加上 &
int** p = &i;
//p--->i--->a
储存的是另一个指针所占用的地址
通过连续解引用,可以访问最下面的东西
举个例子,访问指针数组的时候
*(* (point + 1) + 2)--------其实就是point[1][2]
文字解读:首先point偏移一位,再解引用,就是第一行的首地址
第一行的首地址再偏移两位,再解引用,就是第一行第二个的东西
13.关于数组和指针有四种形式
int point[2][3];//单纯的,长度为12的一个连续的数组
int(*point)[3];//为指向数组的指针,n行3列的数组,内存必然连续
int* point [3];//为存了地址的数组,3行n列的数组,内存可以不连续
int **point;//指向指针的的指针,完全可以不连续,
阅读方法为*(*(point+1)+2) 其实就是point[1][2]
前面两个是内存连续的,但是后面两个是内存连续的
关于
int(*point)[3];//为指向数组的指针,n行3列的数组,内存必然连续
int* point [3];//为存了地址的数组,3行n列的数组,内存可以不连续
看似效果差不多但本质上完全不一样
前者是指向数组首地址的指针,
比如指向一个数组{123456}的时候,指针一开始指向的是1,但是一旦偏移一下指针,就指向4了
而后者则是一个储存地址的数组
见下表就算两者的区别
指针指针常见用法是这样的
string a1 = "jdjd";
string a2 = "jdjd";
string a3 = "jdjd";
char * ass[] = { &a1[0],&a2[0],&a3[0]};
char** as = ass;//这样存入的就是a1[0]这个地址
输出就是j