C语言基础(16)-指针

一.指针的相关概念

1.1 指针变量

指针是一个变量,存放的是一个地址,该地址指向一块内存空间。

例:

int a = 10;

int *p = &a; // 定义一个指针变量p,&符号可以取得一个变量在内存当前中的地址。

*p = 5; // 修改指针所指的内存数据为5

1.2 无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型,可以通过强制转化将void *转化为其它类型指针,也可以用(void *)将其它类型指针强制转化为void *类型指针。

1.3 NULL

NULL在C语言中的定义为(void *)0,空指针就是指向了NULL的指针变量。

int *p = &a;
p = NULL; // p就是一个空指针

1.4 野指针

野指针就是没有指向任何有效地址的指针变量。在代码中应当尽量避免出现野指针,如果一个指针不能确定指向任何一个变量的地址,那么请将这个指针变成空指针。

二.使用指针时的一些注意点

2.1 指针的兼容性

指针之前赋值比普通数据类型赋值检查更为严格,如:不可以将一个double * 赋值给一个int。且指针变量只能存放地址,也不能将一个int类型变量直接赋值给一个指针。

2.2 常量指针与指针常量

记忆方法:* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变

int a =3;
int b = 1;
int c = 2;
int const *p1 = &b;//const 在前,定义为常量指针
int *const p2 = &c;//*在前,定义为指针常量 

常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。   

    p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
    p2= &a是错误的,而*p2 = a 是正确的。

2.3 指针所占的位数

指针所占的位数不会因类型不同而不同,如int *p与double *q 在同一操作系统下所占的位数始终是相同的,只跟操作系统的位数(x64/x86)相关。

2.4 指针与数组之间的关系

在C语言中数组的名字就是数组的地址,数组的地址也等价于数组第一个元素的地址

例:

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数


void main() {
    
    int a[3] = {3,4,5};
    int *p = a;
    int *q = &a[0];
    printf("变量p存放的地址为:%p\n变量q存放的地址为:%p\n",p,q);

    system("pause");
}

也可以通过指针来遍历当前数组元素:

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数


void main() {
    
    int a[3] = {3,4,5};
    int *p = a;
    //int *q = &a[0];
    //printf("变量p存放的地址为:%p\n变量q存放的地址为:%p\n",p,q);
    for (int i = 0; i < 3; i++) {
        printf("第%d个元素为:%d\n",i,p[i]);
    }
    system("pause");
}

三.指针的其它用法

3.1 指针的运算

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数


// 指针的运算
void point_test_1();

// 清除数组中的值(p的值发生改变)
void clearArrayValueByPoint();

// 清除数组中的值(p的值不发生改变)
void clearArrayValueByPoint2();

// 使用指针来进行冒泡排序
void sortByPoint();

// 任何一种数据类型都是char的数组
void point_test_2();
void point_test_3();

// 表示IP地址 192.168.111.123
void ipAddressByPoint();

void main() {
    
    ipAddressByPoint();
    system("pause");
}



// 指针的运算
void point_test_1() {

    int *p1;
    char *p2;
    int a1 = 0;
    char a2 = 0;
    p1 = &a1;
    p2 = &a2;
    printf("%p,%p\n", p1, p2);

    p1++; // 位移了4个字节,4个sizeof(int)
    p2++; // 位移了1个字节,1个sizeof(char)
    printf("%p,%p\n", p1, p2);

    p1 += 4; // 位移了4*sizeof(int) = 16
    p2 += 4; // 位移了4*sizeof(char) = 4
    printf("%p,%p\n", p1, p2);

    p1 -= 2; // 向前移动了2*sizeof(int)
    p2 -= 2; // 向前移动了2*sizeof(char)
    printf("%p,%p\n", p1, p2);

}


// 清除数组中的值(p的值发生改变)
void clearArrayValueByPoint() {

    int array[10] = {1,2,3,4,5,6,7,8,9,10};
    
    int *p = array;

    for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
        *p = 0;
        p++;
    }

    for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
        printf("array[%d]=%d\n", i, array[i]);
    }
}



// 清除数组中的值(p的值不发生改变)
void clearArrayValueByPoint2() {

    int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    int *p = array;

    for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
        *(p + i) = 0; // 循环完成之后,p仍然指向首元素地址
    }

    for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
        printf("array[%d]=%d\n", i, p[i]); // 因为此时p仍然指向首元素地址,所以可以使用p[i]来输出各个元素
    }
}


