06.C(指针)

简介: (创建于2017/8/17)指针的含义#includeint main() { int a = 10; int *p = &a; //这行代码等同于 int *p; p = &a; //int *p; /...

(创建于2017/8/17)

指针的含义

#include<stdio.h>

int main() 
{
    int a = 10;
    int *p = &a;  //这行代码等同于 int *p; p = &a;

    //int *p;
    //*p = &a;    //这种方式是错误的,这两行的意思就是定义以一个指针,但是没有
                  //具体的指向,也就是一个野指针,然后给这个野指针指向的内容(*p就是指向的内容的意思)
                  //赋值,也就是将a变量的地址赋值给p指向的内容,但实际上,p没有具体的指向,所以这样是错误的
    
                  //*p //代表指针所指内存的实际数据,切记,指针只能存放地址,不能将一个int
                  //型的变量直接赋值给一个指针,这样是没有意义的 比如 int *p = 100;
    return 0;        

}
指针的步长

指针运算,不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算

#include <stdio.h>

int main() {
    int a;
    int *p = &a;
    printf("p: %d,p+1:%d\n",p,p+1);

    char b;
    char *q = &b;
    printf("q: %d,q+1:%d\n", q, q + 1);
    system("pause");
}
打印
p: 5962300,p+1:5962304
q: 5962279,q+1:5962280
请按任意键继续. . .
#include<stdio.h>

int main() 
{
    int a = 100000000;
    printf("%d\n",sizeof(a));    //得到4,int占四个字节,也就是说sizeof得到的是字节数
    return 0;        
}

1.取地址符 &

#include<stdio.h>

int main() 
{
    int a = 190;
    printf("%p\n", &a);    //取出a的地址,%p输出指针的地址
    return 0;
}

#include<stdio.h>

int main() 
{
    int a = 190;
    printf("%p\n", &a); 
    int *p = &a;
    printf("%X\n", p);  //a的地址赋值给变量p,将地址格式化为十六进制形式,以大写表示,
    int *p1;            //第一一个变量,名字叫p1,他可以指向一个int类型的地址
    p1=&a;                //指针变量的值一般不能直接赋值一个整数,而是通过取变量地址的方式

    return 0;
}
2.p代表的意思(号,定义指针的时候表示一种类型,使用的时候表示操作指向的内存)
#include<stdio.h>

int main()  
{
    int a = 1;
    int *p;
    p = &a;
    int b = *p;          //p指向a的地址,*p的意思是指针变量指向的内容
    printf("%X\n", b);  

    *p = 2;                  //通过指针间接修改制定内存地址的值,
    return 0;
}

通过指针修改了a的值

#include<stdio.h>

int main() 
{
    int a = 10;
    int *p;
    p = &a;
    *p = 1;
    printf("%d\n",a);
    return 0;
}

输出结果a=1

3.数组中元素地址的特点

int main() 
{
    char buf[10];
    printf("%u,%u,%u,%u\n", buf, &buf[0], &buf[1], &buf[2]);
    printf("---------------------------------------------------\n");
    int buf1[10];
    printf("%u,%u,%u,%u\n", buf1, &buf1[0], &buf1[1], &buf1[2]);
    return 0;
}

输出结果:(char每个元素占一个字节,int每个元素占四个字节,可以从地址的连续性上看出来)
16645708, 16645708, 16645709, 16645710 //可见数组的地址就是数组中第一个元素的地址


16645660, 16645660, 16645664, 16645668
请按任意键继续. . .

4.无类型指针(万能指针)

void p
定义一个指针变量,但不指定他指向的哪种数据类型,可以通过强制转换将void
转换为其他指针类型,也可以用void将其他指针类型转换为void指针类型
1 不可以定义void类型的普通变量,不能确定类型
2 可以定义void
变量,void指针也叫万能指针
3 void
可以指向任何类型的变量,使用指针所指向的内存时,最后转换为它本身的指针类型

#include <stdio.h>

int main() {
    void *p = NULL;
    int a = 10;
    p = &a;
    *((int*)p) = 20;
    printf("*p = %d\n", *((int*)p));
    printf("a = %d\n", a);
    system("pause");
}
打印
*p = 20
a = 20
请按任意键继续. . .

5.指针的大小

指针不管指向哪种数据类型,他的大小都是一定的,不过与编译器有关
32位编译器用32位大小(4字节)保存
64位编译器用64位大小(8字节)保存


#include<stdio.h>

