C语言入门系列之8.指针的概念与应用

CuterCorley 等级 358 0 0

一、指针引入

指针是C语言中的一个重要的概念,也是C语言的一个重要特色。 正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地使用数组等。 掌握指针的应用,可以使程序简洁、紧凑、高效。可以说,不掌握指针就是没有掌握C的精华。

1.地址的概念

数据在内存中的存储和读取如下: C语言入门系列之8.指针的概念与应用 内存区的每一个字节有一个编号,称为地址。 如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。

在C语言中,对变量的访问有两种方式:

  • 直接访问 如a=5;。 系统在编译时,已经对变量分配了地址,例如,若变量a分配的地址是2000,则该语句的作用就是把常数5保存到地址为2000的单元。
  • 间接访问 如scanf("%d",&a);。 调用函数时,把变量a的地址传递给函数scanf,函数首先把该地址保存到一个单元中,然后把从键盘接收的数据通过所存储的地址保存到a变量中。

2.初识指针

在C语言中,指针是一种特殊的变量,它是存放地址的。 假设我们定义了一个指针变量int *i_pointer,通过语句i_pointer = &i;来存放整型变量i的地址,如下: C语言入门系列之8.指针的概念与应用 将i的地址(2000)存放到i_pointer中,这时,i_pointer的值就是(2000) ,即变量i所占用单元的起始地址。 要存取变量i的值,可以采用间接方式: 先找到存放i的地址的变量i_pointer,从中取出i的地址(2000),然后取出i的值3,如下: C语言入门系列之8.指针的概念与应用

3.两个操作符

*:是取值操作符; &:是取址操作符。

如:

int i = 2000;
int *pointer;
pointer = &i;
printf("%d\n", *pointer);

二、指针与指针变量

知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量的地址称为该变量的指针。 C语言中可以定义一类特殊的变量,这些变量专门用来存放变量的地址,称为指针变量。 指针变量的值(即指针变量中存放的值)是地址(即指针)。

1.定义指针变量*

定义指针变量的一般形式为:

类型说明符  *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。 例如float *pointer_1;中,指针变量名是pointer_1,而不是*pointer_1。

下面都是合法的定义:

float  *pointer_3;     // pointer_3是指向float型变量的指针变量
char *pointer_4;      // pointer_4是指向字符型变量的指针变量

可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量,如下: C语言入门系列之8.指针的概念与应用 在定义指针变量时必须指定基类型。 需要特别注意,只有整型变量的地址才能放到指向整型变量的指针变量中,如下∶

float  a;
int  * pointer_1; 
pointer_1 = &a;          

将float型变量的地址放到指向整型变量的指针变量中,是错误的。

2.指针变量的引用&

指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量,否则编译器也会把该值当成一个地址来处理。 C语言中提供了地址运算符&来表示变量的地址,其一般形式为:

&变量名;

&a表示变量a的地址,&b表示变量b的地址。 当然,变量本身必须预先声明。

通过指针变量访问整型变量练习如下:

#include <stdio.h>

int main(){
    int a, b;
    int *pointer_1, *pointer_2;
    a = 100;
    b = 10;
    pointer_1 = &a;
    pointer_2 = &b;

    printf("%d, %d\n", a, b);
    printf("%d, %d\n", *pointer_1, *pointer_2);

    return 0;
} 

打印:

100, 10
100, 10

运行原理如下: C语言入门系列之8.指针的概念与应用

3.对&和*运算符的说明

如果已执行了语句pointer_1 = &a;: (1)&*pointer_1的含义是: &和*两个运算符的优先级别相同,但按自右而左方向结合,因此先进行*pointer_1的运算,它就是变量a,再执行&运算; 因此,&*pointer_1与&a相同,即变量a的地址。 如果有pointer_2 = &*pointer_1;,它的作用是将&*(a的地址)赋给pointer_2 ,如果pointer_2原来指向b,经过重新赋值后它已不再指向b了,而指向了a,如下: C语言入门系列之8.指针的概念与应用 (2) *&a的含义是: 先进行运算&a,得a的地址,再进行*运算,即&a所指向的变量,也就是变量a; *&a和*pointer_1的作用是一样的,它们都等价于变量a,即*&a与a等价。

(3)(*pointer_1)++相当于a++。 其中,括号是必要的,如果没有括号,就成为了*pointer_1++,而++和*为同一优先级别,而结合方向为自右而左,因此它相当于*(pointer_1++)。 由于++在pointer_1的右侧,是后加,因此先对pointer_1的原值进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

练习: 输入a和b两个整数,按先大后小的顺序输出a和b。 代码如下:

#include <stdio.h>

int main(){
    int *p1, *p2, *p, a, b;
    scanf("%d %d", &a, &b);
    p1 = &a;
    p2 = &b;

    if(a < b){
        p = p1;
        p1 = p2;
        p2 = p;
    }

    printf("a = %d, b = %d\n", a, b);
    printf("max = %d, min = %d\n", *p1, *p2);

    return 0;
} 

打印:

5 9
a = 5, b = 9
max = 9, min = 5

执行原理如下: C语言入门系列之8.指针的概念与应用

