C语言学习(7)第六章指针6.7-6.13-创新互联
目录
创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于成都网站设计、网站建设、外贸网站建设、常德网络推广、小程序开发、常德网络营销、常德企业策划、常德品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们大的嘉奖;创新互联公司为所有大学生创业者提供常德建站搭建服务,24小时服务热线:13518219792,官方网址:www.cdcxhl.com6.7 指针数组
6.8 指针的指针
6.9 字符串和指针
6.10 数组指针
6.10.1 二维数组
6.10.2 数组指针的定义方法
6.10.3 各种数组指针的定义
6.10.4 数组名字取地址,变成数组指针
6.10.5 数组名字和指针变量的区别
6.10.6 数组指针取*
6.11 指针和函数的关系
6.11.1 指针作为函数的参数
6.11.2 指针作为函数的返回值
6.11.3 指针保存函数的地址(函数指针)
6.12 经常容易混淆的指针
6.13 特殊指针
6.7 指针数组
1、指针和数组的关系
指针可以保存数组元素的地址,那么也可以定义一个数组,数组中有若干个相同类型的指针变量,这个数组成为指针数组,它是若干个相同类型的指针变量构成的集合。
2、定义方法:类型名 * 数组名[元素个数];
int* p[5]; //定义了一个整形的指针数组p,数组名是p,有5个元素,每个元素都是整形指针变量int*,它们分别储存了一个整形数据的地址
int a[5];
p[0] = a;
6.8 指针的指针指针的指针**p,即指针的地址。我们定义的一个指针变量本身占用4个字节,指针变量也有地址编号,但是类型不同,一个是int*,一个是int**。
int** p表示*p指向的是int*类型数据的地址。无论几层的指针,其占用的存储空间大小都是4个字节。
int a = 0x12345678;
int* p,**p_p;
p = &a; //p中存放的是a的地址编号,p指向a
p_p = &p; //p_p中存放的是p的地址编号,p_p指向p
int main()
{
int a = 5, b = 7;
int* p, ** p_p;
p = &a; //p中储存了a的地址
p_p = & p; //p_p中储存了p的地址
printf("p = %p, *p = %d\n", p,*p);
printf("p_p = %p, *p_p = %p, **p_p = %d\n", p_p,*p_p,**p_p);
return 0;
}
运行的结果是:
p = 006FF8D0, * p = 5
p_p = 006FF8B8, *p_p = 006FF8D0, **p_p = 5
p_p指向p,储存的是p的地址,取p_p指向的值也就是p的地址,再取值也就是a的值了。
6.9 字符串和指针1、字符串:以\0结尾的若干个字符的集合:比如"helloworld",这个字符串有11个字符,分别是h e l l o w o r l d \0。
字符串的地址就是第一个字符的地址:"helloworld"的地址就是字符串中"h"的地址。
可以定义一个字符指针变量:char* s = "helloworld";
2、字符串的存储形式:数组、文字常量区、堆
(1)字符串存放在数组中
即在内存(栈、静态全局区)中开辟了一段空间来存放字符串。
char strint[100] = "I Love China";
定义了一个字符数组string来存放100个字符,并且用"I Love China"来给数组进行初始化。普通全局数组,内存分配在静态全局区,普通局部数组,内存分配在栈区,静态数组(静态全局数组、静态局部数组),内存分配在静态全局区
(2)字符串存放在文字常量区
在文字常量区开辟了一段空间存放字符串,将字符串的首地址赋给指针变量。
char *str = "I Love China";
str只是保存了"I"的地址,也就是字符串的首地址。而这串字符串存放在文字常量区。
(3)字符串存放在堆区
使用malloc等函数在堆区申请空间,将字符串拷贝到堆区
char* str = (char*)malloc(10*sizeof(char)); //动态申请了10个字节的存储空间,首地址给str赋值
strcpy(str."I Love China"); //将字符串"I Love China"拷贝到str指向的内存里
3、字符串的可修改性
字符串的内容是否可以被修改,取决于字符串存放在哪里
(1)存放在数组中的字符串(数组没有被const修饰),内容可以被修改
char str[100] = "Hello World";
str[2] = Y;
int main()
{
char str[100] = "Hello World";
str[2] = 'Y';
printf("%s", str);
return 0;
}
运行的结果:
HeYlo World
(2)存放在文字常量区的内容不可以被修改
char* str = "I Love China";
*str = 'y';//错误,文字常量区的内容不可以修改
但可以给str重新赋值,让它指向别的内容。
(3)堆区中的内容可以被修改
cahr* str = (char*)malloc(10);
strcpy(str,"I Love China")l
*str = 'y';//正确,堆区中的内容可以被修改
4、初始化
(1)字符数组的初始化
char buf_aver[20] = "Hello Wolrd";
(2)指针指向文字常量区初始化
char *buf_point = "hello world";
(3)指针指向堆区,不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf等方法将字符串拷贝到堆区
char* buf_heap;
buf_heap = (char*)malloc(15);
strcpy(bug_heap,"helloworld");
scanf("%s",buf_heap);
5、使用时赋值
(1)字符数组:使用scanf或strcpy
char buf[20] = "hello world";
buf = "hello kitty"; //错误,因为字符数组的名字是数组的首地址,是个常量,不能给常量赋值
strcpy(buf,"hello kitty"); //正确,使用strcpy函数将"hello kitty"赋给buf数组
scanf("%s",buf); //正确,将键盘输入的字符串赋给buf数组
(2)指针指向文字常量区
char* buf_point = "hello world";
buf_point = "hello kitty"; //正确,这是将buf_point指向了一个新的字符串
strcpy(buf_point,"hello kitty"); //错误,这时buf_point指向文字常量区,不能给常量赋值
(3)指针指向堆区
char* buf_heap;
buf_heap = (char*)malloc(15);
strcpy(buf_heap,"hello kitty");
scanf("%s",buf_heap);
【总结】
1、指针可以指向文字常量区
(1)指针指向的文字常量区的内容不可以修改
(2)指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。
2、指针可以指向堆区
(1)指针指向的堆区的内容可以修改。
(2)指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。3、指针也可以指向数组(非const 修饰)
例:
char buf[20]="hello world";
char *str=buf;
这种情况下:
1.可以修改buf 数组的内容。
2.可以通过str修改str指向的内存的内容,即数组buf的内容
3.不能给buf赋值buf= “hello kitty" ;错误的。
4.可以给st赋值,及str指向别处。str= “hello kitty'
6.10 数组指针 6.10.1 二维数组二维数组,可以看成是由多个一维数组组成的,有行有列。
int a[3][5];//3行5列的二维数组,可以看出是由3个一维数组组成,可以认为a中有3个元素,每个元素是一个一维数组
a是二维数组的首地址,a+1指向下一个元素,即下一行。
int main()
{
int a[3][5] = {
{1,3,5,7,9},
{2,4,6,8,10},
{7,9,1,13,12}
};
printf("a = %p\n", a);
printf("a+1 = %p\n", a+1);
return 0;
}
运行的结果:
a = 005AFD74
a + 1 = 005AFD88
a是数组的首地址,一行有5个整形,占用20个字节,所以a+1 比 a多20个字节,它指向下一行。
6.10.2 数组指针的定义方法指向的数组的类型(*指针变量名)[指向的数组的元素个数]
int(*p)[5]; //定义了一个数组指针变量p,p指向的是整形的有5个元素的数组,p+1往下指5个整形
注意:如果不加括号就变成int* p[5],这是一个指针数组,是一个一维数组,有5个元素,每个元素都是整形指针,占用20个字节,而int(*p)[5]是一个指针变量,占用4个字节。
(1)一维数组指针主要配合二维数组使用,此时一维数组指针的元素个数必须等于二维数组的列数。
例如:
int main()
{
int a[3][5] = {
{1,3,5,7,9},
{2,4,6,8,10},
{7,9,1,13,12}
};
int(*p)[5]; //一维数组指针的元素个数必须等于二维数组的列数
p = a;
printf("a = %p, p = %p, &a[0][0] = %p\n", a,p, &a[0][0]);
printf("a+1 = %p,p+1 = %p, &a[1][0] = %p\n", a + 1,p + 1, &a[1][0]);
return 0;
}
运行的结果:
a = 00EFF9A4, p = 00EFF9A4, & a[0][0] = 00EFF9A4
a + 1 = 00EFF9B8, p + 1 = 00EFF9B8, &a[1][0] = 00EFF9B8
(2)常用在函数传参上,实参是n维数组,必须定义一个相同类型的n-1维数组的形参去接。
例如:
void fun(int(*p)[5],int x, int y)
{
p[1][2] = 100;
}
int main()
{
int a[3][5] = {
{1,3,5,7,9},
{2,4,6,8,10},
{7,9,1,13,12}
};
fun(a, 3, 5);
printf("a[1][2] = %d\n", a[1][2]);
return 0;
}
a是数组的首地址,是数组地址,或者叫数组指针,那么形参也必须是相同类型的,所以用int(*p)[5]数组指针类型去接
6.10.3 各种数组指针的定义(1)一维数组指针,加1后指向下一个一维数组
int(*p)[5],配合每行有5个整形的二维数组使用,元素个数必须与二维数组的列数相同。
p指向第一行第一个元素, p+1指向第二行第一个元素。
(2)二维数组指针,加1后指向下一个二维数组
int(*p)[4][5],配合三维数组使用,这个三维数组是由若干个4行5列的二维数组组成的。
p指向第一个二维数组的第一行第一个元素,p+1指向第二个二维数组的第一行第一个元素。
(3)多维数组,类似地。
6.10.4 数组名字取地址,变成数组指针int a[10];
a 是数组的首地址,是int*类型的整形指针,a+1跳一个元素,是a[1]的地址。
&a 变成了一个一维数组指针,是int(*p)[10]类型的一维数组指针,(&a)+1 和 &a相差一个数组,即10个元素,即40个字节
而a 和 &a代表的地址编号是一样的,它们都指向同一个内存地址,但所表示的数据类型不一样。
int main()
{
int a[10];
printf("a = %p\n", a);
printf("a+1 = %p\n", a + 1);
printf("&a = %p\n", &a); //&a变成了一维数组指针类型,int(*p)[10]
printf("(&a)+1 = %p\n", &a+1); //&a+1指向下一个一维数组。
return 0;
}
运行的结果:
a = 00F5FA9C
a + 1 = 00F5FAA0
& a = 00F5FA9C
(&a) + 1 = 00F5FAC4
6.10.5 数组名字和指针变量的区别int a[5];
int* p;
p = a;
数组名字是数组的首地址,是a[0]的地址,存放的是地址编号,指向a[0];
p = a;即p保存了a的地址,也指向a[0],所以在引用数组元素的时候a和p等价。
注意
1、a是常量,是a[0]的地址,而p是指针变量,所以可以给p赋值,但不能给a赋值
2、对a和p的取地址结果不同,p是指针变量,对它取地址得到变量p的地址编号,即指针的指针int**,而a是数组的名字,对a取地址&a得到数组指针int(*p)[5],a和&a的地址编号是一样的,都指向a[0]。
6.10.6 数组指针取*数组指针取*不是取值的意思,而是指针的类型发生变化,
一维数组指针取*,结果为它指向的一维数组第0个元素的地址,它们还是指向同一个地方。
二维数组指针取*。结果为一维数组指针,它们还是指向同一个地方。
三维数组指针取*。结果为二维数组指针,它们还是指向同一个地方。
int main()
{
int a[3][5] = {0};
printf("a = %p\n", a); //a是二维数组的名字,是第0个一维数组的首地址,是个数组指针
printf("a +1 = %p\n", a + 1); //a +1是下一个一维数组的首地址,即第1个一维数组的地址
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]
int(*p)[5];
p = a;
printf("p = %p\n", p);
printf("*p = %p\n", *p);
printf("*p +1 = %p", *p + 1);
return 0;
}
运行的结果:
a = 00AFFA68
a + 1 = 00AFFA7C
* a = 00AFFA68
* a + 1 = 00AFFA6C
p = 00AFFA68
* p = 00AFFA68
* p + 1 = 00AFFA6C
6.11 指针和函数的关系 6.11.1 指针作为函数的参数我们可以给函数传一个整形、浮点型、字符型的数据,也可以传一个地址。
int num;
scanf("%d",&num);
scanf函数通过键盘输入获取一个整数,然后将它存到一个地址处(这里是num的地址,也就是给num赋值)
函数传参
1、传数值
void swap(int x, int y) //x,y是形参,是定义被调函数时,函数名括号里的数据
{
int temp;
temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10, b = 20;
swap(a, b); //a,b是实参,是调用函数时传的参数
printf("a = %d, b = %d\n", a, b);
return 0;
}
运行的结果:
a = 10, b = 20
swap函数调换的是x和y两个数的值,而输出的a和b没有改变。
所以给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值。
2、传地址
例1:
void swap(int* x, int* y)
{
int* temp;
temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10, b = 20;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
结果是:a = 10; b = 20;
例2:
void swap(int* x, int* y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a = 10, b = 20;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
a = 20, b = 10,成功交换了a和b的值。
这里传给swap函数的是a和b的地址,swap函数将地址指向的值相互调换,从而改变了主调函数的实参
!!!要想改变主调函数中的变量的值,必须通过传变量地址,并且使用*+地址赋值,无论这个变量是什么类型的,才能改变主调函数实参的值
void fun(char* q)
{
q = "hello kitty";
}
int main()
{
char* p = "hello world";
fun(p);
printf("%s", p);
return 0;
}
运行的结果:
hello world
fun函数中改变的是局部变量q,没有改变main函数中变量p,所以p还是指向“hello world”
void fun(char** q)
{
*q = "hello kitty";
}
int main()
{
char* p = "hello world";
fun(&p);
printf("%s", p);
return 0;
}
hello kitty
3、传数组
给函数传数组的时候不能一下将数组的内容作为整体传进去,只能传数组的名字进去,而数组的名字是数组的首地址,即4个字节的地址编号
(1)传一维数组的地址,只能传数组的名字(首地址),所以要用对应数据类型指针去接,用int* p或int p[],编译器都认为是int*的整形指针
//void fun(int p[])
void fun(int* p)
{
printf("p[2] = %d\n", p[2]);
*(p + 3) = 100;
}
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
fun(a);
printf("a[3] = %d\n", a[3]);
return 0;
}
(2)传二维数组的地址,要用一维数组指针去接,用int(*p)[3]或int p[][3],编译器都认为是数组指针
//void fun(int p[][3], int x, int y)
void fun(int(*p)[3], int x, int y)
{
int i, j;
for (i = 0; i< x; i++)
{
for ( j = 0; j< y; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main()
{
int a[2][3] = {
{1,4,5},
{2,8,0}
};
fun(a,2,3);
return 0;
}
(3)传指针数组,也只能传首个元素的地址,由于每个元素都是指针,所以传的是指针的指针,要用指针的指针char**,或指针数组char* p去接,编译器都认为是指针的指针。
//void fun(char* p[],int x)
void fun(char** p, int x)
{
int i;
for (i = 0; i< x; i++)
{
printf("q[%d] = %s\n", i,p[i]);
}
}
int main()
{
char* p[3] = { "hello","world","kitty" }; //定义了一个指针数组,数组的每个元素都是char*类型的字符指针
fun(p, 3);
return 0;
}
6.11.2 指针作为函数的返回值char* fun()
{
char str[100] = "hello world";
return str;
}
int main()
{
char* p;
p = fun();
printf("p = %s\n", p);
return 0;
}
运行的结果:
p = hello world
注意,虽然在VS中得到了p = hello world,报了警告:warning C4172: 返回局部变量或临时变量的地址: str,
fun函数中定义的str是局部变量,在fun函数结束后被释放了,所以p在打印时是一个野指针,指向的值是不确定的,有可能p指向的数据被别的替换了,不再是“hello world”。
修改方法1:
返回静态局部数组的地址,静态局部变量的生命周期是从第一次调用函数到程序结束,而不是像普通局部变量一样函数结束就释放掉。
char* fun()
{
static char str[100] = "hello world";
return str;
}
修改方法2:
返回文字常量区的地址
char* fun()
{
char* str = "hello world";
return str;
}
修改方法3:
返回堆内存的地址,堆区的内容一直存在,直到free释放。
#include#include#includechar* fun()
{
char* str;
str = (char*)malloc(100);
strcpy_s(str,100, "helloworld");
return str;
}
int main()
{
char* p;
p = fun();
printf("p = %s\n", p);
free(p);
return 0;
}
6.11.3 指针保存函数的地址(函数指针)1、概念
我们定义的函数在运行的时候,会将函数的指令加载到内存的代码段,函数也有起始地址
C语言规定:函数的名字就是函数的首地址,即函数的入口地址。
所以可以定义一个指针变量,存放函数的地址,即函数指针变量。
2、函数指针用处
保存函数的入口地址,在项目开发中,经常需要便携或者调用带函数指针参数的函数
3、函数指针变量的定义
返回值类型(*函数指针变量名)(形参列表);
int(*p)(int,int);//定义了一个函数指针变量p,p指向的函数必须有一个整形的返回值,且有两个整形的参数。
比如max函数:
int max(int x,int y)
{
}
就满足这个条件,所以可以用p来保存类如max函数的地址
4、调用函数的方法
(1)通过函数的名字调函数(最常用)
int max(int x, int y)
{
}
(2)通过函数指针变量调用函数
#includeint max(int x,int y)
{
if (x >y) return x;
else return y;
}
int min(int x, int y)
{
if (x >y) return y;
else return x;
}
int main()
{
int (*p)(int, int);
int num;
num = max(10, 20); //通过函数名调用函数
printf("max = %d\n", num);
p = min;
num = (*p)(10, 20); //通过函数指针变量调用函数,使用p或*p都可以
printf("min = %d\n", num);
return 0;
}
5、函数指针数组
概念:这个数组是由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储
注意:函数指针数组是一个数组,每个元素都是一个函数指针变量
定义:类型名(*数组名[元素个数])(形参列表)
int(*p[5])(int,int);
定义了一个函数指针数组,有5个元素p[0]~p[4],每个元素都是函数指针变量,每个函数指针变量指向的函数,必须有整形的返回值和两个整形参数
int max(int x, int y)
{
if (x >y) return x;
else return y;
}
int min(int x, int y)
{
if (x >y) return y;
else return x;
}
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 main()
{
int min_xy;
int(*p[5])(int, int) = { mux,min,add,max,sub };
min_xy = p[1](2, 5);
printf("min_xy = %d\n", min_xy);
return 0;
}
运行的结果:
min_xy = 2
这些函数都有一个共同点:返回值是整形int,都有两个整形参数。
6、应用举例
#includeint max(int x, int y)
{
if (x >y) return x;
else return y;
}
int min(int x, int y)
{
if (x >y) return y;
else return x;
}
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 process(int(*p)(int, int), int x, int y)
{
int ret;
ret = (*p)(x, y);
return ret;
}
int main()
{
int num;
num = process(add, 10, 20);
printf("num = %d\n", num);
return 0;
}
6.12 经常容易混淆的指针int* a[10]; //这是一个指针数组,占用40个字节,数组a中有10个元素,每个元素都是整形指针
int(*a)[10]; //这是一个数组指针变量a,占用4个字节,它指向一个有10个元素的一维整形数组,a+1指向下一个数组(即跳过10个整形)
int** p; //二级指针,指针的指针,保存指针变量的地址
int* f(void); //这是函数的声明,声明的这个函数返回值是整形指针int*类型的
int(*f)(void); //*修饰f,f是一个函数指针变量,存放函数的入口地址,这个函数的返回值是整形的,没有参数
6.13 特殊指针1、空类型的指针(void*)
char*类型的指针变量,只能保存char型的数据的地址;
int*类型的指针变量,只能保存int型的数据的地址;
而void*类型的指针变量,可以指向任何类型的数据的地址,同样在32位系统下占用4个字节
2、NULL
空指针:char* p = NULL;
可以认为p哪里都不指向,也可以认为p指向内存编号为0的存储单位,p存放的是0x00 00 00 00。
一般用NULL给指针初始化。
3、main函数传参
int argc 是传参的个数,char* argv[]保存的是传过来参数的首地址
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
本文名称:C语言学习(7)第六章指针6.7-6.13-创新互联
路径分享:http://pwwzsj.com/article/dejpgd.html