int main()
{
    int a = 3;
    char c = 0;
    
    int *p = &a;
    char *p2 = &c;
    printf("%x\n", sizeof(p));
    printf("%x\n", sizeof(p2));
    return 0;
}

输出结果:
4
4
请按任意键继续. . .
6.空指针与野指针
  1. 只有定义后的变量,此变量的地址才是合法的
  2. 野指针就是保存没有意义地址的指针变量或者干脆没有保存地址
  3. 操作野指针本身不会有任何问题
  4. 操作野指针所指向的内存才导致错误,比如你打印一个随手指定的地址没有错,但是操作这个地址上的内存就会有问题
#include<stdio.h>

int main() 
{
    int *p;
    p = NULL;  //空指针,NULL就是数字0
    return 0;
}


#include<stdio.h>
#pragma warning(disable:4700)  
int main() 
{
    int a = 10;
    int *p;                    //p没有具体的指向(或者你随手指定一个地址给他),是野指针(这个无法运行)
    *p = 1;  
    printf("%d\n",a);
    return 0;
}
7.指针的兼容性

指针之间赋值比普通数据类型赋值检查更为严格,例如:不能把double赋值给int
原则上一定是相同类型的指针指向相同类型的变量地址,不能用同一种类型的指针指向另一种类型的变量地址

#include<stdio.h>

int main() 
{
    float a = 3.14;
    int i = a;                //自动数据类型转换,舍去小数点部分
    printf("%d\n", i);
    int *p = &a;            //错误,指针类型不兼容,打印结果无厘头
    printf("%d\n",*p);
    return 0;
}
输出结果:

3
1078523331
请按任意键继续. . .
8.指向常量的指针和指针常量

指向常量的指针
const int* p = &a const 修饰,代表指针所指向的内存是只读的,无法通过指针操作这个内存,比如说不能通过p修改内存的值,但是这个地址可以改变,p可以指向另一个变量的内存
int* const p2 = &a const修饰指针变量,代表指针变量的值为只读,表示指针指向的地址是不能改变的,但是这个地址的值可以改变

#include <stdio.h>
int main() {
    int a = 10;
    int b = 10;
    int *p1 = &a; 
    *p1 = 100;   //操作a的内存
    p1 = NULL;   //操作指针变量p,不会影响到a的内存
    printf("a = %d\n", a);

    //const 修饰*,代表指针所指向的内存是只读的
    //(等价于int const *p2 = &a,两种写法是相同的)
    //表示p2指向地址的值不能改变,但是这个地址可以改变
    const int *p2 = &a;  
    //*p2 = 200;无法修改内存的值,相当于
    p2 = NULL;

    //const修饰指针变量,代表指针变量的值为只读
    //表示p4指向的地址是不能改变的,但是这个地址的值可以改变
    int* const p4 = &a;
    //设置p4指向的地址的值为100
    *p4 = 100;
    //修改p4指向的地址的值,这时错误的
    //p4 = NULL; //err
    system("pause");
}
#include<stdio.h>

int main() 
{
    int a = 10;
    const int *p = &a;   
    //*p = 30;                //不能通过*p的方法修改一个const指针,运行会提示错误,
                            //表达式必须是可修改的左值,const修饰则无法修改(有一种方式可以达到修改的目的,这是C语言的一个不够严谨的地方,看下边)
    a = 20;
    printf("%d\n",*p);        //*p是个只读的值,不能修改
    return 0;
}

#include<stdio.h>
int main() 
{
    int a = 100;
    const int *p = &a;
    int *p2 = p;            //(p = &a) 我们用另一个指针指向p,也就是指向了p所指向的a的地址
                            //然后修改*p2的值达到修改a的值的目的(这是C语言的一个不够严谨的地方,c++中是不可以的)
    *p2 = 200;
    printf("%d\n", a);
    return 0;        
}

同样道理

#include<stdio.h>

int main() 
{
    const int a = 100;
    //a = 200;错误,无法修改
    int *p = &a;
    //在Windows上修改成功,Mac上xcode无法修改成功,打印的仍是100,并且会有警告
    *p = 200;          
    printf("%d\n", a);
    system("pause");    
}

指针常量(一旦定义它所指向的地址无法发生改变,但是该地址的值可以改变,这和上边指向常量的指针恰恰相反,指针常量可以修改值不能修改地址,指向常量 的指针可以修改地址,但是不能修改值)

#include<stdio.h>

