一、关于内存那点事
外存外存又叫外部存储器,长期存放数据,掉电不丢失数据常见的外存设备:硬盘、flash、rom、u盘、光盘、磁带内存内存又叫内部存储器,暂时存放数据,掉电数据丢失常见的内存设备:ram、DDR
物理内存:实实在在存在的存储设备虚拟内存:操作系统虚拟出来的内存,当一个进程被创建的时候,或者程序运行的时候都会 分配虚拟内存,虚拟内存和物理内存之间存在映射关系。
1.堆在动态申请内存的时候,在堆里开辟内存。2.栈主要存放局部变量(在函数内部,或复合语句内部定义的变量)。3.静态全局区1):未初始化的静态全局区静态变量(定义的时候,前面加static修饰),或全局变量 ,没有初始化的,存在 此区2):初始化的静态全局区全局变量、静态变量,赋过初值的,存放在此区4.代码区存放咱们的程序代码5.文字常量区存放常量的。内存以字节为单位来存储数据的,咱们可以将程序中的虚拟寻址空间,看成一个很大的一维的字符数组
二、指针的相关概念
注意:1:无论什么类型的地址,都是存储单元的编号,在 32位平台下 都是4个字节, 即任何类型的指针变量都是4个字节大小2:对应类型的指针变量,只能存放对应类型的变量的地址举例:整型的指针变量,只能存放整型变量的地址
三、指针的定义方法
数据类型 * 指针变量名;int * p; //定义了一个指针变量p在 定义指针变量的时候 * 是用来修饰变量的,说明变量p是个指针变量。变量名是 p
& 取地址 、 *取值&:获取一个变量的地址*:在定义一个指针变量时,起到标识作用,标识定义的是一个指针变量除此之外其他地方都表示获取一个指针变量保存的地址里面的内容
#include <stdio.h>
int main(int argc, char *argv[])
{
//定义一个普通变量
int a = 100;
//定义一个指针变量
int *p;
//给指针变量赋值
//将a的地址保存在p中
p = &a;
printf("a = %d %d\n", a, *p);
printf("&a = %p %p\n", &a, p);
return 0;
}
执行结果a = 100 100
&a = 0060FE98 0060FE98
#include <stdio.h>
int main(int argc, char *argv[])
{
char *a;
short *b;
int *c;
long *d;
float *e;
double *f;
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(c) = %d\n", sizeof(c));
printf("sizeof(d) = %d\n", sizeof(d));
printf("sizeof(e) = %d\n", sizeof(e));
printf("sizeof(f) = %d\n", sizeof(f));
return 0;
}
执行结果sizeof(a) = 4
sizeof(b) = 4
sizeof(c) = 4
sizeof(d) = 4
sizeof(e) = 4
sizeof(f) = 4
四、指针的分类
字符型数据的地址char *p; // 定义了一个字符指针变量,只能存放字符型数据的地址编号char ch;p= &ch;
short int *p; // 定义了一个短整型的指针变量p,只能存放短整型变量的地址short int a;p =&a;
int *p; // 定义了一个整型的指针变量p,只能存放整型变量的地址int a;p =&a;
long int *p; // 定义了一个长整型的指针变量p,只能存放长整型变量的地址long int a;p =&a;
float *p; // 定义了一个float型的指针变量p,只能存放float型变量的地址float a;p =&a;
double *p; // 定义了一个double型的指针变量p,只能存放double型变量的地址double a;p =&a;
五、指针和变量的关系
int a;a=100;
int *p; // 在定义的时候,*不是取值的意思,而是修饰的意思,修饰p是个指针变量p=&a; // 取a的地址给p赋值,p保存了a的地址,也可以说p指向了a*p= 100; // 在调用的时候*是取值的意思,*指针变量 等价于指针指向的变量
int a;int *p=&a; //用a的地址,给p赋值,因为p是指针变量指针就是用来存放变量的地址的。
#include <stdio.h>
int main(int argc, char *argv[])
{
int *p1,*p2,temp,a,b;
p1=&a;
p2=&b;
printf("请输入:a b的值:\n");
scanf("%d %d", p1, p2);//给p1和p2指向的变量赋值
temp = *p1; //用p1指向的变量(a)给temp赋值
*p1 = *p2; //用p2指向的变量(b)给p1指向的变量(a)赋值
*p2 = temp;//temp给p2指向的变量(b)赋值
printf("a=%d b=%d\n",a,b);
printf("*p1=%d *p2=%d\n",*p1,*p2);
return 0;
}
执行结果请输入:a b的值:
90 80
a=80 b=90
*p1=80 *p2=90
扩展:对应类型的指针,只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值的时候,需要强制类型转换
#include <stdio.h>
int main(int argc, char *argv[])
{
int a=0x1234,b=0x5678;
char *p1,*p2;
printf("%#x %#x\n",a,b);
p1=(char *)&a;
p2=(char *)&b;
printf("%#x %#x\n",*p1,*p2);
p1++;
p2++;
printf("%#x %#x\n",*p1,*p2);
return 0;
}
执行结果0x1234 0x5678
0x34 0x78
0x12 0x56
1:*+指针 取值,取几个字节,由指针类型决定的指针为字符指针则取一个字节,指针为整型指针则取4个字节,指针为double型指针则取8个字节。2:指针++ 指向下个对应类型的数据字符指针++ ,指向下个字符数据,指针存放的地址编号加1整型指针++,指向下个整型数据,指针存放的地址编号加4
六、指针和数组元素之间的关系
6.1 数组元素与指针的基本关系
6.2 数组元素的引用方法
int a[10];a[2]=100;
int a[10];int *p;p=a;p[2]=100; //因为p和a等价
int a[10];int *p;p=a;*(p+2)=100; //也是可以的,相当于a[2]=100
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[5]={0,1,2,3,4};
int *p;
p=a;
//只要将数组名赋值给同类型的指针变量,则此时的指针变量与数组名可
//以用相同的方法操作数组
printf("a[2]=%d\n",a[2]);
printf("p[2]=%d\n",p[2]);
//*(a + n) <==> *(p + n) <==> a[n] <==> p[n]
printf("*(p+2) = %d\n",*(p+2));
printf("*(a+2) = %d\n",*(a+2));
printf("p=%p\n",p);
printf("p+2=%p\n",p+2);
printf("&a[0] = %p\n", &a[0]);
printf("&a[2] = %p\n", &a[2]);
return 0;
}
执行结果a[2]=2
p[2]=2
*(p+2) = 2
*(a+2) = 2
p=0060FE88
p+2=0060FE90
&a[0] = 0060FE88
&a[2] = 0060FE90
七、指针的运算
7.1 指针可以加一个整数
#include <stdio.h>
//指针的运算
//指针可以加一个整数,往下指几个它指向的变量,结果还是个地址
void test1()
{
int a[10];
int *p, *q;
//p和q间隔8个字节,意味着加一个整数最终移动的字节数与指针变量的类型也有关系
p = a;
q = p + 2;
printf("p = %p\n", p);
printf("q = %p\n", q);
return ;
}
int main(int argc, char *argv[])
{
test1();
return 0;
}
执行结果p = 0060FE60
q = 0060FE68
7.2 两个相同类型指针可以比较大小
#include <stdio.h>
//指针的运算
//两个相同类型指针可以比较大小
void test2()
{
int a[10];
int *p,*q;
p=&a[1];
q=&a[6];
if(p<q)
{
printf("p < q\n");
}
else if(p>q)
{
printf("p > q\n");
}
else
{
printf("p = q\n");
}
}
int main(int argc, char *argv[])
{
test2();
return 0;
}
执行结果p < q
7.3 两个相同类型的指针可以做减法
#include <stdio.h>
//指针的运算
//两个相同类型的指针可以做减法
void test3()
{
int a[10];
int *p,*q;
p=&a[0];
q=&a[3];
printf("%d\n",q-p);
}
int main(int argc, char *argv[])
{
test3();
return 0;
}
执行结果3
7.4 两个相同类型的指针可以相互赋值
#include <stdio.h>
//指针的运算
//两个相同类型的指针可以相互赋值
void test4()
{
int a = 100;
int *p, *q;
p = &a;
printf("a = %d %d\n", a, *p);
q = p;
printf("*q = %d\n", *q);
*q = 999;
printf("a = %d\n", a);
}
int main(int argc, char *argv[])
{
test4();
return 0;
}
执行结果a = 100 100
*q = 100
a = 999
八、指针数组
1、指针和数组的关系
2、指针数组的定义方法:
int * p[10];//定义了一个整型的指针数组p,有10个元素p[0]~p[9],每个元素都是int *类型的变量
int a;
p[1]=&a;
int b[10];
p[2]=&b[3];
p[2]、*(p+2)是等价的,都是指针数组中的第2个元素。
#include <stdio.h>
int main(int argc, char *argv[])
{
//大多数情况下,指针数组都用来保存多个字符串
char *name[5] = {"Follw me","BASIC","Greatwall","FORTRAN","Computer"};
int i;
for(i=0;i<5;i++)
{
printf("%s\n",name[i]);
}
return 0;
}
执行结果Follw me
BASIC
Greatwall
FORTRAN
Computer
九、指针的指针 -- 二级指针
int a;
int *p;
p=&a;
*p === a
int **q;
q=&p;
*q === p
**q === *p === a
int ***m;
m=&q;
*(*(*m)) === a
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 100;
//定义一个一级指针
//一级指针用于保存普通变量的地址
int *p = &a;
//定义一个二级指针
//二级指针用于保存一级指针的地址
int **q = &p;
printf("a = %d %d %d\n", a, *p, **q);
printf("&a = %p %p %p\n", &a, p, *q);
printf("&p = %p %p\n", &p, q);
printf("&q = %p\n", &q);
return 0;
}
执行结果a = 100 100 100
&a = 0060FE9C 0060FE9C 0060FE9C
&p = 0060FE98 0060FE98
&q = 0060FE94
十、字符串和指针
1、 char string[100] = “I love C!”定义了一个字符数组string,用来存放多个字符,并且用”I love C!”给string数组初始化 ,字符串“I love C!”存放在string中2、 char *str = “I love C!”定义了一个指针变量str,只能存放字符地址编号, 所以说I love C! 这个字符串中的字符不能存放在str指针变量中。 str只是存放了字符I的地址编号,“I love C!”存放在文字常量区3、 char *str =(char*)malloc(10*sizeof(char));动态申请了10个字节的存储空间,首地址给str赋值。strcpy(str,"I love C"); // 将字符串“Ilove C!”拷贝到str指向的内存里
字符数组:在内存(栈、静态全局区)中开辟了一段空间存放字符串字符串指针: 在文字常量区开辟了一段空间存放字符串,将字符串的 首地址 付给str堆:使用malloc函数在堆区申请空间,将字符串拷贝到堆区
可修改性:1. 栈和全局区内存中的内容是可修改的char str[100]=”I love C!”;str[0]=‘y’; //正确可以修改的2. 文字常量区里的内容是不可修改的char *str=”I love C!”;*str =’y’; //错误,I存放在文字常量区,不可修改3. 堆区的内容是可以修改的char *str =(char*)malloc(10*sizeof(char));strcpy(str,"I love C");*str=’y’;//正确,可以,因为堆区内容是可修改的注意:str指针指向的内存能不能被修改,要看str指向哪里。str指向文字常量区的时候,内存里的内容不可修改str指向栈、堆、静态全局区的时候,内存的内容是可以修改初始化:字符数组、指针指向的字符串:定义时直接初始化char buf_aver[]="hello world";char *buf_point="hello world";堆中存放的字符串不能初始化、只能使用strcpy、scanf赋值char *buf_heap;buf_heap=(char *)malloc(15);strcpy(buf_heap,"hello world");scanf(“%s”,buf_heap);使用时赋值字符数组:使用scanf或者strcpychar buf_aver[128];buf_aver="hello kitty"; 错误,因为字符数组的名字是个常量strcpy(buf_aver,"hello kitty"); 正确scanf("%s",buf_aver); 正确指向字符串的指针:char *buf_point;buf_point="hello kitty"; 正确,buf_point指向另一个字符串strcpy(buf_point,"hello kitty"); 错误,只读,能不能复制字符串到buf_piont指向 的内存里,取决于buf_point指向哪里。
十一、数组指针
1、二维数组
例: int a[3][5];
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[3][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
return 0;
}
2、数组指针的概念:
3、数组指针的定义方法:
指向的数组的类型(*指针变量名)[指向的数组的元素个数]
#include <stdio.h>
//定义数组指针
void test1()
{
int a[3][5]; //定义了一个3行5列的一个二维数组
int(*p)[5]; //定义一个数组指针变量p,p+1跳一个有5个元素的整型数组
printf("a=%p\n",a); //第0行的行地址
printf("a+1=%p\n",a+1); //第1行的行地址,a和a +1差20个字节
p=a;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1); //p+1跳一个有5个整型元素的一维数组
}
int main(int argc, char *argv[])
{
test1();
return 0;
}
执行结果a=0060FE50
a+1=0060FE64
p=0060FE50
p+1=0060FE64
#include <stdio.h>
//数组指针的用法
//可以将二维数组的首地址传递到另一个函数里面,此时函数的形参就需要定义为数组指针
void fun(int(*p)[5],int x,int y)
{
p[0][1]=101;
}
void test2()
{
int i,j;
int a[3][5] = {0};
fun(a,3,5);
for(i=0;i<3;i++)
{
for(j=0;j<5;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
int main(int argc, char *argv[])
{
test2();
return 0;
}
执行结果0 101 0 0 0
0 0 0 0 0
0 0 0 0 0
4、各种数组指针的定义:
(1)、一维数组指针,加1后指向下个一维数组int(*p)[5] ;配合每行有5个int型元素的二维数组来用int a[3][5]int b[4][5]int c[5][5]int d[6][5]…..p=a;p=b;p=c;p=d;都是可以的~~~~(2)、二维数组指针,加1后指向下个二维数组int(*p)[4][5];配合三维数组来用,三维数组中由若干个4行5列二维数组构成int a[3][4][5];int b[4][4][5];int c[5][4][5];int d[6][4][5];这些三维数组,有个共同的特点,都是有若干个4行5的二维数组构成。p=a;p=b;p=c;p=d;(3)、三维数组指针,加1后指向下个三维数组int(*p)[4][5][6];p+1跳一个三维数组;什么样的三维数组啊?由4个5行6列的二维数组构成的三维数组配合:int a[7][4][5][6];(4)、四维数组指针,加1后指向下个四维数组,以此类推。。。。
5、容易混淆的内容:
指针 数组 :是个数组,有若干个相同类型的指针构成的集合int *p[10];数组p有10个int *类型的指针变量构成,分别是p[0] ~p[9]数组 指针 :本身是个指针,指向一个数组,加1跳一个数组int (*p)[10];P是个指针,p是个数组指针,p加1指向下个数组,跳10个整形。指针的 指针 :int **p;//p是指针的指针int *q;p=&q;
6、数组名字取地址:变成 数组指针
int a[10];a+1 跳一个整型元素,是a[1]的地址a和a+1 相差一个元素,4个字节&a就变成了一个一维数组指针,是 int(*p)[10]类型的。(&a) +1 和&a相差一个数组即10个元素即40个字节。
7、数组名字和指针变量的区别:
int a[10];int *p;p=a;
相同点:a是数组的名字,是a[0]的地址,p=a即p也保存了a[0]的地址,即a和p都指向a[0],所以在引用数组元素的时候,a和p等价不同点:1、 a是常量、p是变量可以用等号’=’给p赋值,但是不能用等号给a赋值2、 对a取地址,和对p取地址结果不同因为a是数组的名字,所以对a取地址结果为数组指针。p是个指针变量,所以对p取地址(&p)结果为指针的指针。![]()
8、多维数组中指针的转换:
#include <stdio.h>
//二维数组的数组名降级问题
//二维数组的数组名默认是一个行指针,加1保存下一行的首地址
//二维数组的数据名取*,表示地址的降级,意味着行指针降级为列指针,加1保存下一个元素的地址
void test3()
{
int a[3][5];
printf("a=%p\n",a);
printf("a +1=%p\n",a+1);
printf("*a =%p\n",*a);// *a变成了第0行第0列元素的地址
printf("(*a)+1 =%p\n",(*a)+1 ); //结果为第0行第1列元素的地址
}
int main(int argc, char *argv[])
{
test3();
return 0;
}
执行结果a=0060FE54
a +1=0060FE68
*a =0060FE54
(*a)+1 =0060FE58
十二、指针与函数的关系
12.1 指针作为函数的参数
12.1.1 复制传参 -- 传数值
#include <stdio.h>
//函数的传参方式之复制传参:将实参的值传递给形参,不管形参怎么改变,跟实参都没有关系
void myfun1(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
printf("in fun: a = %d, b = %d\n", a, b);
printf("&a = %p, &b = %p\n", &a, &b);
}
void test1()
{
int a = 100, b = 20;
printf("before fun: a = %d, b = %d\n", a, b);
printf("&a = %p, &b = %p\n", &a, &b);
myfun1(&a, &b);
printf("after fun: a = %d, b = %d\n", a, b);
}
int main(int argc, char *argv[])
{
test1();
return 0;
}
执行结果before fun: a = 100, b = 20
&a = 0060FE8C, &b = 0060FE88
in fun: a = 6356616, b = 6356620
&a = 0060FE70, &b = 0060FE74
after fun: a = 100, b = 20
#include <stdio.h>
//函数的传参方式之地址传参:将实参的地址传递给形参,形参对保存的地址的内容
//进行任何操作,实参的值也会跟着改变
void myfun2(int *p, int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
printf("in fun: *p = %d, *q = %d\n", *p, *q);
printf("p = %p, q = %p\n", p, q);
}
void test1()
{
int a = 100, b = 20;
printf("before fun: a = %d, b = %d\n", a, b);
printf("&a = %p, &b = %p\n", &a, &b);
myfun2(&a, &b);
printf("after fun: a = %d, b = %d\n", a, b);
}
int main(int argc, char *argv[])
{
test1();
return 0;
}
执行结果before fun: a = 100, b = 20
&a = 0060FE8C, &b = 0060FE88
in fun: *p = 20, *q = 100
p = 0060FE8C, q = 0060FE88
after fun: a = 20, b = 100
注意:如果实参是一个普通变量,地址传参的话就需要形参是一级指针,如果实参是一个一级指针,地址传参的话就需要形参是一个二级指针,以此类推
12.2 传数组
#include <stdio.h>
//传一维数组
//void fun1(int p[]) //形式1
void fun1(int *p) //形式2(常用)
{
printf("%d\n",p[2]);
printf("%d\n",*(p+3));
}
void test2()
{
int a[10]={1,2,3,4,5,6,7,8};
fun1(a);
}
//传二维数组
//void fun2( int p[][4] ) //形式1
void fun2( int (*p)[4] ) //形式2:通过数组指针
{
//p[x][y] <==> *(*(p + x) + y)
printf("%d\n", p[0][2]);
printf("%d\n", *(*(p+1) + 2));
}
void test3()
{
int a[2][4] = {1, 2, 3, 4,
5, 6, 7, 8};
fun2(a);
}
//传指针数组
void fun3(char **q)
{
int i;
for(i=0;i<3;i++)
{
printf("%s\n",q[i]);
}
}
void test4()
{
char *p[3]={"hello","world","kitty"};
fun3(p);
}
12.3 指针函数 -- 指针作为函数的返回值
//指针函数:指针作为函数的返回值
char *fun4()
{
//栈区开辟的空间会随着当前代码段的结束而释放空间
//char str[100]="hello world";
//静态区的空间不会随着当前代码段的结束而释放空间
static char str[100]="hello world";
return str;
}
void test5()
{
char *p;
p = fun4();
printf("p = %s\n", p);
}
12.4 函数指针 - 指针保存函数的地址
12.4.1 函数指针变量的定义方法
int (*p)(int,int); //定义了一个函数指针变量p,p指向的函数
//必须有一个整型的返回值,有两个整型参数。
int max(int x,int y) { }
int min(int x,int y) { }
//可以用这个p存放这类函数的地址。
p=max; p=min;
12.4.2 调用函数的方法
int max ( int x , int y ) { }int main (){int num ;num = max ( 3 , 5 );}
int max ( int x , int y ) { }int main (){int num ;int ( * p )( int , int );p = max ;num = p ( 3 , 5 );}
12.4.3 函数指针数组
返回值类型 (*函数指针变量名[函数指针的个数])(形参列表);
12.4.4 函数指针最常用的地方
#include <stdio.h>
int add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x-y;
}
int mux(int x,int y)
{
return x*y;
}
int dive(int x,int y)
{
return x/y;
}
int process(int (*p)(int ,int),int a,int b)
{
int ret;
ret = (*p)(a,b);
return ret;
}
int main(int argc, char *argv[])
{
int num;
num = process(add,2,3);
printf("num = %d\n",num);
num = process(sub,2,3);
printf("num = %d\n",num);
num = process(mux,2,3);
printf("num = %d\n",num);
num = process(dive,2,3);
printf("num = %d\n",num);
return 0;
}
执行结果num = 5
num = -1
num = 6
num = 0
十三、经常容易混淆的指针
第一组:1、 int *a[10];这是个指针数组,数组a中有10个整型的指针变量a[0]~a[9]2、int (*a)[10];数组指针变量,它是个指针变量。它占4个字节,存地址编号。它指向一个数组,它加1的话,指向下个数组。3、 int **p;这个是个指针的指针,保存指针变量的地址。它经常用在保存指针的地址:常见用法1:int **pint *q;p=&q;常见用法2:int **p;int *q[10];分析:q是指针数组的名字,是指针数组的首地址,是q[0]的地址。q[0]是个int *类型的指针。 所以q[0]指针变量的地址,是int **类型的第二组:1、int *f(void);注意:*f没有用括号括起来它是个函数的声明,声明的这个函数返回值为int *类型的。2、int (*f)(void);注意*f用括号括起来了,*修饰f说明,f是个指针变量。f是个函数指针变量,存放函数的地址,它指向的函数,必须有一个int型的返回值,没有参数。
十四、特殊指针
1、空类型的指针(void *)
char * 类型的指针指向char型的数据int * 类型的指针指向int型的数据float* 类型的指针指向float型的数据void * 难道是指向void型的数据吗?不是,因为没有void类型的变量
void* 通用指针,任何类型的指针都可以给void*类型的指针变量赋值。主要也是用在函数的参数和返回值的位置int *p;void *q;q=p 是可以的,不用强制类型转换
有个函数叫memsetvoid * memset(void *s,int c,size_t n);这个函数的功能是将s指向的内存前n个字节,全部赋值为 c。Memset可以设置字符数组、整型数组、浮点型数组的内容,所以第一个参数,就必须是个通用指针它的返回值是s指向的内存的首地址,可能是不同类型的地址。所以返回值也得是通用指针
注意:void*类型的指针变量,也是个指针变量,在32为系统下,占4个字节
2、NULL
空指针:char *p=NULL;咱们可以认为p哪里都不指向,也可以认为p指向内存编号为0的存储单位。在p的四个字节中,存放的是0x00 00 00 00一般NULL用在给指针初始化。
十五、main函数传参
int main( int argc, char *argv[])argc:是一个int类型的变量,标识命令终端传入的参数的个数argv:是一个指针数组,用于保存每一个命令终端传入的参数
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("argc=%d\n",argc);
for(i=0;i<argc;i++)
{
printf("argv[%d]=%s\n",i,argv[i]);
}
return 0;
}