指针变量作为函数参数练习: 对输入的两个整数按大小顺序输出,需要用函数实现交换功能。 代码如下:

#include <stdio.h>

int main(){
    void swap(int *p1, int *p2);
    int *pointer_1, *pointer_2, a, b;
    scanf("%d %d", &a, &b);
    pointer_1 = &a;
    pointer_2 = &b;

    if(a < b){
        swap(pointer_1, pointer_2);
    }

    printf("%d > %d\n", a, b);

    return 0;
}

void swap(int *p1, int *p2){
    int temp;
    printf("Swaping......\nPlease wait^_^\n");
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

打印:

5 9
Swaping......
Please wait^_^
9 > 5

执行过程如下: C语言入门系列之8.指针的概念与应用

练习: 输入a、b、c3个整数,按大小顺序输出。 代码如下:

#include <stdio.h>

int main(){
    void exchange(int *p1, int *p2, int *p3);
    int *p1, *p2, *p3, a, b, c;
    scanf("%d %d %d", &a, &b, &c);
    p1 = &a;
    p2 = &b;
    p3 = &c;

    exchange(p1, p2, p3);

    printf("%d > %d > %d\n", a, b, c);

    return 0;
}

void exchange(int *p1, int *p2, int *p3){
    void swap(int *q1, int *q2);
    if(*p1 < *p2){
        swap(p1, p2);
    }
    if(*p1 < *p3){
        swap(p1, p3);
    }
    if(*p2 < *p3){
        swap(p2, p3);
    }
}

void swap(int *q1, int *q2){
    int temp;
    printf("Swaping......\nPlease wait^_^\n");
    temp = *q1;
    *q1 = *q2;
    *q2 = temp;
}

打印:

20 12 25
Swaping......
Please wait^_^
Swaping......
Please wait^_^
25 > 20 > 12

三、数组与指针

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。 指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。 所谓数组元素的指针就是数组元素的地址。

1.指向数组元素的指针

定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。 例如,int a[10];定义a为包含10个整型数据的数组; int *p定义p为指向整型变量的指针变量。 应当注意,如果数组为int型,则指针变量的基类型亦应为int型。 对该指针变量赋值:

p = &a[0];

把a[0]元素的地址赋给指针变量p,也就是使p指向a数组的第0个元素,如下图: C语言入门系列之8.指针的概念与应用

2.通过指针引用数组元素

引用一个数组元素,有2种方式:

  • 下标法 如a[i]
  • 指针法 如*(a+i)*(p+i)

其中的a是数组名,p是指向数组元素的指针变量,其初值p=a。 数组名即翻译成数组的第一个元素的地址。

练习: 输出数组中的全部元素: 假设有一个a数组,整型,有10个元素,要输出各元素的值有三种方法: (1)下标法。 (2)通过数组名计算数组元素地址,找出元素的值。 (3)用指针变量指向数组元素。

方式一代码如下:

#include <stdio.h>

int main(){
    int a[10], i;
    for(i = 0; i < 10; i++){
        scanf("%d", &a[i]);
    }
    printf("\n");

    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }

    return 0;
}

打印:

1 2 3 4 5 6 7 8 9 10

1 2 3 4 5 6 7 8 9 10

方式二代码如下:

#include <stdio.h>

int main(){
    int a[10], i;
    for(i = 0; i < 10; i++){
        scanf("%d", &a[i]);
    }
    printf("\n");

    for(i = 0; i < 10; i++){
        printf("%d ", *(a + i));
    }

    return 0;
}

运行效果与方式一相同。

方式三代码如下:

#include <stdio.h>

int main(){
    int a[10], i, *p;
    for(i = 0; i < 10; i++){
        scanf("%d", &a[i]);
    }
    printf("\n");

    for(p = a; p < (a+10); p++){
        printf("%d ", *p);
    }

    return 0;
}

运行效果与之前相同。

3.用数组名作函数参数

形式为:

f(int arr[], int n)

在编译时是将arr按指针变量处理的,相当于将函数f的首部写成f(int *arr, int n),这两种写法是等价的。

C语言调用函数时虚实结合的方法都是采用值传递方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。

练习: 将数组a中n个整数按相反顺序存放,如下: C语言入门系列之8.指针的概念与应用 常规方式代码如下:

#include <stdio.h>

int main(){
    void reverse(int x[], int n);
    int i, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
    printf("The original array:\n");
    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }
    printf("\n");
    reverse(a, 10);
    printf("The reversed array:\n");
    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

void reverse(int x[], int n){
    int temp, i, j, m;
    m = (n - 1) / 2;
    for(i = 0; i <= m; i++){
        j = n - i - 1;
        temp = x[i];
        x[i] = x[j];
        x[j] = temp;
    }
}

打印:

The original array:
3 7 9 11 0 6 7 5 4 2
The reversed array:
2 4 5 7 6 0 11 9 7 3

指针方式代码如下:

#include <stdio.h>

int main(){
    void reverse(int *a, int n);
    int i, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
    printf("The original array:\n");
    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }
    printf("\n");
    reverse(a, 10);
    printf("The reversed array:\n");
    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

