C语言学习(7)第六章指针6.7-6.13-创新互联

目录

创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于成都网站设计、网站建设、外贸网站建设、常德网络推广、小程序开发、常德网络营销、常德企业策划、常德品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们大的嘉奖;创新互联公司为所有大学生创业者提供常德建站搭建服务,24小时服务热线:13518219792,官方网址:www.cdcxhl.com

6.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