Post

指针

指针是什么:

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

区别:存储单元的内容

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

指针变量是什么:

  • 存放地址的变量

指针变量作为函数参数

注意:

  • 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)+0: 1870657808
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”.”

  • 1.
    • 字符数组由若干个元素组成每个元素放一个字符;
    • 字符指针变量存放的是地址
  • 2.赋值方式不同:
    • 可以对字符指针变量赋值
    • 不可以对数组名赋值
  • 3.初始化的不同:

字符数组:

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

字符指针变量:

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

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

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

  • 5.
    • 指针变量的值可以改变
    • 字符数组名代表一个固定的值,不能改变
  • 6.
    • 字符数组的值是可以改变的(可以再进行赋值)
    • 字符指针变量指向的字符串常量中的内容是不可以被取代在(不能再进行赋值)
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;
}



abcdefg
abcdefg
house
a0cdefg
  • 7.引用数组:
    • 字符数组:
      • 下标法(数组名 or 下标)
      • 地址法
    • 指针变量指向数组:
      • char *a = "china";
      • 可以用a[5]获取相应的值
  • 8.
    • 用指针变量指向一个格式字符串,可以用其代替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);
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]);
}
//输出
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);
    
This post is licensed under CC BY 4.0 by the author.