void reverse(int *x, int n){
    int temp, *i, *j, *p, m;
    m = (n - 1) / 2;
    i = x;
    j = x + n - 1;
    p = x + m;
    for( ; i <= p; i++, j--){
        temp = *i;
        *i = *j;
        *j = temp;
    }
}

与第一种方式运行效果相同。

练习: 从10个数中找出其中最大值和最小值。 常规方式代码如下:

#include <stdio.h>

int max,min;

int main(){
    void find_max_min(int a[], int n);
    int i, a[10];
    printf("Input 10 numbers:\n");
    for(i = 0; i < 10; i++){
        scanf("%d", &a[i]);
    }
    find_max_min(a, 10);
    printf("max = %d, min = %d\n", max, min);

    return 0;
}

void find_max_min(int a[], int n){
    max = min = a[0];
    int i;
    for(i = 1;i < n;i++){
        if(a[i] > max){
            max = a[i];
        }
        else if(a[i] < min){
            min = a[i];
        }
    }
}

打印:

Input 10 numbers:
3 7 9 11 0 6 7 5 4 2
max = 11, min = 0

指针方式:

#include <stdio.h>

int max,min;

int main(){
    void find_max_min(int *a, int n);
    int i, a[10];
    printf("Input 10 numbers:\n");
    for(i = 0; i < 10; i++){
        scanf("%d", &a[i]);
    }
    find_max_min(a, 10);
    printf("max = %d, min = %d\n", max, min);

    return 0;
}

void find_max_min(int *a, int n){
    max = min = *a;
    int i;
    for(i = 1;i < n;i++){
        if(*(a + i) > max){
            max = *(a + i);
        }
        else if(*(a + i) < min){
            min = *(a + i);
        }
    }
}

数组名作函数参数归纳: 如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况: (1)形参和实参都用数组名,如:

void main(){
    int a[10];
    f(a, 10);
}

void f(int x[], int n){
    ...
}

(2)实参用数组名,形参用指针变量,如:

void main(){
    int a[10];
    f(a, 10);
}

f(int *a, int n){
    ...
}

(3)实参形参都用指针变量。例如:

void main(){
    int a[10], *p = a;
    f(p, 10);
}

void f(int *x, int n){
    ...
}

(4)实参为指针变量,形参为数组名,如:

void main(){
    int a[10], *p = a;
    f(p, 10);
}

void f(int x[], int n){
    ...
}

练习: 对数组中10个整数按由大到小顺序排序。 代码如下:

#include <stdio.h>

int main(){
    void sort(int x[], int n);
    int i, *p, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
    printf("The original array:\n");
    for(i = 0; i < 10; i++){
        printf("%d ", a[i]);
    }
    p = a;
    sort(p, 10);
    printf("\nThe sorted array:\n");
    for(i = 0; i < 10; i++){
        printf("%4d", *p);
        p++;
    }

    return 0;
}

void sort(int x[], int n){
    int i, j, k, t;
    for(i = 0;i < n - 1;i++){
        k = i;
        for(j = i + 1;j < n;j++){
            if(x[j] > x[k]){
                t = x[j];
                x[j] = x[k];
                x[k] = t;
            }
        }
    }
}

打印:

The original array:
3 7 9 11 0 6 7 5 4 2
The sorted array:
  11   9   7   7   6   5   4   3   2   0

4.多维数组与指针

基本概念

用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素; 但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。

可以认为二维数组是数组的数组,如定义int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};,二维数组a是由3个一维数组所组成的。 设二维数组的首行的首地址为2000,则有: C语言入门系列之8.指针的概念与应用

在二维数组中,常见表达式及其含义如下: C语言入门系列之8.指针的概念与应用 练习: 输出二维数组有关的值。 代码如下:

#include <stdio.h>

int main(){
    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

    printf("a: %d\n", a);

    printf("*a: %d\n", *a);

    printf("a[0]: %d\n", a[0]);

    printf("&a[0]: %d\n", &a[0]);

    printf("&a[0][0]: %d\n", &a[0][0]);

    printf("a+1: %d\n", a+1);

    printf("*(a+1): %d\n", *(a+1));

    printf("a[1]: %d\n", a[1]);

    printf("&a[1]: %d\n", &a[1]);

    printf("&a[1][0]: %d\n", &a[1][0]);

    printf("a+2: %d\n", a+2);

    printf("*(a+2): %d\n", *(a+2));

    printf("a[2]: %d\n", a[2]);

    printf("&a[2]: %d\n", &a[2]);

    printf("&a[2][0]: %d\n", &a[2][0]);

    printf("a[1]+1: %d\n", a[1]+1);

    printf("*(a+1)+1: %d\n", *(a+1)+1);

    printf("*(a[1]+1): %d\n", *(a[1]+1));

    printf("*(*(a+1)+1): %d\n", *(*(a+1)+1));

    return 0;
}

打印:

a: 6487536
*a: 6487536
a[0]: 6487536
&a[0]: 6487536
&a[0][0]: 6487536
a+1: 6487552
*(a+1): 6487552
a[1]: 6487552
&a[1]: 6487552
&a[1][0]: 6487552
a+2: 6487568
*(a+2): 6487568
a[2]: 6487568
&a[2]: 6487568
&a[2][0]: 6487568
a[1]+1: 6487556
*(a+1)+1: 6487556
*(a[1]+1): 6
*(*(a+1)+1): 6