int main() 
{
    int a = 10;
    int b = 20;
    int *const p = &a;    //定义一个常量指针,可以通过常量指针修改或者
    *p = 20;            //读取一个变量的值
    //p = &b;                //常量指针一旦定义了,就不能修改其指向的变量
    printf("%d\n",*p);        
    return 0;
}
9.指针与数组的关系

注意,二级指针不是二维数组

#include <stdio.h>
int main() {
    int a[3] = {1,2,3};
    //指针数组,是数组,每个元素都是指针
    int *p[3];

    int n = sizeof(p) / sizeof(p[0]);
    int i = 0;
    for (; i < n; i++) {
        //等价于p[i]=a+i;
        p[i] = &a[i];
    }

    for ( i = 0; i < n; i++)
    {
        //p[i]存储的是地址,所以*p[i]是这个地址的值
        printf("*p[i] = %d,p[i]=%d\n", *p[i],p[i]);
    }
    
    system("pause");
}
#include <stdio.h>
int main() {
    int a = 10;
    int *p = &a;
    //通过指针间接操作a的内存
    *p = 111;
    printf("a = %d\n",a);
    printf("*p = %d\n", *p);
    printf("p[0] = %d\n", p[0]);
    //*p等价于*(p+0),同时等价于p[0]
    p[0] = 250;
    printf("a2=%d\n",a);

    //等价于*(p+1),操作野指针所指向的内存,可能会报错
    p[1] = 111;
    printf("p[1] = %d\n", p[1]);
    system("pause");
}
#include<stdio.h>

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

int main() 
{
    char buf[10] = {1,2,3,4,5,6};
    //int n = sizeof(buf)/sizeof(*buf);可以得到数组的长度
    char *p = buf;
    char *p1 = &buf[0];
    char *p2 = &buf[1];
    char *p3 = &buf[2];

    *p2 = 7;  //将数组中的第二个值修改为7

    p3 += 1;  //数组在内存中的地址是连续的,char数组每个元素一个字节,p3
    *p3 = 100;        //表示buf[2]的地址,加一就是表示buf[3]的地址,然后将buf[3]的值修改为100
    p3 -= 2;
    *p3 = 12;
    printbuf(buf);
    return 0;
}

打印
buf[0]=1
buf[1]=12
buf[2]=3
buf[3]=100
buf[4]=5
buf[5]=6
buf[6]=0
buf[7]=0
buf[8]=0
buf[9]=0
请按任意键继续. . .
#include<stdio.h>

int main() 
{
    int buf[10] = {1,2,3,4,5,6,7,8,9};
    int *p = buf;      //指向这个数组,也就是指向数组中的第一个元素的地址
    p++;                //p = p +1; 指向了数组中第二个元素
    //buf++;            //这样写是错误的,它等同于buf = buf + 1;但是buf是一个常量,不能做左值
    int *p2 = buf + 1;  //这样是可以的,等同于p2++,p2会指向数组中第二个元素
    //int *p2 = buf++;  //这样也是错误的,同buf++
    return 0;        
}
#include<stdio.h>

int main() 
{
    int buf[10] = {1,2,3,4,5,6,7,8,9};
    int *p = buf;      
    int i;
    for ( i = 0; i < 10; i++)
    {
        printf("%d\n",p[i]);    //C语言允许指针通过数组下标的方法访问数组成员
        printf("%d\n", *(p + i)); //等同于上边的代码
    }
    return 0;        
}
#include<stdio.h>

int main() 
{
    int a = 100000000;
    char *p = &a;
    printf("%x,%x,%x,%x\n",*p,*(p+1),*(p+2),*(p+3));
    printf("%x,%x,%x,%x\n", p[0], p[1],p[2],p[3]);    //两者等同
    return 0;        
}
10.指针求字符串的长度和合并字符串
#include<stdio.h>

int main() 
{
    char a[100] = "hello";
    char b[100] = "world";
    char *p1 = a;
    char *p2 = b;
    int len = 0;
    while (*p1)          //值=0的时候循环退出,=0说明到达了字符串末尾,因为字符串以0结尾
    {
        p1++;
        len++;
    }
    printf("%d\n", len);

    while (*p2)
    {
        *p1 = *p2;            //将p2所在地址的值赋给当前p1所在的位置
        p2++;
        p1++;
    }
    printf("%s\n", a);    //p1指向的是数组a,修改的值是数组a的值
    return 0;

}

值传递

不会改变实参的值

#include <stdio.h>
#include <string.h>
void printbuf(char buf[])
{
    for (int i = 0; i < 10; i++)
    {
        printf("buf[%d]=%d\n", i, buf[i]);
    }
}