// 使用指针来进行冒泡排序
void sortByPoint() {

    int array[3][4] = { { 32, 45, 56, 13 }, { 65, 43, 132, 54 }, {12,32,43,55} };
    int *p = array; // array可理解成里面有12个元素的array[12],任何一个多维数组都可以理解成一个一维数组

    // 排序
    for (int i = 0; i < 12; i++) {
        
        for (int j = 1; j < 12-i;j++) {
            if (p[j] < p[j-1]) {
                int tmp = p[j];
                p[j] = p[j - 1];
                p[j - 1] = tmp;
            }
        }
    }

    // 输出
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("array[%d][%d]=%d\t",i,j,array[i][j]);
        }
        printf("\n");
    }

}


// 任何一种数据类型都是char的数组
void point_test_2() {

    int a = 0x12345678;
    unsigned char *p = (char *)&a;
    printf("%x, %x, %x, %x\n",p[0],p[1],p[2],p[3]);

}


void point_test_3() {

    int a = 0x12345678;
    unsigned char *p = (char *)&a;
    printf("%x, %x, %x, %x\n", p[0], p[1], p[2], p[3]);
    p[1] = 0;
    printf("%x\n",a);

}


// 表示IP地址 192.168.111.123
void ipAddressByPoint() {

    unsigned int a = 0;
    unsigned char *p = (char *)&a;
    p[0] = 123;
    p[1] = 111;
    p[2] = 168;
    p[3] = 192;
    printf("%u\n",a);

}

3.2 多级指针

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数

// 多级指针
void multiPoint();

// 二级指针
void multiPoint1();

// 三级指针
void multiPoint2();

void main() {
    
    multiPoint2();
    system("pause");
}



// 多级指针
void multiPoint() {

    int *a[10] = { 0 };
    int **p = a;
    int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    for (int i = 0; i < 10; i++) {
        a[i] = &array[i]; // 将指针数组中的每一个指针指向数组中的每一个元素
    }

    for (int i = 0; i < 10; i++) {
        printf("array[%d]=%d\n", i, *p[i]); // 取出每个指针对应的数组内容
    }
}

// 二级指针
void multiPoint1() {

    int a = 10;
    int *p = &a;
    int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
    // 修改a的值
    **q = 100;
    printf("a=%d\n",a);
}


// 三级指针
void multiPoint2() {

    int a = 10;
    int *p = &a;
    int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
    int ***z = &q;
    // 修改a的值
    ***z = 100;
    printf("a=%d\n", a);
}

3.3 指针变量当做函数的参数

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数

// 将指针变量用作函数的参数
void pointAsFunctionArgus(int *a);

// 交换两个数的值
void myswap(int *a1, int *a2);

void main() {
    
    //int a = 10;
    //// 将a的值+1
    //pointAsFunctionArgus(&a);
    //printf("a=%d\n",a);

    int a = 10;
    int b = 20;
    // 交换a,b的值
    myswap(&a,&b);
    printf("a=%d\nb=%d\n",a,b);

    system("pause");
}

// 交换两个数的值
void myswap(int *a1, int *a2) {

    int tmp = *a1;
    *a1 = *a2;
    *a2 = tmp;

}

// 将指针变量用作函数的参数
void pointAsFunctionArgus(int *a) {
    (*a)++;
}

3.4 数组名作为函数参数

当数组名作为函数参数时,C语言将数组名解释为指针,以下三种写法等价:

int func(int array[10]);

int func(int array[]);

int func(int *array)

如果数组名作为函数的参数,那么这个就不是数组了,而是一个指针变量。

C语言中,如果把数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,需要再增加一个参数来标明这个数组的大小。

 

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数

// 把数组名作为函数参数
void arrayNameAsFunctionArgus(int array[10]);

// 将数组内容输出
void myprint(const int *a, unsigned int n);

// 冒泡排序
void bubble(int *a, unsigned int n);

void main() {
    
    //int array[10] = {1,2,3,4,5,6,7,8,9,10};
    //arrayNameAsFunctionArgus(array);

    int array[3][4] = { { 34, 54, 32, 45 }, { 90, 34, 12, 56 }, {90,220,332,10} };
    bubble(array,sizeof(array) / sizeof(int));
    myprint(array, sizeof(array) / sizeof(int));

    system("pause");
}

// 冒泡排序
void bubble(int *a,unsigned int n) {

    for (int i = 0; i < n; i++) {
        for (int j = 1; j < n - i; j++) {
            if (a[j] < a[j-1]) {
                int tmp = a[j];
                a[j] = a[j - 1];
                a[j - 1] = tmp;
            }
        }
    }
}