指向多维数组元素的指针变量

把二维数组a分解为一维数组a[0]、a[1]、a[2]之后,设p为指向二维数组的指针变量,可定义为:

int (*p)[4]

它表示p是一个指针变量,它指向包含4个元素的一维数组。 p指向第一个一维数组a[0],其值等于a、a[0]、&a[0][0]等; p+i则指向一维数组a[i]。

*(p+i)+j是二维数组i行j列的元素的地址,而*(*(p+i)+j)则是i行j列元素的值。 二维数组指针变量说明的一般形式为:

类型说明符  (*指针变量名)[长度]

其中类型说明符为所指数组的数据类型,*表示其后的变量是指针类型,长度表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。

练习: 用指针变量输出二维数组元素的值。 代码如下:

#include <stdio.h>

int main(){
    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int (*p)[4];
    int i, j;
    p = a;
    for(i = 0;i < 3;i++){
        for(j = 0;j < 4;j++){
            printf("%4d", *(*(p + i) + j));
        }
        printf("\n");
    }

    return 0;
}

打印:

   1   2   3   4
   5   6   7   8
   9  10  11  12

练习: 通过输入指定行数和列数打印出二维数组对应任一行任一列元素的值。 代码如下:

#include <stdio.h>

int main(){
    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int (*p)[4], i, j;
    p = a;
    printf("i = ");
    scanf("%d", &i);
    while(i < 0 || i >= 3){
        printf("i = ");
        scanf("%d", &i);
    }
    printf("j = ");
    scanf("%d", &j);
    while(j < 0 || j >= 4){
        printf("j = ");
        scanf("%d", &j);
    }
    printf("a[%d, %d] = %d\n", i, j, *(*(p+i)+j));

    return 0;
}

打印:

i = 3
i = 2
j = -1
j = 2
a[2, 2] = 11

四、字符串与指针

1.字符串定义

字符数组形式: 用字符数组存放一个字符串。

练习: 定义一个字符数组,对它初始化,然后输出该字符串。 代码如下:

#include <stdio.h>

int main(){
    char string[] = "I love China!";
    printf("%s", string);

    return 0;
}

打印:

I love China!

存放原理如下: C语言入门系列之8.指针的概念与应用

指针形式: 用字符指针指向一个字符串。

练习: 定义一个字符指针,用字符指针指向字符串中的字符。 代码如下:

#include <stdio.h>

int main(){
    char *string = "I love China!";
    printf("%s", string);

    return 0;
}

打印:

I love China!

2.字符串中字符的存取方法

对字符串中字符的存取,可以用下标方法,也可以用指针方法。

练习: 将字符串a复制为字符串b。 下标法:

#include <stdio.h>

int main(){
    char a[] = "I love China!",b[40];
    int i;
    for(i = 0;*(a+i) != '\0';i++){
        *(b+i) = *(a+i);
    }
    *(b+i) = '\0';
    printf("String a is: \n%s\n", a);
    printf("String b is: \n");
    for(i=0;b[i]!='\0';i++){
        printf("%c", b[i]);
    }

    return 0;
}

打印:

String a is:
I love China!
String b is:
I love China!

指针方法:

#include <stdio.h>

int main(){
    char a[] = "I love China!",b[40], *p1, *p2;
    p1 = a;
    p2 = b;
    for( ;*p1 != '\0';p1++, p2++){
        *p2 = *p1;
    }
    *p2 = '\0';
    printf("String a is: \n%s\n", a);
    printf("String b is: \n");
    int i;
    for(i=0;b[i]!='\0';i++){
        printf("%c", b[i]);
    }

    return 0;
}

执行效果与下标法相同。

3.字符指针作函数参数

练习: 用函数调用实现字符串的复制。 用字符数组做参数代码如下:

#include <stdio.h>

int main(){
    void copy_string(char from[], char to[]); 
    char a[] = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char from[], char to[]){
    int i = 0;
    while(from[i] != '\0'){
        to[i] = from[i];
        i++;
    }
    to[i] = '\0';
}

打印:

String a = I am a teacher.
String B = You are a student.
Copy string a to string b:
String a = I am a teacher.
String B = I am a teacher.

形参用字符指针变量代码如下:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    for( ; *from != '\0'; from++, to++){
        *to = *from;
    }
    *to = '\0';
}

运行效果与之前相同。 注意: 在定义和初始化b数组时不能通过指针方式,因为指针方式定义的数组为常量,存储在常量区,不可改变,因此调用函数改变b数组时会出现异常。

代码还可以进行简化或改写: 简化1:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    while((*to = *from) != '\0'){
        to++;
        from++;
    }
}

简化2:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    while((*to++ = *from++) != '\0'){
        ;
    }
}

简化3:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    while(*from != '\0'){
        *to++ = *from++;
    }
    *to = '\0';
}

简化4:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    while(*to++ = *from++){
        ;
    }
}

简化5:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    for( ; *to++ = *from++; ){
        ;
    }
}