void swap(int m, int n) {
    int temp;
    temp = m;
    m = n;
    n = temp;
    printf("m=%d,n=%d\n", m, n);
}
int main() {
    
    int a = 11;
    int b = 12;
    swap(a, b);
    printf("a=%d,b=%d\n", a, b);
    system("pause");
}
地址传递

传递的是地址,会改变实参的值

#include <stdio.h>

void swap(int* m, int* n) {
    int temp;
    temp = *m;
    *m = *n;
    *n = temp;
    printf("m=%d,n=%d\n", *m, *n);
}
int main() {
    
    int a = 11;
    int b = 12;
    swap(&a, &b);
    printf("a=%d,b=%d\n", a, b);
    system("pause");
}
形参中传递的数组

1.形参中的数组,是指针变量
2.形参数组:int a[10000], int a[],int *a,对编译器来讲没有任何区别,都是当作int指针处理
3.形参中的数组和非形参中的数组区别:形参中数组是指针,非形参数组是数组

//#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void print_array(int *buf, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("buf[%d]=%d\n", i, buf[i]);
    }
}

//形参中的数组不是数组,是指针变量
void pintf_array2(int buf[10])
{
    int n = sizeof(buf) / sizeof(buf[0]);
    for (int i = 0; i < n; i++)
    {
        printf("n=%d,sizeof(buf)=%d,sizeof(buf[0])=%d\n", n,sizeof(buf)), sizeof(buf[0]);
        printf("buf[%d]=%d\n", i, buf[i]);
    }
}

int main() {
    int a[] = { 1,-2,3,-4,5,-6,7,-7,8,-8,9 };
    //在这里这种方式可以得到数组的长度,但是放在上边的位置,通过形参中的数组是获取不到的
    //因为形参中的数组只是一个指针,那么sizeof(buf),buf是指针变量,32位操作系统中,指针变量buf的
    //大小为4,sizeof(buf[0]),buf[0]是int型的变量,也是4,所以sizeof(buf) / sizeof(buf[0])得到的是1并非
    //数组的长度,所以通过指针传递数组操作数组,一定要将数组长度提前得到传递到方法中
    int n = sizeof(a) / sizeof(a[0]);
    pintf_array2(a);
    printf("-----------------------------------------\n");
    print_array(a, n);
    system("pause");
}
返回局部变量的地址

下边的代码在visual studio和qt中都可以编译通过,也可以获取到p指向的地址,*p的值也的确是所赋的值,然而实际上,这是有问题的,由于方法执行后销毁掉,a的地址其实已经变成了非法地址,这时候去操作这个地址也是非法的,在Linux 64位gcc执行,无法获取到这个a的地址。这跟编译器有关系

#include <stdio.h>
//返回a的地址
int* fun() {
    int a;
    printf("&a = %p\n", &a);
    return &a;
}

int main() {
    int *p = NULL;
    //p指向a的地址        
    p = fun();
    //fun方法执行完后,自动销毁a变量,所以此时这个a的地址已经是非法地址了        
    printf("p = %p\n", p);
    *p = 100;
    printf("*p = %d\n", *p);
    system("pause");
}
返回全局变量的地址
#include <stdio.h>
int a;
int* fun() {
    printf("&a = %p\n", &a);
    return &a;
}

int main() {
    int *p = NULL;
    p = fun();
    printf("p = %p\n", p);
    *p = 100;
    printf("*p = %d\n", *p);

    //直接操作地址,不需要定义中间变量
    printf("*fun = %d\n", *fun());
    system("pause");
}
打印字符串说明
#include <stdio.h>

int main() {
    char str[] = {"hello world"};

    //以%s为占位符打印一个字符数组(字符串),得到的是这个字符串的内容
    printf("str = %s\n",str);
    //以%p为占位符打印一个字符数组(字符串),得到的是这个字符串的地址
    printf("str = %p\n", str);
    //这样打印是会报错的,因为%s打印字符串就已经做了相应的处理了,不用加*
    //printf("str = %s\n",*str);
    //str表示字符串中第一个字符的地址,*str表示第一个字符,所以用%c打印可以正常输出
    printf("str = %c\n", *str);

    //以%s为占位符打印一个字符数组(字符串),其实内部实现就是这样
    int i = 0;
    while (str[i] != '\0') {
        printf("%c", str[i]);
        i++;
    }
    printf("\n");
    system("pause");
}

打印:
str = hello world
str = 0055FB1C
str = h
hello world
请按任意键继续. . .

