指针强化
铁律1:指针是一种数据类型
1)指针也是一种变量,占有内存空间,用来保
存内存地址
测试指针变量占有内存空间大小
2)*p操作内存
在指针声明时,*号表示所声明的变量为指针
在指针使用时,*号表示 操作 指针所指向的内存空间中的值
*p相当于通过地址(p变量的值)找到一块内存;然后操作内存
*p放在等号的左边赋值(给内存赋值)
*p放在等号的右边取值(从内存获取值)
3)指针变量和它指向的内存块是两个不同的概念
//含义1 给p赋值p=0x1111; 只会改变指针变量值,不会改变所指的内容;p = p +1; //p++
//含义2 给*p赋值*p='a'; 不会改变指针变量的值,只会改变所指的内存块的值
//含义3 =左边*p 表示 给内存赋值, =右边*p 表示取值 含义不同切结!
//含义4 =左边char *p
//含义5保证所指的内存块能修改
4)指针是一种数据类型,是指它指向的内存空间的数据类型
含义1:指针步长(p++),根据所致内存空间的数据类型来确定
p++=è(unsigned char )p+sizeof(a);
结论:指针的步长,根据所指内存空间类型来定。
注意: 建立指针指向谁,就把把谁的地址赋值给指针。图和代码和二为一。
不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系)。
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main81()
{
int a = 10;
char *p1 = 100; //分配4个字节的内存
char ****p2 = 100;
int *p3 = NULL;
p3 = &a;
*p3 = 20; //间接的修改a的值
//*就像一把钥匙 通过一个地址(&a),去修改a变量的标示的内存空间
{
int c = 0;
c = *p3; //c=20
//*p放在=号左边 写内存
//*p放=号的右边 读内存
printf("c:%d \n", c);
}
{
char *p4 = NULL;
p4 = (char *)malloc(100);
p4 = (char *)malloc(200); //0xcc11
}
printf("a:%d , p1:%d , p2: %d", sizeof(a), sizeof(p1), sizeof(p2));
printf("hello...\n");
system("pause");
return ;
}
char *getStr81()
{
char *tmp = NULL;
tmp = "abcdefgf";
return tmp;
}
/*
int getABC1(char *p1); int getABC1(char* p1);
int getABC2(char ** p2); int getABC2(char * *p2); int getABC2(char **p2);
int getABC3(char ***p3);
int getABC4(char (*p4)[30]); int getABC4(char (*p4) [30]);
int getABC5(char p5[10][30]); int getABC5(char p5[10][30]);
//指针做函数参数 形参有多级指针的时候,
//站在编译器的角度 ,只需要分配4个字节的内存(32bit平台)
//当我们使用内存的时候,我们才关心指针所指向的内存 是一维的还是二维的
*/
void main()
{
char *p = getStr81();
printf("p:%s \n", p);
*(p+2) = 'r'; //经常出现的错误 保证指针所指向的内存空间 可以被修改
system("pause");
return ;
}
铁律2:间接赋值(*p)是指针存在的最大意义
1)两码事:指针变量和它指向的内存块变量
2)条件反射:指针指向某个变量,就是把某个变量地址否给指针
3)*p间接赋值成立条件:3个条件
a)2个变量(通常一个实参,一个形参)
b) 建立关系,实参取地址赋给形参指针
c)*p形参去间接修改实参的值
//abc这3个条件 写在有一个函数
//ab 写在一块 c 单独写在另外一个函数里面 =====>函数调用
//a bc写在一块引用
Int iNum = 0; //实参 int *p = NULL; p = &iNum; iNum = 1; *p =2 ; //通过*形参 == 间接地改变实参的值 *p成立的三个条件: |
|
4)引申: 函数调用时,用n指针(形参)改变n-1指针(实参)的值。
//改变0级指针(int iNum = 1)的值有2种方式
//改变1级指针(eg char *p = 0x1111 )的值,有2种方式
//改变2级指针的(eg char **pp1 = 0x1111 )的值,有2种方式
//函数调用时,形参传给实参,用实参取地址,传给形参,在被调用函数里面用*p,来改变实参,把运算结果传出来。
//指针作为函数参数的精髓。
铁律3:理解指针必须和内存四区概念相结合
1)主调函数 被调函数
a) 主调函数可把堆区、栈区、全局数据内存地址传给被调用函数
b) 被调用函数只能返回堆区、全局数据
理解:因为在调用函数时,其中的东西会不断进栈,而当函数执行完后,整个函数会被不断出栈,这样里面所定义的变量什么的都会被回收,这样也就产生了
上面所说的两句话。
2)内存分配方式
a) 指针做函数参数,是有输入和输出特性的。
铁律4:应用指针必须和函数调用相结合(指针做函数参数)
编号 | 指针函数参数 内存分配方式(级别+堆栈) | 主调函数 实参 | 被调函数 形参 | 备注
| ||||
01 | 1级指针 (做输入) | 堆 | 分配 | 使用 | 一般应用禁用 | |||
栈 | 分配 | 使用 | 常用 | |||||
Int showbuf(char *p); int showArray(int *array, int iNum) | ||||||||
02 | 1级指针 (做输出) | 栈 | 使用 | 结果传出 | 常用 | |||
int geLen(char *pFileName, int *pfileLen); | ||||||||
03 | 2级指针 (做输入) | 堆 | 分配 | 使用 | 一般应用禁用 | |||
栈 | 分配 | 使用 | 常用 | |||||
int main(int arc ,char *arg[]); 指针数组 int shouMatrix(int [3][4], int iLine);二维字符串数组 | ||||||||
04 | 2级指针 (做输出) | 堆 | 使用 | 分配 | 常用,但不建议用,转化成02 | |||
int getData(char **data, int *dataLen); Int getData_Free(void *data); Int getData_Free(void **data); //避免野指针 | ||||||||
05 | 3级指针 (做输出) | 堆 | 使用 | 分配 | 不常用 | |||
int getFileAllLine(char ***content, int *pLine); int getFileAllLine_Free(char ***content, int *pLine); |
指针做函数参数,问题的实质不是指针,而是看内存块,内存块是1维、2维。
1)如果基础类int变量,不需要用指针;
2)若内存块是1维、2维。
铁律5:一级指针典型用法(指针做函数参数)
一级指针做输入
int showbuf(char *p)
int showArray(int *array,int iNum)
一级指针做输出
int geLen(char *pFileName,int *pfileLen);
理解
主调函数还是被调用函数分配内存
被调用函数是在heap/stack上分配内存
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//一级指针的典型用法
//数组 int a[10]
//字符串
//1 C语言的字符串 以零结尾的字符串
//2 在C语言中没有字符串类型 通过字符数组 来模拟字符串
//3 字符串的内存分配 堆上 栈上 全局区 (很重要)
//字符数组 初始化
void main51()
{
//1 指定长度
char buf2[100] = {'a', 'b', 'c', 'd'};
//1-1char buf3[2] = {'a', 'b', 'c', 'd'}; //如果初始化的个数大于内存的个数 编译错误
//1-22//后面的buf2[4]-buf2[99] 0
//2 不指定长度 C编译器会自动帮程序员 求元素的个数
char buf1[] = {'a', 'b', 'c', 'd'}; //buf1是一个数组 不是一个以0结尾的字符串
printf("buf2: %s \n", buf2);
printf("buf2[88]:%d \n", buf2[88]);
printf("hello....\n");
system("pause");
return ;
}
//用字符串 来 初始化字符数组
//strlen() 长度 不包括0
//sizeof() 内存块的大小
void main52()
{
int size = 0;
char buf3[] = "abcd"; // buf3 作为字符数组 应该是5个字节 //作为字符串 应该4个字节
int len = strlen(buf3);
printf("buf3字符的长度:%d \n", len); //4
//buf3 作为数组 数组是一种数据类型 本质(固定小大内存块的别名)
size = sizeof(buf3); //
printf("buf3数组所占内存空间大小:%d \n", size); //5
printf("hello....\n");
{
char buf4[128] = "abcd"; // buf
printf("buf4[100]:%d \n", buf4[100]);
}
system("pause");
return ;
}
//通过数组下标 和 指针
void main58()
{
int i = 0;
char *p = NULL;
char buf5[128] = "abcdefg"; // buf
for (i=0; i<strlen(buf5); i++)
{
printf("%c ", buf5[i]);
}
p = buf5; //buf 代表数组首元素的地址
for (i=0; i<strlen(buf5); i++)
{
p = p +i;
printf("%c ", *p ) ;
}
//buf
for (i=0; i<strlen(buf5); i++)
{
printf("%c ", *(buf5+i) ) ;
}
//本质
//[] *的推导过程
// buf5[i] ===> buf5[0+i]; ==> *(buf5+i);
{
//buf5 = buf5 + 1;
//buf5 = 0x11;
}
printf("hello....\n");·
system("pause");
}
// []的本质 :和*p 是一样 ,只不过是符合程序员的阅读习惯
// buf5 是一个指针, 只读的常量 buf5是一个常量指针 析构内存的时候,保证buf所指向的内存空间安全释放
// 理解 因为buf5[]在栈上分配内存,结束的时候会被退栈结束。
//为什么这么做?
//p普通指针和内存首地址区别
铁律6:二级指针典型用法(指针做函数参数)
二级指针做输入
int main(int arc ,char *arg[]); 字符串数组
int shouMatrix(int [3][4], int iLine);
二级指针做输出
int Demo64_GetTeacher(Teacher **ppTeacher);
int Demo65_GetTeacher_Free(Teacher **ppTeacher);
int getData(char **data, int *dataLen);
Int getData_Free(void *data);
Int getData_Free2(void **data); //避免野指针
理解
主调函数还是被调用函数分配内存
被调用函数是在heap/stack上分配内存
//指针数组
char * p1[] = {"123", "456", "789"};
//二维数组
char p2[3][4] = {"123", "456", "789"}; 不能用**p2来接,提可用用(*p)[4]接 具体原因看下
//手工二维内存
char **p3 = (char **)malloc(3 * sizeof(char *)); //int array[3];
二级指针做输入第一种内存模型
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main21()
{
int i = 0, j = 0;
int num = 0;
char *tmp = NULL;
//数组 数组中的每一个元素是指针 指针数组
char *myArray[] = {"aaaaaa", "ccccc", "bbbbbb", "111111"};
//打印
num = sizeof(myArray)/sizeof(myArray[0]); // 16/4
//理解:总的指针大小除以单个指针大小
printf("排序之前\n");
for (i=0; i<num; i++)
{
//printf("%s \n", myArray[i]);
printf("%s \n", *(myArray+i) );
}
//排序
for (i=0; i<num; i++)
{
for (j=i; j<num; j++)
{
if (strcmp(myArray[i], myArray[j]) > 0 )
{
tmp = myArray[i]; //注意 交换的是数组元素 交换的是指针的值
myArray[i] = myArray[j];
myArray[j] = tmp;
}
}
}
printf("排序之后\n");
for (i=0; i<num; i++)
{
//printf("%s \n", myArray[i]);
printf("%s \n", *(myArray+i) );
}
printf("hello...\n");
system("pause");
return ;
}
void printMyArray11(char **myArray, int num)
{
int i = 0;
for (i=0; i<num; i++)
{
//printf("%s \n", myArray[i]);
printf("%s \n", *(myArray+i) );
}
}
void sortMyArray11(char **myArray, int num)
{
int i =0 , j = 0;
char *tmp = NULL;
//排序
for (i=0; i<num; i++)
{
for (j=i; j<num; j++)
{
if (strcmp(myArray[i], myArray[j]) > 0 )
{
tmp = myArray[i]; //注意 交换的是数组元素 交换的是指针的值 //改变指针的指向
myArray[i] = myArray[j];
myArray[j] = tmp;
}
}
}
}
void main211()
{
int i = 0, j = 0;
int num = 0;
char *tmp = NULL;
//数组 数组中的每一个元素是指针 指针数组
char *myArray[] = {"aaaaaa", "ccccc", "bbbbbb", "111111"};
//打印
num = sizeof(myArray)/sizeof(myArray[0]);
printf("排序之前\n");
printMyArray11(myArray, num);
sortMyArray11(myArray, num);
printf("排序之后\n");
printMyArray11(myArray, num);
printf("hello...\n");
system("pause");
return ;
}
二级指针做输入第二种内存模型
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//打印 排序
//封装成函数
void main31()
{
int i = 0, j = 0;
int num = 4;
char myBuf[30];
char tmpBuf[30];
char myArray[10][30] = {"aaaaaa", "ccccc", "bbbbbbb", "1111111111111"};
//打印
printf("排序之前\n");
for (i=0; i<num; i++)
{
printf("%s\n", myArray[i]);
}
for (i=0; i<num; i++)
{
for (j=i+1; j<num; j++)
{
if (strcmp (myArray[i], myArray[j]) > 0)
{
strcpy(tmpBuf, myArray[i]); //交换的是内存块
strcpy(myArray[i], myArray[j]);
strcpy(myArray[j], tmpBuf);
}
}
}
//打印
printf("排序之后\n");
for (i=0; i<num; i++)
{
printf("%s\n", myArray[i]);
}
printf("hello...\n");
system("pause");
return ;
}
//问题的本质是:dm03_二级指针做输入_第2种内存模型 的 myArray + 1
// dm03_二级指针做输入_第1种内存模型 myArray + 1 不一样 ;
//指针的步长不一样 指针所指向的内存空间的数据类不一样 。。。。
void printMyArray02_err(char **myArray, int num)
/*
理解
二维数组名即数组地址,指向首行地址,不是指针的指针。
表面上看,行地址即一维数组指针,而数组名指向行就应该是指针的指针。
但是你考虑过没有,如果a[10][10],假设int **p=a; 那么要对行递增执行p++时,编译器如何知道列宽?因为int **是指指向一个 int *类型的指针,其数据宽度是4字节,内置宽度,因此p将指向下一个整数元素地址,也就是p递增了4字节,而不是10个int型数据的宽度,这就错位了。
所以a[10][10]的地址类型不是简单的指针的指针,而是行指针的指针,而行宽是由你定义的数组列数和元素类型所决定,int类型就是4*10=40个字节。这叫数据对齐。因此编译器在转换数组名时,会根据对齐要求而确定相应的指针类型,所以a的地址类型应该是int (*)[10],而不是int **。
所以应该这样
int (*p)[10]=a;
其含义为,p是一个指向(含10个int类型元素的一维数组或行的)指针,其实本质上任何指针都是4字节(32位系统),你完全可以将一种指针类型强制转为任何其他类型,那为什么还要区分指针类型,就是为了指针运算时实现数据对齐,准确定位。*/
{ int i = 0; for (i=0; i<num; i++) { //printf("%s \n", myArray[i]); printf("%s \n", *(myArray+i) ); // } } void printMyArray02(char myArray[10][30], int num) { int i = 0; for (i=0; i<num; i++) { //printf("%s \n", myArray[i]); printf("%s \n", *(myArray+i) ); // } } //交换的是内存块。。。。。。。。 void sortMyArray02(char myArray[10][30], int num) { int i, j = 0; char tmpBuf[30]; for (i=0; i<num; i++) { for (j=i+1; j<num; j++) { if (strcmp (myArray[i], myArray[j]) > 0) { strcpy(tmpBuf, myArray[i]); //交换的是内存块 strcpy(myArray[i], myArray[j]); strcpy(myArray[j], tmpBuf); } } } } //打印 排序 //封装成函数 void main333() { int i = 0, j = 0; int num = 4; char myBuf[30]; char tmpBuf[30]; char myArray[10][30] = {"aaaaaa", "ccccc", "bbbbbbb", "1111111111111"}; //myArray: 编译器只会关心:有10行 ,每行30列。。。。。干什么?myArray+1 多维数组名的本质, { int len1 = sizeof(myArray);//300 int len2 = sizeof(myArray[0]);//30 int size = len1/len2;//10 printf("len1:%d , len2:%d size:%d \n", len1, len2, size); } //打印 printf("排序之前\n"); printMyArray02(myArray, num); sortMyArray02(myArray, num); //打印 printf("排序之后\n"); printMyArray02(myArray, num); printf("hello...\n"); system("pause"); return ; }
二级指针做输入的第三种内存模型
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h> // void main41() { int i = 0, j = 0; char **p2 = NULL; int num = 5; char *tmp = NULL; char tmpbuf[100]; p2 = (char **)malloc(sizeof(char *) * num); for (i=0; i<num; i++) { p2[i] = (char *)malloc(sizeof(char) * 100 ); //char buf[100]; sprintf(p2[i], "%d%d%d", i+1, i+1, i+1); } //排序之前 printf("排序之前\n"); for (i=0; i<num; i++) { printf("%s \n", p2[i]); } //排序 交换的是 :指针 /* for (i=0; i<num; i++) { for (j=i+1; j<num; j++) { if (strcmp( p2[i] , p2[j]) < 0) { tmp = p2[i]; p2[i] = p2[j]; p2[j] = tmp; } } } */ //排序 交换的时候内存 for (i=0; i<num; i++) { for (j=i+1; j<num; j++) { if (strcmp( p2[i] , p2[j]) < 0) { strcpy(tmpbuf, p2[i]); strcpy( p2[i], p2[j]); strcpy( p2[j], tmpbuf); } } } //排序之前 printf("排序之后\n"); for (i=0; i<num; i++) { printf("%s \n", p2[i]); } //释放内存 for(i=0; i<num; i++) { if (p2[i] != NULL) { free(p2[i]); p2[i] = NULL; } } if (p2!=NULL) { free(p2); } printf("hello...\n"); system("pause"); return ; } // // char **getMem41(int num) { int i = 0; char **p2 = NULL; p2 = (char **)malloc(sizeof(char *) * num); if (p2 == NULL) { return NULL; } for (i=0; i<num; i++) { p2[i] = (char *)malloc(sizeof(char) * 100 ); //char buf[100]; sprintf(p2[i], "%d%d%d", i+1, i+1, i+1); } return p2; } void printMyArray03(char **myArray, int num) { int i = 0; for (i=0; i<num; i++) { //printf("%s \n", myArray[i]); printf("%s \n", *(myArray+i) ); } } void sortMyArray03(char **myArray, int num) { int i =0 , j = 0; char *tmp = NULL; //排序 for (i=0; i<num; i++) { for (j=i; j<num; j++) { if (strcmp(myArray[i], myArray[j]) > 0 ) { tmp = myArray[i]; //注意 交换的是数组元素 交换的是指针的值 //改变指针的指向 myArray[i] = myArray[j]; myArray[j] = tmp; } } } } void getMem41_Free(char **p2, int num) { int i = 0; //释放内存 for(i=0; i<num; i++) { if (p2[i] != NULL) { free(p2[i]); p2[i] = NULL; } } if (p2!=NULL) { free(p2); } } void main444() { int i = 0, j = 0; char **p2 = NULL; int num = 5; char *tmp = NULL; char tmpbuf[100]; p2 = getMem41(num); //排序之前 printf("排序之前\n"); printMyArray03(p2, num); sortMyArray03(p2, num); //排序 交换的是 :指针 /* for (i=0; i<num; i++) { for (j=i+1; j<num; j++) { if (strcmp( p2[i] , p2[j]) < 0) { tmp = p2[i]; p2[i] = p2[j]; p2[j] = tmp; } } } */ //排序之前 printf("排序之后\n"); printMyArray03(p2, num); getMem41_Free(p2, num); //p2是一个野指针 printf("hello...\n"); system("pause"); return ; }
铁律7:三级指针输出典型用法
三级指针做输出
int getFileAllLine(char ***content, int *pLine);
int getFileAllLine_Free(char ***content, int *pLine);
理解
主调函数还是被调用函数分配内存
被调用函数是在heap/stack上分配内存
铁律8:杂项,指针用法几点扩充
1)野指针 2种free形式
int getData(char **data, int *dataLen);
int getData_Free(void *data);
int getData_Free2(void **data);
2)2次调用
主调函数第一次调用被调用函数求长度;根据长度,分配内存,调用被调用函数。
3)返回值char */int/char **
4)C程序书写结构
商业软件,每一个出错的地方都要有日志,日志级别
铁律9:一般应用禁用malloc/new
指针的输入输出特性
//指针做输出:被调用函数分配内存 -----OK
//指针做输入:主调用函数 分配内存
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h> // int getMem41(char **myp1/*out*/ , int *mylen1 /*out*/, char **myp2 /*out*/, int *mylen2 /*out*/) { int ret = 0; char *tmp1, *tmp2; tmp1 = (char *)malloc(100); strcpy(tmp1, "1132233"); //间接赋值 *mylen1 = strlen(tmp1); //1级指针 *myp1 = tmp1; //2级指针的间接赋值 tmp2 = (char *)malloc(200); strcpy(tmp2, "aaaaavbdddddddd"); *mylen2 = strlen(tmp2); //1级指针 *myp2 = tmp2; //2级指针的间接赋值 return ret; } char * getMem42(int num) { int ret = 0; char *tmp1; tmp1 = (char *)malloc(num); strcpy(tmp1, "1132233"); return tmp1; } int main44() { int ret = 0; char *p1 = NULL; int len1 = 0; char *p2 = NULL; int len2 = 0; ret = getMem41(&p1, &len1, &p2, &len2); if (ret != 0) { printf("func getMem41() err:%d \n", ret); return ret; } printf("p1:%s \n", p1); printf("p2:%s \n", p2); if (p1 != NULL) { free(p1); p1 = NULL; } if (p2 != NULL) { free(p2); p2 = NULL; } p1 = getMem42(100); printf("p1:%s \n", p1); if (p1 != NULL) { free(p1); p1 = NULL; } printf("p1:%d \n", p1); system("pause"); return ret; }
理解:在这个程序中,要做输出时,函数的形参就必须是二级指针,因为当传入一级指针时,就没办法做输出。