c语言基础(四)指针
内存
在32 位系统下,32 位寄存器最大寻址空间为 2的32次方 ,0x00 00 00 00 ~0xff ff ff ff,每个进程(运行着的程序)的寻址范围是4G。64 位操作系统的内存寻址空间为 **2的64次方 **
在运行程序的时候,操作系统会将虚拟内存进行分区。
堆:在动态申请内存的时候,在堆里开辟内
栈:主要存放局部变量(在函数内部,或复合语句内部定义的变量)。
静态全局区
1):未初始化的静态全局区
静态变量(定义的时候,前面加static 修饰),或全局变量,没有初始化的,存在此区
2):初始化的静态全局区
全局变量、静态变量,赋过初值的,存放在此区
代码区:存放的程序代码
文字常量区:存放常量
指针
系统给虚拟内存的每个存储单元分配了一个编号,从0x00 00 00 00 ~0xff ff ff ff,这个编号咱们称之为地址,指针就是地址
指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号
32位系统指针变量4 个字节。对应类型的指针变量,只能存放对应类型的变量的地址,即整型的指针变量,只能存放整型变量的地址
指针变量
形式:数据类型* 指针变量名;
int * p;//定义了一个整形指针变量p
如果在一行中定义多个指针变量,每个指针变量前面都需要加*来修饰
int *p,*q;//定义了两个整型的指针变量p 和q
int * p,q;//定义了一个整型指针变量p,和整型的变量q
在定义指针变量的时候* 是用来修饰变量的,说明变量p 是个指针变量。
变量名是p
关于指针的运算符
& 取地址、*取值
指针大小:32位和64位系统中,编译器为了相互兼容,所以指针都是4个字节长度。
#include <stdio.h>
int main()
{
char* p1;
short int* p2;
int* p3;
long int* p4;
float* p5;
double* p6;
int num,i = 25;
p1 = &i;
num = *p1;
printf("char的指针大小是:%d\n", sizeof(p1));
printf("取出指针p1的值:%d\n", num);
printf("取出变量i的内存地址(p1):%p\n", p1);
printf("short int的指针大小是:%d\n", sizeof(p2));
printf("int的指针大小是:%d\n", sizeof(p3));
printf("long int的指针大小是:%d\n", sizeof(p4));
printf("float的指针大小是:%d\n", sizeof(p5));
printf("double的指针大小是:%d\n", sizeof(p6));
return 0;
}
指针的分类
按指针指向的数据的类型来分
字符指针,短整型指针,整型指针,长整型指针,float 型的指针,double 型的指针,函数指针,结构体指针,指针的指针,数组指针,通用指针void *p;
多字节变量,占多个存储单元,每个存储单元都有地址编号,c 语言规定,存储单元编号最小的那个编号,是多字节变量的地址编号。
例:字符指针,
字符型数据的地址
char *p;//定义了一个字符指针变量,只能存放字符型数据的地址编号
char ch;
p= &ch;
无论什么类型的指针变量,都是4 个字节,指针只能存放对应类型的变量的地址编号。
指针和变量
指针可以存放变量的地址编号
int a=10;
int *p;
p=&a; //p 保存了a 的地址,也可以说p 指向了a
通过指针变量来引用变量
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* p1, * p2, temp, a, b;
p1 = &a;
p2 = &b;
printf("请输入:a b 的值:\n");
scanf_s("%d %d", p1, p2);//给p1 和p2 指向的变量赋值,并不是给p1赋一个地址值,而是给p1指向的变量赋值,输入语句中不能使用*p1
printf("p1:%p\n",p1); //查看p1代表的内存地址
printf("请输入:a b 的值:\n");
scanf_s("%d %d", p1, p2);//给p1 和p2 指向的变量赋值
printf("p1:%p\n", p1); //查看p1代表的内存地址,a的值会变但内存地址不变
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;
}
对应类型的指针,只能保存对应类型数据的地址,
如果想让不同类型的指针相互赋值的时候,需要强制类型转换
#include<stdio.h>
int main()
{
int a = 0x12345678, b = 0xabcdef12;
char* p1, * p2;
printf("%0x %0x\n", a, b);
p1 = (char*)&a;
p2 = (char*)&b;
printf("%0x %0x\n", *p1, *p2);
p1++;
p2++;
printf("%0x %0x\n", *p1, 0x000000ff & *p2);
return 0;
}
*指针 取值,取几个字节,由指针类型决定的指针为字符指针则取一个字节,
指针为整型指针则取4 个字节,指针为double 型指针则取8 个字节。
指针++ 指向下个对应类型的数据
字符指针++ ,指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4
指针与数组元素
变量存放在内存中,有地址编号,数组是多个相同类型的变量的集合,每个变量都占内存空间,都有地址编号,指针变量当然可以存放数组元素的地址。
利用指针引用数组元素
指针名加下标
int a[5];
int *p;
p=a;
p[2]=100;//相当于a[2]=100;
c 语言规定:数组的名字就是数组的首地址,即第0 个元素的地址,就是&a[0],a是地址,是个常量。p是指针变量,a不可以被赋值
#include <stdio.h>
int main()
{
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]); //p[2]相当于a[2]
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);//元素加2 0+2
return 0;
}
指针的运算
指针可以加一个整数,往下指几个它指向的变量,结果还是个地址
指针指向数组元素的时候,加一个整数才有意义
#include <stdio.h>
int main()
{
int b[5];
int* q;
q = b;
printf("q=%p\n", q); //相当于 b[0]的内存地址
printf("q+1=%p\n", q+1); //相当于 b[1]的内存地址
return 0;
两个相同类型指针可以比较大小
只有两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义
指向前面元素的指针小于指向后面元素的指针
两个相同类型的指针可以做减法
必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义
做减法的结果是,两个指针指向的中间有多少个元素
两个相同类型的指针可以相互赋值
只有相同类型的指针才可以相互赋值
如果类型不相同的指针要想相互赋值,必须进行强制类型转换
int *p;
int *q;
int a;
p=&a;//p 保存a 的地址,p 指向了变量a
q=p; //用p 给q 赋值,q 也保存了a 的地址,指向a
指针数组
指针可以保存数组元素的地址
数组中有若干个相同类型指针变量,这个数组被称为指针数组
指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合
形式:类型说明符* 数组名[元素个数];
int * p[5];//定义了一个整型的指针数组p,有5 个元素p[0]~p[4],
每个元素都是int *类型的变量
p[2]、*(p+2)是等价的,都是指针数组中的第2 个元素。
#include <stdio.h>
int main(int argc, char *argv[])
{
char *name[5] = {"hello","China","beijing","project","Computer"};
int i;
for(i=0;i<5;i++)
{
printf("%s\n",name[i]); //name[i],指针数组的第i个元素
}
指针数组根据类型不同可分为:字符指针数组char *p[10]、短整型指针数组、整型的指针数组、长整型的指针数组,float 型的指针数组、double 型的指针数组,结构体指针数组、函数指针数组
指针的指针
指针的指针,即指针的地址,指针变量本身指针变量占4 个字节,指针变量也有地址编号。
#include<stdio.h>
int main()
{
int a = 100;
int* p;
int** q;
p = &a;
q = &p;
printf("a的地址&a=%p,p=%p\n",&a,p);
printf("p的地址&p=%p,q=%p\n", &p, q);
printf("a的值:%d\n",a);
printf("*p的值:%d\n",*p);
printf("**q的值:%d\n",**q);//**q=*p=a
return 0;
}
p q都是指针变量,都占4 个字节,都存放地址编号,只不过类型不一样而已
字符串与指针
字符串:字符串就是以’\0’结尾的若干的字符的集合
字符串的存储形式: 数组、文字常量区、堆
字符串存放在数组中,其实就是在内存(栈、静态全局区)中开辟了一段空间存放字符串。
用数组表示字符串char string[] = “ hello world”
定义了一个字符数组string,用来存放多个字符
普通全局数组,内存分配在静态全局区;普通局部数组,内存分配在栈区;静态数组(静态全局数组、静态局部数组),内存分配在静态全局区
存放在数组中的字符串的内容是可修改的
字符串存放在文字常量区
在文字常量区开辟了一段空间存放字符串,将字符串的首地址付给指针变量。
char *str = “ hello world”
定义了一个指针变量str,只能存放字符地址编号,str 只是存放了字符h的地址编号,“hello world”存放在文字常量区
文字常量区里的内容是不可修改的
str 指向文字常量区的时候,它指向的内存的内容不可被修改。
str 是指针变量可以指向别的地方,即可以给str 重新赋值,让它指向别的地方。
使用函数在堆区申请空间,将字符串拷贝到堆区。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* str ;
str = (char*)malloc(20);
strcpy(str, "hellolulu");
printf("%s\n",str);
*str = 'y';
printf("%s\n", str);
return 0;
}
str 指向堆区的时候,str 指向的内存内容是可以被修改的。
str 是指针变量,也可以指向别的地方。即可以给str 重新赋值,让它指向别的地方
str 指向文字常量区的时候,内存里的内容不可修改
str 指向数组(非const 修饰)、堆区的时候,它指向内存的内容是可以修改
数组指针
二维数组
二维数组,有行,有列。二维数组可以看成有多个一维数组构成的,是多个一维数组的集合,可以认
为二维数组的每一个元素是个一维数组。
数组的名字是数组的首地址,是第0 个元素的地址,是个常量,数组名字加1 指向下个元素
二维数组a 中,a+1 指向下个元素,即下一个一维数组,即下一行。
数组指针:本身是个指针,指向一个数组,加1 跳一个数组,即指向下个数组。
形式:数组的类型(*指针变量名)[指向的数组的元素个数]
例子:int (*p)[5];//定义了一个数组指针变量p,p 指向的是整型的有5 个元素的数组
p+1 往下指5 个整型,跳过一个有5 个整型元素的数组。
#include<stdio.h>
int main()
{
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 个整型元素的一维数组
return 0;
}
一维数组指针,加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;
二维数组指针,加1 后指向下个二维数组
int(*p)[4][5];
配合三维数组来用,三维数组中由若干个4 行5 列二维数组构成
必须个共同的特点,都是有若干个4 行5 的二维数组构成。
指针数组:是个数组,有若干个相同类型的指针构成的集合
int *p[10];
数组p 有10 个int *类型的指针变量构成,分别是p[0] ~p[9] 占四十个字节的存储空间
数组指针:本身是个指针,指向一个数组,加1 跳一个数组
int (*p)[10]; 占四个字节的存储空间
p 是个数组指针,p 加1 指向下个数组,跳10 个整型
指针的指针:
int **p;//p 是指针的指针 占四个字节的存储空间
int *q;
p=&q;
数组名字取地址:变成数组指针
一维数组名字取地址,变成一维数组指针,即加1 跳一个一维数组
int a[10];
a+1 跳一个整型元素,是a[1]的地址
a 和a+1 相差一个元素,4 个字节
&a 就变成了一个一维数组指针,是int(*p)[10]类型的。
(&a) +1 和&a 相差一个数组即10 个元素即40 个字节。
a 和&a 所代表的地址编号是一样的,即他们指向同一个存储单元,但是a和&a 的指针类型不同
二维数组名字取地址,变成二维数组指针,即加1 跳一个二维数组
c 语言规定,数组名字取地址,变成了数组指针。加1 跳一个数组。
数组名字与指针变量的区别
int a[5];
int *p;
p=a;
a[2]、*(a+2)、p[2]、*(p+2) 都是对数组a 中a[2]元素的引用。
a 是数组的名字,是数组第一个元素的首地址,p=a 即p 保存了a[0]的地址,即a 和p 都指向a[0],所以在引用数组元素的时候,a 和p 等价
a 是常量、p 是变量
可以用等号’=’给p 赋值,但是不能用等号给a 赋值
对a 取地址,和对p 取地址结果不同
因为a 是数组的名字,所以对a 取地址结果为数组指针。
int a[5],取地址:int(*p)[5]加1 跳一个一维数组
p 是个指针变量,所以对p 取地址(&p)结果为指针的指针。
int *p,取地址变成int**q,加1 跳了一个指针四个字节
数组指针取* ,并不是取值的意思,而是指针的类型发生变化:
一维数组指针取* ,结果为它指向的一维数组第0 个元素的地址,它们还是指向同一个地方。
二维数组指针取*,结果为一维数组指针,它们还是指向同一个地方。
三维数组指针取*,结果为二维数组指针,它们还是指向同一个地方。
a是数组第一个元素的首地址,
一维数组对a取&,得到一维数组的数组指针,加1跳过数组
一维数组对a取*,得到第0 个元素的地址,加1跳过第0 个元素
二维数组对a取&,得到二维数组的数组指针,加1跳过数组
二维数组对a取*,得到第0 个元素(第0行)的地址,加1跳过第0 个元素(跳过第0行)
#include<stdio.h>
int main()
{
int a[3][5];
int(*p)[5];
p = a;
printf("a=%p\n", a);//a 是一维数组指针,指向第0 个一维数组,即第0 行
printf("*a=%p\n", *a);//*a 是第0 行第0 个元素的地址,即&a[0][0]
printf("*a +1=%p\n", *a + 1);//*a +1 是第0 行第1 个元的地址,即&a[0][1]
printf("p=%p\n", p);//p 是一维数组指针,指向第0 个一维数组,即第0 行
printf("*p=%p\n", *p);//*p 是第0 行第0 个元素的地址,即&a[0][0]
printf("*p +1=%p\n", *p + 1);//*p +1 是第0 行第1 个元的地址,即&a[0][1]
return 0;
}
指针与函数
指针作为函数的参数
给函数传输一个整型、字符型、浮点型的数据
#include<stdio.h>
void fun(int x, int y)
{
int z;
z = x;
x = y;
y = z;
}
int main()
{
int a=10, b=20;
printf("a的值是:%d\n", a);
printf("b的值是:%d\n", b);
fun(a, b);
printf("a的值是:%d\n", a);
printf("b的值是:%d\n", b);
return 0;
}
//给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值
给函数传一个地址
#include<stdio.h>
void fun(int *x, int *y)
{
int z;
z = *x; //对x取值是int 10,赋值给int变量z
*x = *y;
*y = z;
}
int main()
{
int a=10, b=20;
printf("a的值是:%d\n", a);
printf("b的值是:%d\n", b);
fun(&a, &b); //对a和b分别取地址,由指针变量接受
printf("a的值是:%d\n", a);
printf("b的值是:%d\n", b);
return 0;
}
//调用函数的时候传变量的地址,在被调函数中通过*地址来改变主调函数中的变量的值
要想改变主调函数中变量的值,必须传变量的地址,而且还得通过*+地址去赋值.
#include<stdio.h>
void fun(char**q)
{
*q = "hello a shu"; //q=&p,*q=p
}
int main()
{
char* p = "hello world";
printf("p的值是:%s\n",p);
fun(&p); //传指针的地址,指针的指针用**q(这两个*是修饰)接受,
printf("p的值是:%s\n", p);
return 0;
}
要想改变主调函数中变量的值,必须传变量的地址,而且还得通过*+地址去赋值。无论这个变量是什么类型的。
给函数传数组
给函数传数组的时候,没法一下将数组的内容作为整体传进去。只能传数组名进去,数组名就是数组的首地址,即只能把数组的地址传进去。也就是说,只能传一个4 个字节大小的地址编号进去
#include<stdio.h>
//void fun(int* p)
void fun(int p[]) //*p相当于p[]
{
//p[1] = 5;
*(p + 1) = 5; //*(p+1)相当于p[]
}
int main()
{
int a[5] = {0,1,2,3,4};
printf("a[1]的值是:%d\n",a[1]);
fun(a);
printf("a[1]的值是:%d\n", a[1]);
return 0;
}
//传一维数组
#include<stdio.h>
void fun(int (*p)[2],int x,int y)//(*p)[2]相当于p[][2]
{
p[1][0] = 6;
(*(p + 1) )[1] = 5;// (*(p+1))[1]相当于p[1][1]
}
int main()
{
int a[2][2] = {
{1,2},
{3,4}
};
printf("a[1][0]=%d\n", a[1][0]);
printf("a[1][1]=%d\n", a[1][1]);
fun(a, 2, 2);
printf("a[1][0]=%d\n", a[1][0]);
printf("a[1][1]=%d\n", a[1][1]);
return 0;
}
//传二维数组
#include<stdio.h>
void fun(int**p)
{
int i;
for (i = 0; i < 3; i++)
{
printf("p[%d]=%d\n",i,p[i]);
}
}
int main()
{
int* p[3] = {1,2,3};
fun(p);
return 0;
}
#include<stdio.h>
void fun(char**p)
{
int i;
for (i = 0; i < 3; i++)
{
printf("p[%d]=%s\n",i,p[i]);
}
}
int main()
{
char* p[3] = {"hello","a","shu"};
fun(p);
return 0;
}
函数的返回值可以是整型数据、字符数据、浮点型,也可以是指针。
#include<stdio.h>
char *fun()
{
char a[] = "hello,world";
return a; //a是字符串h的地址
}
int main()
{
char *p;
p= fun();
printf("%s", p);//返回的指针指向的内容已经被释放了,返回这个地址,也没有意义了。
return 0;
}
#include<stdio.h>
char *fun()
{
static char a[] = "hello,world";//静态局部变量,函数结束后,不释放
return a; //a是字符串h的地址
}
int main()
{
char *p;
p= fun();
printf("%s", p);
return 0;
}
#include<stdio.h>
char* fun()
{
char *a= "hello,world";//文字常量区的字符串
return a;
}
int main()
{
char* p;
p = fun();
printf("%s", p);
return 0;
}
#include<stdio.h>
char* fun()
{
char* a;
a= (char*)malloc();//堆区开辟空间,堆区的内容一直存在,直到free 才释放。
strcpy(a, "hello,world");
return a;
}
int main()
{
char* p;
p = fun();
printf("%s\n", p);
free(p);
}
地址指向的内存的内容得存在,返回的地址才有意义。函数中指针指向的内容在函数结束时都会释放,需要static设置静态局部变量,或者指针内容保存在文字常量区,堆区
函数指针
定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段,所以函数也有起始地址,函数的名字就是函数的首地址,即函数的入口地址,咱们就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量。
形式:*返回值类型 (函数指针变量名) (形参列表)
#include<stdio.h>
int mux(int x ,int y)
{
return x * y;
}
int main()
{
int (*p)(int,int);//定义一个函数指针变量,有两个int类型的形参
int num;
num = mux(10, 9);
printf("num=%d\n",num);
p = mux; //p和mux都有两个int类型的形参,mux是函数的地址
num = (*p)(11, 10); // (*p)(11,10)相当于p(11,10)
printf("num=%d\n", num);
return 0;
}
函数指针数组
由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。是个数组,每个元素都是一个函数指针变量
形式:类型名 (*数组名[元素个数])(形参列表)
#include<stdio.h>
int mux(int x ,int y)
{
return x * y;
}
int add(int x, int y)
{
return x + y;
}
int main()
{
int(*p[2])(int,int) = {mux,add};
int num;
num = p[0](2, 3);
printf("%d\n", num);
num = (*p[1])(2, 3);
printf("%d\n", num);
return 0;
}
#include<stdio.h>
int mux(int x ,int y)
{
return x * y;
}
int add(int x, int y)
{
return x + y;
}
int pro(int (*p)(int, int),int x,int y)//定义pro函数一个函数指针形参(两个int形参),两个int形参
{
int q;
q = p(x, y); //相当于q =(*p)(x, y)
return q;
}
int main()
{
int num;
num = pro(mux,2, 3); //给pro函数传入mux首地址,2,3实参
printf("%d\n", num);
num = pro(add,2, 3);
printf("%d\n", num);
return 0;
}
指针概念的小结
int *a[10];这是个指针数组,数组a 中有10 个整型的指针变量
int (*a)[10];数组指针变量,它是个指针变量。它占4 个字节
int **p;指针的指针,保存指针变量的地址。
int *f(void);:*f 没有用括号括起来,它是个函数的声明,声明的这个函数返回值为int *类型的。
int (*f)(void);*f 用括号括起来了,*修饰f 说明,f 是个指针变量。f 是个函数指针变量,存放函数的地址,它指向的函数