自己实现字符串拷贝函数strcpy
#include <stdio.h>
void my_strcpy(char *dst, char* src) {
    int i = 0;
    while (*(src + i) != '\0') {
        *(dst + i) = *(src + i);
        i++;
    }
    *(dst + i) = 0;
}
int main() {
    char src[] = "hello world";
    char dst[100];
    char *p = dst;
    my_strcpy(p, src);
    printf("%s\n", p);
    system("pause");
}
字符串常量

每个字符串都是一个地址,这个地址是指字符串首元素地址,字符串常量放在data区,文字常量区

#include <stdio.h>

int main() {
    //每个字符串都是一个地址,这个地址是指字符串首元素地址
    //每个字符串常量放在data区,文字常量区
    
    //直接打印一个字符串,以p为占位符得到的是地址
    printf("s1 = %p\n", "hello");

    //因为每个字符串都是代表首地址,所以+1之后就是把地址向后移动了一步,
    //看打印结果,这两行 代码恰恰证明了每个字符串都是地址这个结论
    printf("s1 = %s\n", "hello");
    printf("s1 = %s\n", "hello"+1);
    //文字常量区的字符串,是只读的不能修改
    char *p = "good morning";
    //这里会报错
    *p = "nice";
    printf("*p = %s\n", *p);
    system("pause");
}
查找匹配字符串出现的位置

#include <stdio.h>

int main() {
    char *p = "11abcd121232abcd23232abcd98y7abcdqqq";
    int i = 0;
    char *temp = NULL;
    while (1) {
        //查找匹配的字符串,如果找到,返回匹配字符串的地址,没有找到返回空
        temp = strstr(p,"abcd");
        if (temp == NULL) {
            break;
        }
        else {
            i++;
            //  找到一个之后,设置这个字符串的后边位置为下一次寻找的起始位置
            p = temp + strlen("abcd");
        }
    }
    printf("个数 = %d\n", i);
    system("pause");
}
两头堵模式实现拷贝字符串并去除两端的空字符
#include <stdio.h>

int main() {
    char *p = "       1234567        ";
    //start指针指向第一个元素地址
    char *start = p;
    //end指向最后一个元素地址
    char *end = p + strlen(p) - 1;

    //从左到右,找到第一个不是空的字符位置
    while (*start == ' ' && *start != '\0') {
        start++;
    }
    //从右到左,找到第一个不是空的字符位置
    while (*end == ' ' && end != p) {
        end--;
    }

    //得到不包含空字符的字符串长度
    int n = end - start + 1;
    printf("n = %d\n", n);

    char buf[100] = "aaaaaaaaaaaaaa";
    //拷贝
    strncpy(buf, start, n);
    buf[n] = 0;
    printf("buf = %s\n", buf);
    system("pause");
}
11.游戏作弊器

__declspec(dllexport)      //添加这个关键字代表go函数是可以在其他程序中调用的dll函数
void go()
{
    int *p = 0x1CF88808;    //得到植物大战僵尸中阳关变量的地址,这个地址是
    while (1)                //植物大战僵尸的地址,我自己写的程序没有权利修改其他程序的地址的内容
    {
        if (*p < 200)
        {
            *p = 400;
        }
    }
}

//操作步骤:目前此方法只针对单机游戏有效
/*
    1.生成dll动态链接库,项目->属性->配置属性->项目默认值->配置类型 选择动态库(.dll)
    2.在顶部添加关键字__declspec(dllexport)
    3.修改返回值类型为void,函数名随便改个(这一步非必需)
    4.点击本地windows调试器,会在指定目录位置生成.dll文件
    5.需要两个工具辅助a:Cheat Engine 6.3
    用于获取游戏中相应变量的地址b.DllInject.exe用于将dll文件注入到你要修改的游戏中
    7.注入之后游戏修改成功
*/
12.指针的运算
#include<stdio.h>
int main()
{
    int buf[10];
    int *p = buf;                      //注意指针指向数组不需要取地址符,但是指向数组中的某一个元素,需要区地址符
    int *p1 = &buf[1];
    printf("%d,%d\n", p, p1);
    p++;
    printf("%d\n", p);
    return 0;
}

输出结果:
15465412,15465416
15465416
请按任意键继续. . .

为什么p++之后得到的地址比原本增加了4呢?
指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数作为倍数的运算
char *p;
p++ 移动了sizeof(char)这么多个字节数

int p;
p++ 移动了sizeof(int)这么多个字节数
p+=3 移动了3
sizeof(int)个字节(注意这个int指的是他本身类型的int)