简化6:

#include <stdio.h>

int main(){
    void copy_string(char *from, char *to); 
    char *a = "I am a teacher.", b[] = "You are a student.";
    printf("String a = %s\nString B = %s\n", a, b);
    printf("Copy string a to string b:\n");
    copy_string(a, b);
    printf("String a = %s\nString B = %s\n", a, b);

    return 0;
}

void copy_string(char *from, char *to){
    char *p1, *p2;
    p1 = from;
    p2 = to;
    while((*p2++ = *p1++) != '\0'){
        ;
    }
}

这个方式变得稍复杂了点,重在说明方法。

4.字符指针变量和字符数组的比较

虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混为一谈。 主要概括起来有以下几点: (1)字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第1个字符的地址),而不是将字符串放到字符指针变量中。 (2)赋值方式: 对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。

char  str[20];
str = "I love China!";

而对字符指针变量,可以采用下面方法赋值:

char *a;
a = "I love China!";

但注意赋给a的不是字符,而是字符串第一个元素的地址。 (3)初始化 对字符指针变量赋初值char *a = "I love China!";等价于

char *a;
a = "I love China!";

而对数组的初始化char str[20] = {"I love China!"};不能等价于

char str[20];
str[] = "I love China!";

(4)如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址;而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个字符变量的地址。也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据。 例如:

char str[10];
scanf("%s", str);

是可以的,下面的方式:

char *a;
scanf("%s", a);

虽然一般也能运行,但这种方法是危险的。 (5)指针变量的值是可以改变的。

改变指针变量的值测试:

#include <stdio.h>

int main(){
    char *a = "I am Corley!!";
    printf("%s\n", a);
    a += 7;
    printf("%s\n", a);

    return 0;
}

打印:

I am Corley!!
rley!!

若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符。

下标形式引用指针变量测试:

#include <stdio.h>

int main(){
    char *a = "I am Corley!!";
    int i;
    printf("The sixth character is %c\n", a[5]);
    for(i = 0; a[i] != '\0'; i++){
        printf("%c", a[i]);
    }
    printf("\n");

    return 0;
}

打印:

The sixth character is C
I am Corley!!

五、指向函数的指针

1.用函数指针变量调用函数

可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配给一个入口地址,这个函数的入口地址就称为函数的指针。

练习: 常规方式代码如下:

#include <stdio.h>

#if(1)
int main(){
    int max(int, int);
    int a, b, c;

    scanf("%d %d", &a, &b);
    c = max(a, b);
    printf("a = %d, b = %d, max = %d\n", a, b, c);

    return 0;
}
#endif

int max(int a, int b){
    return a > b ? a : b;
}

打印:

12 20
a = 12, b = 20, max = 20

指针方式:

#include <stdio.h>

#if(1)
int main(){
    int max(int, int);
    int a, b, c;
    int (*p)();
    p = max;
    scanf("%d %d", &a, &b);
    c = (*p)(a, b);
    printf("a = %d, b = %d, max = %d\n", a, b, c);

    return 0;
}
#endif

int max(int a, int b){
    return a > b ? a : b;
}

执行效果与常规方式相同。

2.用指向函数的指针作函数参数

函数指针变量常见的用途之一是把指针作为参数传递到其他函数。 函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等; 指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。

//实参函数名     f1             f2
//             ↓               ↓
void  sub(int (*x1)(int), int (*x2)(int,int)){
    int a, b, i, j;
    a = (*x1)(i);
    b = (*x2)(i, j);
    ...
}

