c语言-指针

指针是什么:

  • 地址:
    • 位置信息
    • 类型信息

区别:存储单元的内容

对变量的访问都是通过地址进行

指针变量是什么:

  • 存放地址的变量

指针变量作为函数参数

注意:

  • c语言中实参变量和形参变量之间的数据传递是单向传递的“值传递”
  • 而指针作为函数参数时,同样要遵循“单向值传递的规则`!

指针指向数组:

指针运算

如: int a[10]; int *p = a;

指针法 ==> 下标法

  • p ==> &a[0]
  • p+1 ==> &a[1]
  • *p ===> a[0]
  • *(p+1) ===> a[1]
  • p2-p1 ===> 2
1
2
3
4
5
int *p1 = a[3];
int *p2 = a[5]
printf("%d\n",p2-p1);
// ==> p2 - p1 ==> 2
//表示元素之间差2个元素

注意:两个地址不能相加,如p1+p2没有意义

引用数组元素的三种方法

  • 1.下标法
    a[i]
  • 2.通过数组名计算数组元素的地址,找出元素的值
  • {1.2.效率相同,都是转化为*(a+i)}
  • 3.使用指针变量指向数组元素(效率最高,不必每次都重新计算地址)

注意:

  • 不能将数组名a++,因为本质上:数组名是一个常量地址,不可以改变;
  • 而指针变量可以进行运算,因为它是一个变量`

数组的高级用法:

  • p[i]是什么
1
p[1] == *(p+1) = a[0+1] == a[1]
  • 指针引用数组元素方法总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>

int main(int argc, char const *argv[])
{
/* code */
int a[100] = {1,2,3,4,5,6,7,8,9,10};
int *p = &a[0]; //初始化
//int *p = a

//1.p++
p++;
printf("p++: %d\n",*p);
// == 》 2

p = a;
//2. *p++ == *(p++)
//*和++优先级级相同,自右向左运算
printf("*p++: %d\n",*p++);
printf("*p: %d\n",*p);
p = a;
printf("*++p:%d\n",*(++p));
//注意:++p 和 p++ 的区别

p=a;
//3.*(p++) and *(p++)
//这里注意自增自减符的特性即可

//4.++(*p)
p++;
printf("*p: %d\n",*p);
printf("++(*p):%d\n",++(*p));
//这里注意是将数组中的值自增,而不是p移动到下一个数组元素

//5. *(++p) == a[i++] *(p--) == a[i--] *(--p) == a[--i]
//p先自增或自减运算,在进行取值*运算

p = a;
//输出数组的100个元素
//方法一
printf("输出数组的100个元素【方法一】:\n");
while (p<a+100)
{
printf("%d",*p++);
}
printf("\n");


p=a;
//方法二
printf("输出数组的100个元素【方法二】:\n");
while(p<a+100)
{
printf("%d",*p);
p++;
}
printf("\n");

return 0;
}

使用数组名作为函数参数

通过指针引用多维数组

1.多维数组元素的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char const *argv[])
{
int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
printf("a: %d,*a: %d\n",a,*a);
//a: 0行起始地址 *a: 0行0列元素地址
printf("a[0]: %d,*(a+0): %d\n",a[0],*(a+0));
//a[0]: *(a+0): 0行0列元素地址
printf("&a[0]: %d,&a[0][0]:%d\n",&a[0],&a[0][0]);
//&a[0]: 0行起始地址 &a[0][0]: 0行0列元素地址

printf("a[1]: %d, a+1: %d\n",a[1],a+1);
//a[1]: 1行0列元素地址 a+1: 1行起始地址
printf("&a[1][0]: %d,*(a+1)+0: %d\n",&a[1][0],*(a+1)+0);
//第1行0列元素地址
printf("a[2]: %d,*(a+2):%d\n",a[2],*(a+2));
//第2行0元素地址

printf("a[1][0]: %d,*(*(a+1)+0): %d",a[1][0],*(*(a+1)+0));
//1行0列元素的值


return 0;
}
1
2
3
4
5
6
7
a: 1870657792,*a: 1870657792
a[0]: 1870657792,*(a+0): 1870657792
&a[0]: 1870657792,&a[0][0]:1870657792
a[1]: 1870657808, a+1: 1870657808
&a[1][0]1870657808,*(a+1)+01870657808
a[2]: 1870657824,*(a+2):1870657824
a[1][0]: 9,*(*(a+1)+0): 9