如果此时
p = (int)p+3; 将p强转为一个整数,那么就会按照整数的加法去计算,得到的也就是增加3后的结果,但是这样
对指针是没有意义的

#include<stdio.h>
int main()
{
    int buf[10];
    int *p = buf;
    int *p1 = &buf[1];
    printf("%d,%d\n", p, p1);
    p++;
    printf("%d\n", p);

    int *pp1 = &buf[1];
    int *pp2 = &buf[3];
    printf("%d\n", pp2 - pp1);    //结果是2  两个指针相减得到两个数组元素 的相对距离

    char *pp3 = &buf[1];
    char *pp4 = &buf[3];

    printf("%d\n", pp4 - pp3);      //结果是8  相差两个int,一个int4个字节,也就是相差八个字节,char类型
                                  //的指针一个char一个字节,那么也就是8个char
    
    long long *pp3 = &buf[1];
    long long *pp4 = &buf[3];    //long long 占八个字节,所以结果是1

    short *pp3 = &buf[1];
    short *pp4 = &buf[3];    //short 占2个字节,所以结果是4(本质上就是有几个short)

    printf("%d\n", pp4 - pp3);    //不要相加,编译无法通过,没有意义
    return 0;                      


}
指针的一些操作

赋值:int *p = &a;
求值:int i = *p;
取指针地址:int **pp = &p; p本身是一个指针
求差值: p1 -p2 通常用与一个数组内求两个元素的距离
比较: p1 == p2 通常用来比较两个指针是否指向同一个位置

13.通过指针求数组中的最大值
#include<stdio.h>
int max(int arr[])
{
    int maxValue = arr[0];
    for (int i = 0; i < 10; i++)
    {
        if (maxValue < arr[i])
        {
            maxValue = arr[i];
        }    
    }
    printf("%d\n", maxValue);
}

int max2(int *s)
{
    int maxValue = *s;
    for (int i = 0; i < 10; i++)
    {
        if (maxValue < *(s+i))
        {
            maxValue = *(s+i);
        }
    }
    printf("%d\n", maxValue);
}
int main() 
{
    int arr[100] = {1,34,23,45,767,232,12,23,34};
    max(arr);
    max2(arr);
    return 0;
}
14.通过指针将数组逆置
#include<stdio.h>