其大致原理如下: 有一个函数(假设函数名为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参为两个函数名f1和f2,给形参传递的是函数f1和f2的地址,这样在函数sub中就可以调用f1和f2函数了。

练习: 设一个函数process,在调用它的时候,每次实现不同的功能(有点类似多态)。 输入a和b两个数,第一次调用process时找出a和b中大者,第二次找出其中小者,第三次求a与b之和。 代码如下:

#include <stdio.h>

int main(){
    int max(int, int);
    int min(int, int);
    int sum(int, int);
    void process(int, int, int(*fun)());
    int a, b;
    printf("Input a and b:\n");
    scanf("%d %d", &a, &b);
    printf("Max = ");
    process(a, b, max);

    printf("Min = ");
    process(a, b, min);

    printf("Sum = ");
    process(a, b, sum);

    return 0;
}

int max(int a, int b){
    return a > b ? a : b;
}

int min(int a, int b){
    return a < b ? a : b;
}

int sum(int a, int b){
    return a + b;
}

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

打印:

Input a and b:
12 20
Max = 20
Min = 12
Sum = 32

3.返回指针值的函数

一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。 这种带回指针值的函数一般定义形式为:

类型名  *函数名(参数表列);

例如int *a(int x, int y);

练习: 有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩,用指针函数来实现。 代码如下:

#include <stdio.h>

int main(){
    double scores[][4] = {{60.0, 70.0, 80.5, 90.5}, {56.0, 89.0, 67.0, 88.0}, {34.2, 78.5, 90.5, 66.0}};
    double *search(double(*pointer)[4], int n);
    double *p;
    int i, m;
    printf("Please input the number of student:");
    scanf("%d", &m);
    printf("The scores of No.%d are:\n", m);
    p = search(scores, m);
    for(i = 0; i < 4; i++){
        printf("%8.2f", *(p+i));
    }

    return 0;
}

double *search(double(*pointer)[4], int n){
    return *(pointer + n);
}

打印:

Please input the number of student:2
The scores of No.2 are:
   34.20   78.50   90.50   66.00

改进: 对于前面的练习,找出其中有不及格课程的学生及其学生号。 代码如下:

#include <stdio.h>

int main(){
    double scores[][4] = {{60.0, 70.0, 80.5, 90.5}, {56.0, 89.0, 67.0, 88.0}, {34.2, 78.5, 90.5, 66.0}};
    void *search(double(*pointer)[4], int n);
    search(scores, 3);

    return 0;
}

void *search(double(*pointer)[4], int n){
    int i, j;
    for(i = 0; i < n;i++){
        double *p = *(pointer + i);
        for(j = 0; j < 4; j++){
            if(*(p+j) < 60){
                printf("No.%d has score below 60.\n", i);
                break;
            } 
        }
    }
}

打印:

No.1 has score below 60.
No.2 has score below 60.

指针函数和函数指针的区别

这两个概念都是简称: 指针函数是指带指针的函数,即本质是一个函数; 函数指针是指向函数的指针变量,因而函数指针本身首先应是指针变量,只不过该指针变量指向函数。

4.指针数组和指向指针的指针

指针数组的概念: 一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。 一维指针数组的定义形式为

类型名 *数组名[数组长度];

例如int *name[4];

练习:

#include <stdio.h>

int main(){
    int a[5] = {1, 2, 3, 4, 5};
    int *p[5] = {&a[0], &a[1], &a[2], &a[3], &a[4]};
    int i;
    for(i = 0; i < 5; i++){
        printf("%5d", *p[i]);
    }
    printf("\n");

    return 0;
}

打印:

    1    2    3    4    5

练习: 将下边字符串按字母顺序(由小到大)输出:

{"baidu.com", "www.baidu.com", "pan.baidu.com", "baidu.com/profile"}

实现思路: 声明一个指针数组来指向字符串数组; 排序利用strcmp()函数来解决; 各个功能抽象为函数或文件。

代码如下:

#include <stdio.h>
#include <string.h>

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

    char *name[] = {"baidu.com", "www.baidu.com", "pan.baidu.com", "baidu.com/profile"};
    int n = 4;
    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++){
        k = i;
        for(j = i + 1; j < n; j++){
            if(strcmp(name[k], name[j]) > 0){
                k = j;
            }
            if(k != i){
                temp = name[i];
                name[i] = name[k];
                name[k] = temp;
            }
        }
    }
}

void print(char *name[], int n){
    int i;
    for(i = 0; i < n; i++){
        printf("%s\n", name[i]);
    }
}

打印:

baidu.com
baidu.com/profile
pan.baidu.com
www.baidu.com

定义一个指向指针数据的指针变量,形式如下:

类型名 **指针变量名;

例如char **p;: p的前面有两个*号,*运算符的结合性是从右到左,因此**p相当于*(*p),显然*p是指针变量的定义形式。 如果没有最前面的,那就是定义了一个指向字符数据的指针变量; 现在它前面又有一个\号,表示指针变量p是指向一个字符指针变量的,*p就是p所指向的这个指针变量。

练习:

#include <stdio.h>
#include <string.h>

int main(){
    char *name[] = {"baidu.com", "www.baidu.com", "pan.baidu.com", "baidu.com/profile"};
    char **p;
    int i;
    for(i = 0; i < 4; i++){
        p = name + i;
        printf("%s\n", *p);
    }

    return 0;
}

打印:

baidu.com
www.baidu.com
pan.baidu.com
baidu.com/profile

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

指针数组的一个重要应用是作为main函数的形参,在之前的程序中,main函数的第一行一般写成以下形式:

void  main(){
    ...
}

括号中是空的,实际上,main函数可以有参数,如void main(int argc, char *argv[]),argc和argv就是main函数的形参。 main函数是由操作系统调用的,其形参的值不是在程序中得到,实际上实参是和命令一起给出的,也就是在命令行中包括命令名和需要传给main函数的参数。 命令行的一般形式为:

命令名 参数1 参数2 …… 参数n

练习:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]){
    int i;
    printf("The number of string is: %d\n", argc - 1);
    for(i = 1;i < argc; i++){
        printf("The string %d is: %s\n", i, argv[i]);
    }

    return 0;
}

编译生成.exe可执行文件(这里文件名为pointer.exe),在命令行当前路径下执行:

pointer C Java Python

打印:

The number of string is: 3
The string 1 is: C
The string 2 is: Java
The string 3 is: Python

六、指针小结

1.数据类型小结

C语言入门系列之8.指针的概念与应用

2.指针运算小结

(1)指针变量加(减)一个整数 例如p++、p--、p+i、p-i、p+=i、p-=i等。

(2)指针变量赋值: 将一个变量地址赋给一个指针变量,如:

