c语言基础(四)

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 是个函数指针变量,存放函数的地址,它指向的函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值