// 将数组内容输出
void myprint(const int *a, unsigned int n) {

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

}


// 把数组名作为函数参数,当数组名作为函数参数时,C语言将数组名解释为指针(下列三种写法等价)
//void arrayNameAsFunctionArgus(int array[])
//void arrayNameAsFunctionArgus(int *array)
void arrayNameAsFunctionArgus(int array[10]) {
    *array = 100; // 这种写法很危险!!!
    array += 2; // 1.使用者可以修改原始数组里的值,2.若使用者对指针进行运算,还会发生越界!!!正确写法:    func(const int array[])
    // const 保护形参的值不被改变

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

四.memset、memcpy,memmove函数

4.1 memset函数

#include <string.h>

void *memset( void *buffer, int ch, size_t count );

功能:设置一块内存区域。主要作用:把一块内存重新设置为0

 

参数说明:第一个参数是内存首地址,第二个参数是要设置的值,第三个参数是这块内存的大小,单位:字节。

4.2 memcpy函数

#include <string.h>

void *memcpy( void *to, const void *from, size_t count );

 

功能:内存拷贝。使用memcpy的时候要注意内存区域不能重叠。

参数说明:第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数

4.3 memmove函数

#include <string.h>

void *memmove( void *to, const void *from, size_t count );

功能:内存移动,参数与memcpy一致。

参数说明第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数

示例代码:

#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数
#include <string.h> // 使用memset、memcpy、memmove函数

// memset函数的使用
void memsetDemo();

// memcpy函数的使用(使用memcpy的时候需要注意内存区域不能重叠)
void memcpyDemo();

// memmove函数的使用
void memmoveDemo();

void main() {
    
    memmoveDemo();
    system("pause");
}

// memmove函数的使用
void memmoveDemo() {

    int a1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int a2[10];
    memmove(a2, a1, sizeof(a1));

    // 输出a2的值
    for (int i = 0; i < sizeof(a2) / sizeof(int); i++) {
        printf("a2[%d]=%x\n", i, a2[i]);
    }

}


// memcpy函数的使用
void memcpyDemo() {
    int a1[10] = {1,2,3,4,5,6,7,8,9,10};
    int a2[10];
    memcpy(a2,a1,sizeof(a1));
    
    // 输出a2的值
    for (int i = 0; i < sizeof(a2) / sizeof(int); i++) {
        printf("a2[%d]=%x\n",i,a2[i]);
    }

}

// memset函数的使用
void memsetDemo() {

    char array[10] = { 0 };
    array[1] = 100;
    array[5] = 2;
    array[8] = 6;
    // 把这块内存的所有成员再次初始化为0
    memset(array,0,sizeof(array));

    for (int i = 0; i < sizeof(array) / sizeof(char); i++) {
        printf("array[%d]=%d\n",i,array[i]);
    }
}

五.字符数组与字符串指针

C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中:

#include <stdio.h>
int main(){
    char str[] = "http://www.baidu.com";
    int len = strlen(str), i;
    //直接输出字符串
    printf("%s\n", str);
    //每次输出一个字符
    for(i=0; i<len; i++){
        printf("%c", str[i]);
    }
    printf("\n");
    return 0;
}

运行结果:
http://www.baidu.com
http://www.baidu.com

或者是定义字符串指针,使用这种方式输出:

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

int main()
{
    char *str = "http://www.baidu.com";
    int len = strlen(str), i;
   
for (i = 0; i < len; i++) { printf("%c", *str++); } printf("\n"); return 0; }

输出结果:
http://www.baidu.com

那这两种方式输出的区别在哪呢?区别在于:字符数组存储在全局数据区或者是栈区,而指针指向的字符串存储在常量区。全局数据区或栈区的字符串有读取和写入的权限,而常量区的字符串只有读取的权限,没有写入权限。内存权限不同导致的一个显著的结果就是:字符数组可以在定义后读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后,则不能对其进行修改。

例:修改字符串中第一个字符为a

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

int main()
{

    char *str = "http://www.baidu.com";
    
    int len = strlen(str), i;
    for (i = 0; i < len; i++) {
        printf("%c", *str++);
    }        
    printf("\n");        
    
    *str = 'a';
        
    return 0;
}

运行结果:

http://www.baidu.com
Bus error: 10

出现了段错误。

 

posted @ 2017-02-07 14:54  夜行过客  阅读(464)  评论(0编辑  收藏  举报