p = &a;                // 将变量a的地址赋给p
p = array;            // 将数组array首元素地址赋给p
p = &array[i];        // 将数组array第i个元素的地址赋给p
p = max;            // max为已定义的函数,将max的入口地址赋给p
p1 = p2;             // p1和p2都是指针变量,将p2的值赋给p1

(3)指针变量可以有空值,即该指针变量不指向任何变量,可以表示为p = null;

(4)两个指针变量可以相减: 如果两个指针变量都指向同一个数组中的元素,则两个指针变量值之差是两个指针之间的元素个数 ,如下: C语言入门系列之8.指针的概念与应用 图中p2-p1的值为3。

(5)两个指针变量比较 若两个指针指向同一个数组的元素,则可以进行比较,指向前面的元素的指针变量小于指向后面元素的指针变量。

3.void类型和const修饰指针

void真正发挥的作用在于:

  • 对函数返回的限定;
  • 对函数参数的限定。

例如void abc(void);

ANSI C新标准增加了一种void指针类型,即不指定它是指向哪一种类型数据的指针变量。 例如,void *p;表示指针变量p不指向一个确定的类型数据,它的作用仅仅是用来存放一个地址。

void指针可以指向任何类型数据,也就是说,可以用任何类型的指针直接给void指针赋值; 但是,如果需要将void指针的值赋给其他类型的指针,则需要进行强制类型转换。

const指针: 当用const修饰指针时,根据const位置的不同有三种效果。 原则是:修饰谁,谁的内容就不可变,其他的都可变。

在定义const char *str = "Welcome to China!!\n";时,通过str = "Welcome to Beijing!!\n";改变字符串是合法的,而通过str[0] = 'w';改变字符串是不合法的; 在定义char * const str = "Welcome to China!!\n";时,通过str[0] = 'w';改变字符串是合法的,通过str = "Welcome to Beijing!!\n";改变字符串是不合法的; 在定义const char * const str = "Welcome to China!!\n";时,通过str[0] = 'w';str = "Welcome to Beijing!!\n";改变字符串是都是不合法的。

扩展-memcpy

memcpy是memory copy的缩写,意为内存复制,在写C语言程序的时候,常常会用到它。 函数原型如下:

void *memcpy(void *dest, const void *src, size_t n);

功能是从src的开始位置拷贝n个字节的数据到dest,如果dest存在数据,将会被覆盖。 memcpy函数的返回值是dest的指针。 memcpy函数定义在string.h头文件里。

练习:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char string1[60] = "The quick brown dog jumps over the lazy fox";
char string2[60] = "The quick brown fox jumps over the lazy dog";

void main()
{
   printf( "Function:\tmemcpy without overlap\n" );
   printf( "Source:\t\t%s\n", string1 + 40 );
   printf( "Destination:\t%s\n", string1 + 16 );
   memcpy( string1 + 16, string1 + 40, 3 );
   printf( "Result:\t\t%s\n", string1 );
   printf( "Length:\t\t%d characters\n\n", strlen(string1));

   memcpy( string1 + 16, string2 + 40, 3 );

   printf( "Function:\tmemmove with overlap\n" );
   printf( "Source:\t\t%s\n", string2 + 4 );
   printf( "Destination:\t%s\n", string2 + 10 );
   memmove( string2 + 10, string2 + 4, 40 );
   printf( "Result:\t\t%s\n", string2 );
   printf( "Length:\t\t%d characters\n\n", strlen(string2));

   printf( "Function:\tmemcpy with overlap\n" );
   printf( "Source:\t\t%s\n", string1 + 4 );
   printf( "Destination:\t%s\n", string1 + 10 );
   memcpy( string1 + 10, string1 + 4, 40 );
   printf( "Result:\t\t%s\n", string1 );
   printf( "Length:\t\t%d characters\n\n", strlen(string1));
}

打印:

Function:       memcpy without overlap
Source:         fox
Destination:    dog jumps over the lazy fox
Result:         The quick brown fox jumps over the lazy fox
Length:         43 characters

Function:       memmove with overlap
Source:         quick brown fox jumps over the lazy dog
Destination:    brown fox jumps over the lazy dog
Result:         The quick quick brown fox jumps over the lazy dog
Length:         49 characters

Function:       memcpy with overlap
Source:         quick brown dog jumps over the lazy fox
Destination:    brown dog jumps over the lazy fox
Result:         The quick quick brown dog jumps over the lazy fox
Length:         49 characters

本文原文首发来自博客专栏C语言学习,由本人转发至https://www.helloworld.net/p/g3vca3hzOF2P,其他平台均属侵权,可点击https://blog.csdn.net/CUFEECR/article/details/105984493查看原文,也可点击https://blog.csdn.net/CUFEECR浏览更多优质原创内容。

收藏
评论区

相关推荐