int reverse(int arr[])
{
    int start = 0;
    int end = 9;
    while (start < end)
    {
        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
        start++;
        end--;
    }
    int i;
    for (i = 0; i <= 9; i++)
    {
        printf("%d\n",arr[i]);
    }
}
int reverse2(int *arr)
{
    int *start = &arr[0];
    int *end = &arr[9];
    while (start < end)
    {
        int temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
    int i;
    for (i = 0; i <= 9; i++)
    {
        printf("%d\n", arr[i]);
    }
}
int main() 
{
    int arr[100] = {1,34,23,45,767,232,12,23,34,56};
    reverse2(arr);
    return 0;
}
15.在不对数组排序的条件下,求数组中第二大元素
#include<stdio.h>

void s_max(int arr[])
{
    int max;
    int s_max;
    if (arr[0] > arr[1])
    {
        max = arr[0];
        s_max = arr[1];
    }
    else
    {
        max = arr[1];
        s_max = arr[0];
    }
    int i;
    for (i = 2; i < 10; i++)
    {
        if (max < arr[i])
        {
            s_max = max;
            max = arr[i];
        }
        else if(max > arr[i] && arr[i] > s_max)
        {
            s_max = arr[i];
        }
    }
    printf("%d\n", s_max);
}
void s_max2(int *s)
{
    int max;
    int s_max;
    if (*s > *(s+1))
    {
        max = *s;
        s_max = *(s + 1);
    }
    else
    {
        max = *(s + 1);
        s_max = *s;
    }
    int i;
    for (i = 2; i < 10; i++)
    {
        if (max < *(s+i))
        {
            s_max = max;
            max = *(s + i);
        }
        else if (max > *(s + i) && *(s + i) > s_max)
        {
            s_max = *(s + i);
        }
    }
    printf("%d\n", s_max);
}
int main() 
{
    int arr[100] = {1,34,23,45,767,232,12,23,34,56};
    s_max(arr);
    printf("---------------------------------------\n");
    s_max2(arr);
    return 0;
}
16.指针实现冒泡排序
#include<stdio.h>

void bubble(int arr[])
{
    for (int i = 0; i < 7; i++)
    {
        for (int j = 0; j < 7-i-1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }

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

void bubble2(int *s)
{
    for (int i = 0; i < 7; i++)
    {
        for (int j = 0; j < 7 - i - 1; j++)
        {
            if (*(s+j) > *(s+j+1))    //s指向数组中第一个元素的位置,s+j就是指向数组中第j个元素的地址,*(s+j)就是第j个元素的值
            {
                int temp = *(s+j);
                *(s + j) = *(s + j+1);
                *(s + j + 1) = temp;
            }
        }
    }

    int i;
    for (i = 0; i <7; i++)
    {
        printf("%d\n", *(s+i));  //注意括号的位置
    }
}
int main() 
{
    int arr[100] = {1,34,23,45,767,232,12,23,34,56};
    bubble(arr);
    printf("---------------------------------------");
    bubble2(arr);
    return 0;
}
17.指针实现将英文逆置
#include<stdio.h>

int reverse(char arr[])
{
    char *start = &arr[0];
    char *end = &arr[strlen(arr) - 1];

    while (start < end)
    {
        char *temp = *start;    
        *start = *end;
        *end = temp;
        start++;
        end--;
    }

    printf("%s\n", arr); 
    
}

int main() 
{
    char arr[100] = "hello world";
    reverse(arr);
    return 0;
}
18.指针实现汉字逆置
#include<stdio.h>

int reverse(char arr[])
{
    short *start = &arr[0];                  //short刚好占两个字节,在vs中表示汉字正合适
    short *end = &arr[strlen(arr) - 2];  //vs中汉字是gbk编码方式,一个汉字占2个字节
                                  //qt中是utf-8编码方式,每个汉字占3个字节,strlen得到的是字节数,假如有四个汉字,strlen结果在vs中就是8,因此这里减的是2
    while (start < end)
    {
        short *temp = *start;    
        
        *start = *end;
        *end = temp;
        start++;
        end--;
    }

    printf("%s\n", arr); 
    
}

int main() 
{
    char arr[100] = "你好世界";
    reverse(arr);
    return 0;
}
19.多级指针
img_39564c97021dfa35b16a4d07882563fe.png
多级指针图解.png
#include <stdio.h>
#include <string.h>

int main() {
    int a = 10;
    int *p = &a;
    int **q = &p;
    int ***t = &q;
    int ****m = &t;

    printf("*m = %d\n", *m);
    printf("t = %d\n", t);

    printf("**m = %d\n", **m);
    printf("q= %d\n",q);

    printf("***m = %d\n", ***m);
    printf("p= %d\n", p);

    printf("****m = %d\n", ****m);
    printf("a= %d\n", a);
    system("pause");
}

打印
*m = 12385392
t = 12385392
**m = 12385404
q= 12385404
***m = 12385416
p= 12385416
****m = 10
a= 10
请按任意键继续. . .

从上边的图解中可以看到,首先定义了一个变量a,给他赋值为10,然后定义了一个一级指针p指向他,二级指针q指向p,三级指针t指向q,四级指针m指向t,我们知道,*号用在定义指针的时候表示一种类型,例如int *p = &a 定义一种一级指针类型指向a,在使用的时候表示操作所指向的内存,m指向t所指向的内存,那么*m就是操作m所指向的内存,就是t指向的内存,*m ->t 。那么**m就可以看作*(*m) -> *t,*t同理就是操作t所指向的内存,就是q,那么**m -> q,以此类推

简单应用:英汉词典

#define _CRT_SECURE_NO_WARNINGS
#include<string.h>
#include<stdio.h>
#define MAX 3

struct dict {
    char *key;
    char *content;
};

void get_dict(struct dict **tmp) {
    //在堆区分配MAX个结构体空间
    //把一级指针p取地址传入到tmp,这步操作等价于struct dict **tmp = &p;
    //tmp是个中间变量,我们是要将p指向申请到的空间,*tmp表示的就是
    //指针变量p,所以*tmp = xxx就相当于p = xxx
    *tmp = (struct dict *)malloc(MAX * sizeof(struct dict));
    if (tmp == NULL) {
        return;
    }

    //上边我们申请了MAX个结构体空间,接下来要在这个空间中申请局部的
    //空间来存储每个结构体中的变脸key content

    //第一个结构体放第一个单词(用.实现)
    //(*tmp)[0]表示第一个结构体变量,strlen("a") + 1表示求字母a的长度,+1是因为后边需要\0

    //key申请空间
    (*tmp)[0].key = (char *)malloc(strlen("a") + 1);
    //存值
    strcpy((*tmp)[0].key,"a");

    //content申请空间
    (*tmp)[0].content = (char *)malloc(strlen("字母A")+1);
    //存值
    strcpy((*tmp)[0].content,"字母A");

    //第二个结构体存放第二个单词(用->实现)
    (*tmp + 1)->key = (char *)malloc(strlen("b") + 1);
    strcpy((*tmp + 1)->key, "b");

    (*tmp + 1)->content = (char *)malloc(strlen("字母B") + 1);
    strcpy((*tmp + 1)->content, "字母B");

    //第三个结构体存放第三个单词
    (*tmp + 2)->key = (char *)malloc(strlen("c") + 1);
    strcpy((*tmp + 2)->key, "c");

    (*tmp + 2)->content = (char*)malloc(strlen("字母C") + 1);
    strcpy((*tmp + 2)->content, "字母C");
}

//把p传递到方法中,相当于操作struct dict *tmp = p
//此时等于把p指向的内容赋值给了tmp,tmp指针同样也指向
//p所指向的空间了,那么就可以通过tmp取读取空间中的内容
void print_dict(struct dict *tmp) {
    int i = 0;
    for (; i < MAX; i++) {
        printf("[%s]%s\n", (tmp+i)->key, (tmp + i)->content);
    }
}
void free_dice(struct dict *tmp) {
    //先释放成员变量,再释放结构体指针变量
    int i = 0;
    for (i = 0; i < MAX; i++) {
        if ((tmp + i)->key != NULL) {
            free((tmp + i)->key);
            (tmp + i)->key = NULL;
        }
        if ((tmp + i)->content != NULL) {
            free((tmp + i)->content);
            (tmp + i)->content = NULL;
        }
    }
}

int search_dict(struct dict *tmp, char *key, char *content) {
    int i = 0;
    for (i = 0; i < MAX; i++) {
        if (strcmp(tmp[i].key,key) == 0)
        {
            strcpy(content, tmp[i].content);
            return 1;
        }
    }
    return 0;
}
int main(int argc, char *argv[]) {
    //一个结构体存储一个单词
    struct dict *p = NULL;

    //给单词p分配空间,然后设置存储内容
    get_dict(&p);

    //遍历单词,上边分配空间时将p的地址传入,是为了修改p
    //指向的内容,而下边是为了读取,不需要修改,所以直接传递p
    print_dict(p);

    //查询单词解释
    char key[1024];
    char content[1024];

    while (1) {
        printf("输入需要查询的单词:");
        //注意这里使用fgets存在的问题,fgets接收到字符串保存到key
        //中后,会在字符串末尾加上一个换行符\n,所以直接这样进行
        //strcmp(key,"break")等比较的操作是行不通的,因为他多了个换行符
        //除非这样比较strcmp(key,"break\n")
        //fgets(key, sizeof(key), stdin);
        scanf("%s", key);
        if (strcmp(key,"break")==0)
        {
            break;
        }
        int flag = search_dict(p,key, content);
        if (flag == 0)
        {
            printf("查询不到次单词%s\n", key);
        }
        else {
            printf("%s 的解释为:%s\n",key, content);
        }
    }

    //释放空间
    void free_dice(p);
    system("pause");
}
二维数组和指针的关系
img_a18437cb1d3c5c522f8e12310c53e09b.png
二维数组与指针.png
相关文章
|
2月前
|
存储 程序员
C指针详解
C指针详解。
13 0
|
9月前
|
C++
|
10月前
|
C++
C++指针练习5
C++指针练习5
|
10月前
|
存储 编译器 C++
认识C++指针
认识C++指针
|
11月前
|
C++
C++ 指针篇
指针大小:32位操作系统中指针占4个字节; 64位操作系统中指针占8个字节
103 0
指针(二)
指针变量指向的类型:将变量名和离他最近的一个一起拖黑 剩下啥类型 指针变量指向的类型就是啥类型
指针(二)
|
存储 编译器 C++
一文带你理解C++的指针
一文带你理解C++的指针
130 0
一文带你理解C++的指针
自引用指针this的详解
自引用指针this的详解
127 0
|
存储 C++
C++指针详解
计算机程序在存储数据时必须跟踪3种属性 1、信息存储在何处 2、存储的值是多少 3、存储的信息是什么类型 指针是一个变量,其存储的是值的地址,而不是值本身。 获取常规变量地址 使用&——地址运算符
C++指针详解