tips:在二维数组a[3][4]中

  • a[0] 类型为int*型(指向整型变量
  • a 类型为int (*p)[4] (指向含有4个元素的一维数组)

2.指向多为数组元素的指针变量

  • 利用二维数组的顺序存储方式 a[i][j] ===> a + (i*m +j) ===》基类型: int*
  • 利用指向包含m个元素的一维数组 int (*p)[m] ===>基类型:一维数组

3.指向数组的指针作为函数参数

  • 1.用指向变量的指针变量
  • 2.用指向一维数组的指针变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void average(float *p,int n)
//指针变量
{

}

void search(float (*p)[4],int n)
//p是指向具有4个元素的一维数组的指针
{

}
int main()
{
float score[3][4] = {};
average(*score,12);
// -- *score int* 型

search(score);
//--- int(*p)[4]型
}

通过指针引用字符串

字符串的引用方式:

  • 数组名+下标
  • 用字符指针变量指向一个字符串常量
1
2
3
4
5
#include <stdio.h>
int main()
{
char* string = "absfag adg";
return 0;}

字符指针作函数参数

使用字符指针变量和字符数组的比较

tips:字符串是常量,

  • 在C语言中没有专门的字符串变量,如果想将一个字符串存放在变量中以便保存,必须使用字符数组,即用一个字符型数组来存放一个字符串,数组中每一个元素存放一个字符。例如“char a[10]=”love”.”

    • 字符数组由若干个元素组成每个元素放一个字符;
    • 字符指针变量存放的是地址
  • 2.赋值方式不同:

    • 可以对字符指针变量赋值
    • 不可以对数组名赋值
  • 3.初始化的不同:

字符数组:

  • 字符数组仅仅可以在定义!时可以整体赋值
  • 之后不能使用赋值语句对全部元素进行赋值

字符指针变量:

  • 可以先定义字符指针,然后再给字符指针整体赋值
  • 4.存储单元的内容:
    • 字符数组:预先定义大小
    • 字符指针:分配一个指针变量的大小
      • 16位机器的代码时,指针占2个字节

      • 32位机器的代码时,指针占4个字节

      • 64位机器的代码时,指针占8个字节

    • 指针变量的值可以改变
    • 字符数组名代表一个固定的值,不能改变
    • 字符数组的值是可以改变的(可以再进行赋值)
    • 字符指针变量指向的字符串常量中的内容是不可以被取代在(不能再进行赋值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>

int main(int argc, char const *argv[])
{
char a[] = "abcdefg"; //字符数组
char b[14] = "abcdefg";//字符数组
char *c = "house"; //字符串常量



//经典错误:
//a = "adags";
//b[] = "aggadsga";


//注意字符数组仅仅可以在定义!时可以整体赋值
//之后不能使用赋值语句对全部元素进行赋值

printf("%s\n%s\n",a,b);

//经典错误:
//*(c+1) = 'r';
//c[1] = 'r';
//字符指针变量指向的字符串常量中的内容是不可以被取代的
//即不能被赋值

printf("%s\n",c);


char *d = a;//本质上也是字符数组,是变量
*(d+1) = '0'; //合法
printf("%s\n",d);

return 0;
}



1
2
3
4
abcdefg
abcdefg
house
a0cdefg
  • 7.引用数组:

    • 字符数组:
      • 下标法(数组名 or 下标)
      • 地址法
    • 指针变量指向数组:
      • char *a = "china";
      • 可以用a[5]获取相应的值
    • 用指针变量指向一个格式字符串,可以用其代替printf中的格式字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用指针变量指向一个格式字符串,可以用其代替printf中的格式字符串
int m = 1;
float n=2.5;

char *format_1; //字符指针
format_1 = "m=%d,n=%f\n";

char format_2[] = "m=%d,n=%f\n";//字符数组

//经典错误,数组要定义大小
//char format_3[];
//format_3 = "m=%d,n=%f\n";

printf(format_1,m,n);
printf(format_2,m,n);
1
2
m=1,n=2.500000
m=1,n=2.500000

指向函数的指针

什么是函数指针:

  • 函数名代表函数的起始地址
1
2
3
4
5
int (*p)(int,int)
//p是指向函数的指针变量
//可以指向 [函数返回值类型为int]
//并且函数有 [两个整型参数的函数]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int add(int x,int y)
{
printf("%d,%d\n",x,y);
return (x+y);
}

/* code */
//定义方式1
int (*f)(int,int);
//定义方式2
int (*f)();
//错误定义:
//float (*f)(int);

f = add;


//调用方式1
add(2,3);
//调用方式2
f(4,5);
//调用方式3
(*f)(7,9)
  • 赋值:
    • 指向函数的指针只能指向在定义时指向的类型的函数
    • 只需要给出函数名,不能给出函数参数
1
2
p = max(a,b); //wrong
p = max;//right
  • 调用:
    • c = (*p)(a,b);
    • c = p(a,b);
    • 注意返回值c的类型
  • 对函数的指针变量不能进行算术运算,没有意义
  • 通过指针变量可以先后调用不同的函数
1
2
if(n==1) p = max;
else if(n==2) p = min;

用指向函数的指针作为函数参数:

  • 把函数的入口地址作为参数传递到其他函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int fun(int x,int y,int(*p)(int,int)); 
int max(int,int);
int min(int,int);
int add(int,int);

int a=34,b=-21,n;
scanf("%d",&n);
if(n==1) fun(a,b,max);
else if(n==2) fun(a,b,min);
else if(n==3) fun(a,b,add);

int fun(int x, int y,int (*p)(int,int))
{
int result;
result = (*p)(x,y);
printf("%d\n",result);
}

返回指针值的函数

  • 返回值的类型是指针类型
    • 类型名 * 函数名(参数列表)

指针数组和多重指针

指针数组:

  • 一个数组–里面的元素为指针类型数据

  • 一般用来装多个字符串

    1
    2
    int *p[4];
    //*比[]优先级低

实例:利用指针数组实现字符串排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <string.h>
#define N 13

int main(int argc, char const *argv[])
{
void sort(char *name[],int n);
void print(char *name[],int n);

char *name[] ={"4PGC938", "2IYE230", "3CIO720", "1ICK750", "1OHV845", "4JZY524", "1ICK750", "3CIO720","1OHV845", "1OHV845","2RLA629", "2RLA629", "3ATW723"};

sort(name,N);
print(name,N);

return 0;
}


void sort(char *name[],int n) //使用选择排序
{
char *temp;
int i,j,k;
for(i=0;i<N-1;i++)
{
for(j=i+1;j<N;j++)
{
if(strcmp(name[i],name[j])>0)
/*如果返回值小于 0,则表示 str1 小于 str2。
如果返回值大于 0,则表示 str1 大于 str2。
如果返回值等于 0,则表示 str1 等于 str2。*/
{
temp = name[i];
name[i] = name[j];
name[j] = temp;
}
}
}
}


void print(char *name[],int n)
{
int i;
for(i=0;i<N;i++) printf("%s\n",name[i]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//输出
1ICK750
1ICK750
1OHV845
1OHV845
1OHV845
2IYE230
2RLA629
2RLA629
3ATW723
3CIO720
3CIO720
4JZY524
4PGC938

注意点:

  • 不能写成以下形式:
    if ( * name[ k]> * name[j] ) k= j ;
    这样只比较name [ k ] 和name [ j] 所指向的宇符串中的笫1 个宇符。

    • 字符串比较应当用strcmp 函数。
    • 想想一下如果字符串的第一个字符都相同,那么会发生什么。
  • tips:

1
2
3
4
5
6
7
8
9
10
11
12
//print 函数也可改写为以下形式:
void print_2( char * name[], int n)
{
int i=0;
char * p;
p= name[0] ;
while(i < n )
{p= * (name+ i++) ;
// p为 指向数组的指针变量
printf("%s\n" , p) ;
}
}

指向指针数据的指针变量

  • 指向指针的指针
1
2
3
4
5
//定义
char **p;
//相当于
char *(*p);

实例1:char**p ---> char *name[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main(int argc, char const *argv[])
{

char *name[]= {"Follow me","BASIC","Great Wall","FORTRAN" ,"Computer design"};
char **p;
int i;
for(i=0;i<5;i++)
{
p=name+i;
//name是一维指针
//p指向一维指针
printf("%s\n",*p);
}
return 0;
}

实例2:int **p --> int *num[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int demo02(void)
{
int a[5] = {1,3,5,7,9};
int *num[5] = {&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i;
p = num;
for(i=0;i<5;i++)
{
printf("%d",**p);
p++;
}
printf("\n");
return 0;

}

指针数组作为main函数的形参

1
int main( int argc , char * argv[ ] )
  • argc (argument count 的缩写,意思是参数个数)

  • argv(argument vector 缩写,意思是参数向量)

  • 如果用带参数的main 函数,其笫一个形参必须是int 型,用来接收形参个数.

  • 第二个形参必须是宇符指针数组,用来接收从操作系统命令行传来的字符串中首字符的地址。

什么情况会用到这个?

例如在DOS, UNIX 或Linux 等系统的操作命令状态下,在命令行中包括了命令名和需要传给ma in 函数的参数。

  • 命令行的一般形式为:
    • 命令名参数1 参数2…参数n

动态内存分配与指向它的指针

  • 动态分配区域
    • 堆区(heap)
  • 静态存储区域:
    • 栈区(stack)

怎样动态分配内存:

malloc、calloc、free、realloc函数

1.malloc函数

1
void * inalloc( unsigned int size ) 
  • size:开辟连续空间,大小为size字节

  • 返回值:如果此函数未能成功地执行(例如内存空间不足), 则返回空指针(NU LL ) 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

int main () {
char *str;

/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);

/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);

free(str);

return(0);
}

2.calloc函数-动态数组

1
void *calloc(size_t nitems, size_t size)
  • nitems − 分配一维数组元素个数

  • size − 每个元素个数的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int main () {
int i, n;
int *a;

printf("Number of elements to be entered:");
scanf("%d",&n);

a = (int*)calloc(n, sizeof(int));
printf("Enter %d numbers:\n",n);
for( i=0 ; i < n ; i++ ) {
scanf("%d",&a[i]);
}

printf("The numbers entered are: ");
for( i=0 ; i < n ; i++ ) {
printf("%d ",a[i]);
}
free( a );

return(0);
}

3.realloc函数 – 重新分配动态存储区

1
void *realloc(void *ptr, size_t size)

如果已经通过matloc 函数或ca lloc 函数获得了动态空间,想改变其大小,可以用recalloc 函数重新分配。

  • ptr - 这是指向先前用 malloc、calloc 或 realloc 分配的内存块的指针,将被重新分配。如果该指针为 NULL,函数将分配一个新的内存块并返回一个指向该内存块的指针。

  • size - 这是内存块的新大小,以字节为单位。如果它为 0 且 ptr 指向一个现有的内存块,ptr 指向的内存块将被解分配,并返回一个 NULL 指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

int main () {
char *str;

/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);

/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);

free(str);

return(0);
}

4.free函数–释放动态内存

1
void free(void *ptr)
  • ptr - 这是指向先前用 malloc、calloc 或 realloc 分配的内存块的指针,将被取消分配。如果参数传递的是空指针,则不会执行任何操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main () {
char *str;

/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);

/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);

/* Deallocate allocated memory */
free(str);

return(0);
}

void指针类型: - 指向空类型不指向确定的类型的数据

注意! 不要把”指向void 类型“理解为能指向“ 任何的类型”的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a=3;
int * p1 = &a;
char * p2;
void * p3;
p3 = (void *)p1;
p2 = (char *)p3;
printf("%d", *p1);
p3 = &a ; printf("%d", *p3);
//定义a 为整型变扯
// pl 指向int 型变扯
// p2 指向c ha r 型变扯
// p3 为无类型指针变批(基类型为void 型)
//将pl 的值转换为void *类型. 然后赋值给p3
//将p3 的值转换为char *类型. 然后赋值给p2
//合法,输出整型变蜇a 的值
//错误, p3 是无指向的,不能指向a

void指针存在的意义:

  • 当使用动态内存分配函数时,我们其实只希望获得其动态存储区的起始地址

    在c89中

    malloc函数的返回地址一律指向字符型数据,即得到char* 型指针)

    原型为:

    1
    char * malloc( unsigned int size)
    C99对此作了修改

    这些函数不是返回char *指针,而是使其无指向,函数返回void * 指针。这种指针称为空类型指针(typeless pointer),它不指向任一种具体的类型数据,只提供一个纯地址。

    • 不能通过void * 指针存取数据,在程序中它只是过渡性
    • 只有转换为有指向的地址,才能存取数据
  • 自动进行类型转换

    1
    2
    3
    4
    5
    int *pt;
    //手动
    pt = (int*)malloc(100);
    //自动
    pt = malloc(100);

c语言-指针
https://clint456.github.io/2023/09/10/C语言-2023-9-11-指针/
作者
clint
发布于
2023年9月11日
许可协议