C++概述
概述 C 是静态,可编译,通用,大小写敏感,格式自由的编程语言,它支持程序化,面向对象的,和泛型编程方式。 C 被看作是中间层语言,因为它同时包含了低级语言和高级语言的特性。 C 是于 1979 年在新泽西的茉莉山丘的贝尔实验室由 Bjarne Stroustrup 开发的,它是 C 语言的加强版,最开始它被称作 “C with Classes”,但是
C++ 基本语法
C 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 对象具有状态和行为。例如:一只狗的状态 颜色、名称、品种,行为 摇动、叫唤、吃。对象是类的实例。 类 类可以定义为描述对象行为/状态的模板/蓝图。 方法 从基本上说,一个方法表示一种行为。一个类可以包含多个
【C 陷阱与缺陷 学习笔记】(一)词法陷阱
一 内容 0\. 不同于 当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。 1.本意是检查 x 与 y 是否相等: c if(x y) break; 实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。 2.本意是跳过文件中的空白字符: c while(c '' || c '\t' ||
Swift与Objective-C混合编程之Swift与Objective-C API映射
原创文章,欢迎转载。转载请注明:关东升的博客 Swift与ObjectiveC API映射 在混合编程过程中Swift与ObjectiveC调用是双向的,由于不同语言对于相同API的表述是不同的,他们之间是有某种映射规律的,这种API映射规律主要体现在构造函数和方法两个方面。 1、构造函数映射 在Swift与ObjectiveC语言进行混合
统计字符串中字符出现的次数(Python版)
字符串转list python s 'aabbccd' list1 list(s) 方法一: python list1 'a', 'a', 'b', 'c', 'c', 'c', 'c' dict_cnt {} for value in list1: dict_cntvalue dict_cnt.get(value,
c++11 实现单例模式
C11出来后,里面新增加了好多好用的功能 下面的单例就是使用了C11中的标准库中的mutex和unique_prt 进行内存管理的. 此单例模式不用担心内存的释放问题 pragma once include <memory include <mutex template <class T class Singleton { public: ty
C语言_练习题(一)
前言: 看懂理解代码很容易,难的是把所理解的融会贯通,融合到实例中,你会发现事实和理论会有些许差别,编写实例能更好的帮你积累经验。 0x1 编写一个程序,要求提示输入一个ASCII码值(如,66),然后打印输入的字符。 代码: include <stdio.h int main(){ char i; printf("请输入一个ASCI
我的C语言基础
C语言32个关键字auto 声明自动变量short 声明短整型变量或函数int 声明整型变量或函数long 声明长整型变量或函数float 声明浮点型变量或函数double 声明双精度变量或函数char 声明字符型变量或函数struct 声明结构体变量或函数union 声明共用数据类型enum 声明枚举类型typedef 用以给数据类型取别名co
C语言基础习题50例(一)1-5
虎为百兽尊,罔敢触其怒。惟有父子情,一步一回顾。 习题1 有 1 、 2 、 3 、 4 个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?实现思路:显然,这个题目需要用到循环,并且是循环嵌套,先列出所有可能的组合,再去掉重复的组合即可。代码如下:cinclude <stdio.hint main(){ int i, j, k,
C语言基础习题50例(二)6-10
给大家推荐一门大数据Spark入门课程,希望大家喜欢。 习题6 用 号输出字母C的图案。实现思路:单行打印即可。代码如下:cinclude <stdio.h int main (void){ printf("\n"); printf("\n"); printf("\n"); printf("
C语言基础习题50例(三)11-15
你们看出神马了吗(\\^_\^\) 习题11 有一对兔子,从出生后第 3 个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少实现思路:从第1个月起,兔子对数分别为1、1、2、3、5、8、13、21...,显然是斐波拉契数列。代码如下:cinclude<stdio.hint mai
C语言基础习题50例(四)16-20
给大家介绍一堂Python入门课,感觉还不错,适合初学者入门。 习题16 输入两个正整数 m 和 n ,求其最大公约数和最小公倍数。实现思路:求两个数的最大公约数分别采用辗转相除法、辗转相减法、枚举法得到,最小公倍数用两个数之积除以最大公约数即可获得。方式一——辗转相除法:思路:(1)将两整数求余 a%b x;(2)如果x 0;则b为最大公
C语言基础习题50例(七)31-35
喜提头条号黄V,有兴趣的朋友可以关注一波,主写IT领域。 习题31 请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续判断第二个字母。实现思路:使用switch语句,如果第1个字母一样,则判断用情况语句或if语句判断第2个字母。也可以使用条件判断语句,实现相近。代码如下:cinclude<stdio.hint ma
C语言基础习题50例(十)46-50
知足常足,终身不辱。月圆缺,水满溢,事情到了极致一定会遭受祸患,只有懂得知足,才是富足。 习题46 宏define命令练习。实现思路:宏通过define命令定义,分为无参宏和带参宏,可以分别进行测试。这只是一种简单的字符串代换。代码如下:cinclude <stdio.hdefine TRUE 1define FALSE 0
游戏安全实践的一些思考
移动的游戏能够稳定健康的上线。主要需要依赖以下在四个方面:1.前端展示,或者说客户端正常运行。性能稳定不崩溃,不过热能够稳定运行。2.后端,或者游戏后台服务端的。不但要稳定。还有能在有限的服务器资源下,能承受大量的同时在线用户。而且要让游戏中的每个模块都能够承受承受大量的同时在线用户。3.安全也是重点之中。这既包括客户端,又包括服务端。客户端的安全,包括要防