11.C语言提高(一)

1.排序

形参中的数组,编译器会把他当作指针处理
形参写在函数上,和写在函数内是一样的,只不过是
写在函数上具有对外的属性

2.关于数组地址

看下面函数的运行结果,你会发现,b+1和&b+1的区别,b表示的是数组元素首地址,所以b+1就是将从首元素向后移动一个元素后得到的第二个元素的地址,但是&b+1的结果优点出乎意料,它相对于首元素地址向后移动了40个位置,而40刚好是数组b的长度,怎么解释呢,其实就是&b表示的是整个数组的地址,把整个数组当作一个整体,那么加一就是移动这样一个整体后的地址了

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

void main() {
    int b[10];
    printf("b:%d,b+1:%d,&b+1:%d", b, b + 1, &b + 1);
    system("pause");
}
打印结果:
b:19922588,b+1:19922592,&b+1:19922628请按任意键继续. . .

1)数组首元素的地址和数组地址是两个不同的概念
2)数组名代表数组首元素地址,他是个常量
解释如下:变量本质是内存空间的别名,而数组在定义时就会分配内存,内存就固定了,所以数组名起名以后就不能更改了
3)数组首元素的地址和数组值相等
C语言规定:
int a[10]
&a表示整个数组的地址,a表示数组首元素的地址,区别体现在地址+1的结果

如何定义一个数组数据类型
int a;//定义了一个int类型a
typedef int (MyArrayType)[5];//定义一个数组数据类型MyArrayType,
MyArrayType myArray;    //相当于int myArray[5]
myArray[0] = 0;
...
myArray[4]=4;
定义数组指针的第一种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void main() {
    char *MyArray[] = {"1111","2222","3333"};
    //数组指针,一个指针,指向了数组

    //定义了一个数组数据类型
    typedef int(MyArrayType)[5];

    int i = 0;

    //用类型定义变量,相当于int myArray[5]
    MyArrayType myArray;

    //定义一个指针变量,这个变量指向数组
    MyArrayType *pArray;

    //定义一个数组,相当于一级指针
    int myArray2[5];

    //数组指针指向这个数组,相当于二级指针
    pArray = &myArray2;

    for (i = 0; i < 5; i++) {
        (*pArray)[i] = i + 1;
    }

    for (i = 0; i < 5; i++) {
        printf("%d", (*pArray)[i]);
    }
    system("pause");
}
定义数组指针的第二种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
    //定义声明一个数组指针类型
    typedef int(*PArrayType)[5];
    //告诉编译器给我分配一个指针变量
    PArrayType pArray;

    //定义一个数组,相当于一级指针
    int myArray2[5];

    //数组指针指向这个数组,相当于二级指针
    pArray = &myArray2;
    

    int i = 0;

    for (i = 0; i < 5; i++) {
        (*pArray)[i] = i + 1;
    }

    for (i = 0; i < 5; i++) {
        printf("%d", (*pArray)[i]);
    }
    system("pause");
}
定义数组指针的第三种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
    //直接定义一个指向数组的数组指针变量
    int(*PArrayType)[5];

    //定义一个数组,相当于一级指针
    int myArray2[5];

    //数组指针指向这个数组,相当于二级指针
    PArrayType = &myArray2;
    

    int i = 0;

    for (i = 0; i < 5; i++) {
        (*PArrayType)[i] = i + 1;
    }

    for (i = 0; i < 5; i++) {
        printf("%d", (*PArrayType)[i]);
    }
    system("pause");
}
3.内存四区的建立流程
img_f1b44cee9613ef919a9145ef676013ba.png
内存四驱的建立流程.png
4.写一段程序证明栈的生长方向(开口向上还是开口向下)

在栈内存中定义两个变量ab一个数组buf,比较他们的地址。如图所示,如果栈的开口向上,那么先入栈的变量地址会小于后入栈的变量地址,如果开口向下则反之,所以可以根据这个特性来判断,我们打印了ab的地址,结果是a地址大于b,那么可以确定当前环境下,栈开口是向下的,可以默认栈的开口是向下的,这种情况比较多。
同时可以看到我们定义了一个数组,可以看到,虽然栈开口向下,但是数组中元素的存储方式并没有像栈中元素ab一样,你在栈开口向上的情况下试一下会发现,栈的开口不影响数组元素的存储形式,数组存储永远是图中所示的样子


img_730190a6048b68e183809a535d14e6c3.png
栈属性.png
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void main() {
    int a;
    int b;
    printf("&a:%d,&b:%d\n", &a, &b);
    char buf[2] = {'a','b'};
    printf("&buf[0]:%d,&buf[1]:%d\n", &buf[0], &buf[1]);
    system("pause");
}
打印结果
&a:8387696,&b:8387684
&buf[0]:8387672,&buf[1]:8387673
请按任意键继续. . .
5.strcpy函数的5中推演方式
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
移动from和to指针,当from指针到达末尾的时候跳出
*/
void copy1(char *from, char *to) {
    for (; *from != '\0'; from++, to++) {
        *to = *from;
    }
    //在字符串的末尾加上\0,因为拷贝的时候跳过了\0
    *to = '\0';
    return;
}

/*
    把自增运算放在了循环中,++优先级大于*
    但是因为++是放在后边的,所以整体执行顺序还是先
    将*from赋值给*to,然后from和to做自增运算
*/
void copy2(char *from, char *to) {
    for (; *from != '\0';) {
        *to++ = *from++;
    }
    *to = '\0';
    return;
}

/*
    这种写法可以减少手动在末尾添加0的操作,
    (*to = *from) != '\0'这行代码做了两部操作
    第一先赋值,第二判断是否是0,所以当到达末尾
    得的时候,0也被拷贝了
*/
void copy3(char *from, char *to) {
    while ((*to = *from) != '\0') {
        from++;
        to++;
    }
}

/*
   这个操作更加简化,所以操作写在了判断条件中
*/
void copy4(char *from, char *to) {
    while ((*to++ = *from++) != '\0') {
        
    }
}

/*
最简版
*/
void copy5(char *from, char *to) {
    while (*to++ = *from++) {

    }
}
void main() {
    char *from = "hello world";
    char to[100];

    copy5(from, to);

    //C语言打印数据会将数组元素打印出来
    printf("to:%s\n", to);
    system("pause");
}
6.字符串反转的两种方式
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//两头堵模型实现字符串反转
void inverse1(char *str) {
    int length;
    char *start;
    char *end;
    length = strlen(str);
    start = str;
    end = str + length - 1;

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

/*
递归调用实现字符串反转,这一点利用了栈的结构特点,先入后出
不断的将字符串中每一个字符入栈,最后打印每一个字符,由后往前
*/
void inverse2(char *str) {
    if (str == NULL) return;

    //到达字符串末尾,结束递归
    if (*str == '\0') return;

    inverse2(str + 1);
    printf("%c", *str);

}
/*
将反转后的字符串保存到全局变量中
*/

//全局变量被修改,多线程操作中会涉及到线程安全的问题
char buf[100];
void inverse3(char *str) {
    if (str == NULL) return;

    //到达字符串末尾,结束递归
    if (*str == '\0') return;

    inverse3(str + 1);
    strncat(buf, str,1);
}
void main() {
    char a[] = "abcdefg";
    //inverse1(a);
    //printf("%s\n", a);
    //inverse2(a);

    memset(buf, 0, sizeof(buf));
    inverse3(a);
    printf("buf = %s\n", buf);
    system("pause");
}
7.const深入理解
int main(){
  const int a;
  int const b;
  const char *c;
  char * const d;
  const char * const e;
}

1.const修饰常量,前两种情况是相同的,表示常量ab都不能被修改
2.const修饰指针,第三种是一个指向常整型的指针,他所指向的内存数据不能被修改,但是他本身可以被修改,也就是所,可以修改他指向的地址
3.const修饰指针,第四种是一个常量指针,指针变量不能被修改,但是他指向的内存空间可以被修改,不能改变指向,但是值可变
4.const修饰指针,第五种是一个指向常整型的常量指针,指针和他指向的内存空间都不能被修改

8.二级指针做函数参数的作用

通过二级指针做参数的好处是,可以用于修改原一级指针所指向的内存,同时也可以修改原一级指针的指向。那么直接用一级指针做参数进行传递就做不到这两点了吗,答案是,可以做到其中以点,就是操作原一级指针指向的内存,但是无法对原一级指针进行操作

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
    二级指针做函数参数,用于修改传入一级指针的指向
*/
int getMem(char **a1, int *myLen1, char ** a2, int *myLen2) {
    char *temp1 = NULL;
    char *temp2 = NULL;
    temp1 = (char *)malloc(100);
    if (temp1 == NULL) {
        return -1;
    }
    strcpy(temp1, "hello");

    //让传入的二级指针指向的指针指向temp指向的内存空间
    //这行代码的意思:*a1表示p1,让p1指向了temp1指向的空间
    *a1 = temp1;

    temp2 = (char *)malloc(100);
    if (temp2 == NULL) {
        return -1;
    }
    strcpy(temp2, " world");
    *a2 = temp2;

}
/*
 通过二级指针释放一级指针指向的内存
*/
void freeMem(char** temp1, char **temp2) {
    if (temp1 == NULL || temp2 == NULL)
    {
        return;
    }
    free(*temp1);
    *temp1 = NULL;
    free(*temp2);
    *temp2 = NULL;
}

/*
    如果传入一级指针释放内存会怎样?
*/
void freeMem(char* temp1, char *temp2) {
    if (temp1 == NULL || temp2 == NULL)
    {
        return;
    }
    free(temp1);

    //这一步只是将局部变量temp1设置为null,所以并不影响main函数
    //中p1这个变量,所以传递一级指针,无法操作原一级指针变量
    temp1 = NULL;
    free(temp2);

    //这一步只是将局部变量temp2设置为null,所以并不影响main函数
    //中p2这个变量,所以传递一级指针,无法操作原一级指针变量
    temp2 = NULL;
}
void main() {
    char *p1 = NULL;
    int len1 = 0;
    char *p2 = NULL;
    int len2 = 0;
    int ret;
    ret = getMem(&p1, &len1, &p2, &len2);
    printf("%s\n", p1);
    printf("%s\n", p2);
    freeMem(&p1, &p2);
    system("pause");
}
9.二级指针做输入,指向指针数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void printArr(char** temp, int num) {
    int i = 0;
    for (; i < num; i++) {
        //注意,这里*(temp) = *(temp+0) ,二级指针指向
        //指针数组,*(temp+i)表示的是原数组arr中的第i
        //个元素的地址,而我们知道,C语言直接打印数组
        //元素的地址,就会将数组元素打印出来
        printf(" *(temp + %d)  = %s\n", i, *(temp + i));

    }
}

void bubbleSort(char **temp, int num) {

    int i = 0, j = 0;
    for (int i = 0; i<num - 1; i++) {//外层循环控制排序趟数
        for (int j = 0; j<num - 1 - i; j++) {//内层循环控制每一趟排序多少次
            
            if (strcmp(*(temp + j),*(temp + j + 1))>0)
            {
                char *smallest = *(temp + j);
                *(temp + j) = *(temp + j + 1);
                *(temp + j + 1) = smallest;
            }
        }
    }

}

void main() {
    //这是一个指针数组,存放的是是每个元素的地址
    char *arr[] = {"aaaaa","ccccc","ddddd","bbbbb"};
    int num = sizeof(arr) / sizeof(arr[0]);

    printf("数组的长度是:%d\n", num);

    printf("排序之前\n");


    printArr(arr, num);

    bubbleSort(arr, num);
    printf("排序之后\n");
    printArr(arr, num);
    system("pause");
}
img_b1c6d1dccd53b28b27b16c179c9c2a20.png
二级指针做输入第一种形式.png

顺便复习一下冒泡排序

原理:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。

举例说明:要排序数组:int[] arr={6,3,8,2,9,1};

第一趟排序:
    第一次排序:6和3比较,6大于3,交换位置: 3 6 8 2 9 1
    第二次排序:6和8比较,6小于8,不交换位置:3 6 8 2 9 1
    第三次排序:8和2比较,8大于2,交换位置: 3 6 2 8 9 1
    第四次排序:8和9比较,8小于9,不交换位置:3 6 2 8 9 1
    第五次排序:9和1比较:9大于1,交换位置: 3 6 2 8 1 9
    第一趟总共进行了5次比较, 排序结果: 3 6 2 8 1 9


第二趟排序:
    第一次排序:3和6比较,3小于6,不交换位置:3 6 2 8 1 9
    第二次排序:6和2比较,6大于2,交换位置: 3 2 6 8 1 9
    第三次排序:6和8比较,6大于8,不交换位置:3 2 6 8 1 9
    第四次排序:8和1比较,8大于1,交换位置: 3 2 6 1 8 9
    第二趟总共进行了4次比较, 排序结果: 3 2 6 1 8 9


第三趟排序:
    第一次排序:3和2比较,3大于2,交换位置: 2 3 6 1 8 9
    第二次排序:3和6比较,3小于6,不交换位置:2 3 6 1 8 9
    第三次排序:6和1比较,6大于1,交换位置: 2 3 1 6 8 9
    第二趟总共进行了3次比较, 排序结果: 2 3 1 6 8 9


第四趟排序:
    第一次排序:2和3比较,2小于3,不交换位置:2 3 1 6 8 9
    第二次排序:3和1比较,3大于1,交换位置: 2 1 3 6 8 9
    第二趟总共进行了2次比较, 排序结果: 2 1 3 6 8 9


第五趟排序:
    第一次排序:2和1比较,2大于1,交换位置: 1 2 3 6 8 9
    第二趟总共进行了1次比较, 排序结果: 1 2 3 6 8 9


最终结果:1 2 3 6 8 9

由此可见:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数

10.二级指针做输入,第二种形式
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
    int i = 0;
    char **p2 = NULL;
    int num = 5;

    //在堆空间中开辟内存空间,大小是sizeof(char*)*num,
    //用于存储5个char*类型的指针变量
    p2 = (char **)malloc(sizeof(char*)*num);
    //开辟空间之后给每个元素赋值
    for (i = 0; i < num; i++) {
        //给每个char*类型的指针变量开辟指向的内存空间,这个空间
        //用于存放char类型的数据,最大可存100个char
        *(p2+i) = (char*)malloc(sizeof(char) * 100);

        //赋值
        sprintf(*(p2 + i), "存入我的数据%d,", i + 1);
    }
    for (i = 0; i < num; i++) {
        printf("%s\n", *(p2 + i));
    }


    //释放
    for (i = 0; i < num; i++) {
        if (*(p2 + i) != NULL) {
            free(*(p2 + i));
            *(p2 + i) = NULL;
        }
    }
    if (p2 != NULL)
    {
        free(p2);
        p2 = NULL;
    }
    system("pause");
}
void main2() {
    int i = 0;
    char **p2 = NULL;
    int num = 5;

    //在堆空间中开辟内存空间,大小是sizeof(char*)*num,
    //用于存储5个char*类型的指针变量
    p2 = (char **)malloc(sizeof(char*)*num);
    //开辟空间之后给每个元素赋值
    for (i = 0; i < num; i++) {
        //给每个char*类型的指针变量开辟指向的内存空间,这个空间
        //用于存放char类型的数据,最大可存100个char
        p2[i] = (char*)malloc(sizeof(char) * 100);

        //赋值
        sprintf(p2[i], "存入我的数据%d,", i + 1);
    }
    for (i = 0; i < num; i++) {
        printf("%s\n", p2[i]);
    }


    //释放
    for (i = 0; i < num; i++) {
        if(p2[i] != NULL) {
            free(p2[i]);
            p2[i] = NULL;
        }
    }
    if (p2 != NULL)
    {
        free(p2);
        p2 = NULL;
    }
    system("pause");
}
img_878961237102be64bd53429348dad22f.png
二级指针第二种形式.png
11.二级指针操作字符串练习

切割字符串并存储在堆内存中,然后打印切割结果
char *p1 = "abcdef,acccd,eeee,aaaa,e3eeeee,sssss,ssccccccc,";

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int splitString(const char *buf1, char c, char** myp, int *count) {
    char *p = NULL, *ptmp = NULL;
    int tmpcount = 0;

    //两个指针都指向了字符串指针
    p = buf1;
    ptmp = buf1;

    do {
        //从p中找char c
        p = strchr(p, c);
        if (p != NULL) {
            //大于0说明指针已经有移动位置,新的字符串和旧的不是同一个,
            //如果字符串第一个字符就找到了符合条件的,那么p-pmt = 0
            if (p - ptmp > 0) {
                //找到一个逗号后,将逗号之前的字符串拷贝到二级指针指向的空间中
                strncpy(*(myp + tmpcount), ptmp, p - ptmp);
                //在截出来的字符串后边添加字符串结束符\0
                *(*(myp + tmpcount) + (p - ptmp)) = '\0';
                tmpcount++;
                ptmp = p = p + 1;
                /*strncpy(myp[tmpcount], ptmp, p - ptmp);
                myp[tmpcount][p - ptmp] = '\0';
                tmpcount++;
                ptmp = p = p + 1;*/
            }
        }
        else {
            break;
        }
    } while (*p != '\0');
    *count = tmpcount;
    return *count;
}
void main() {
    int ret = 0, i = 0;
    char *p1 = "abcdef,acccd,eeee,aaaa,e3eeeee,sssss,ssccccccc,";
    char cTemp = ',';
    int count;

    //开辟空间
    char **p = NULL;
    p = (char **)malloc(10 * sizeof(char *));
    if (p == NULL) {
        return;
    }
    for (i = 0; i < 10; i++) {
        *(p + i) = (char *)malloc(sizeof(char)*30);
        //p[i] = (char *)malloc(sizeof(char) * 30);
    }

    ret = splitString(p1, cTemp, p, &count);

    printf("得到了%d个元素\n", count);
    if (ret == 0)
    {
        printf("error\n");
    }

    for (i = 0; i < count; i++) {
        printf("%s\n", *(p + i));
    }


    //回收内存
    for (i = 0; i < 10; i++) {
        if (*(p + i) != NULL) {
            free(*(p + i));
            *(p + i) = NULL;
        }
    }
    if (p != NULL)
    {
        free(p);
        p = NULL;
    }

    //释放
    /*for (i = 0; i < num; i++) {
        if (p2[i] != NULL) {
            free(p2[i]);
            p2[i] = NULL;
        }
    }
    if (p2 != NULL)
    {
        free(p2);
        p2 = NULL;
    }*/
    system("pause");
}
12.定义数组指针的三种方式

1)通过数组类型定义数组指针
typedef int(ArrayType)[5];
ArrarType *pointer;

  1. 声明一个数组指针类型
    typedef int (*MyPointer)[5];
    MyPointer myPoint;
  2. 直接定义
    int(*pointer)[n];
    ponter为数组指针变量名
    type (int)为指向的数组的类型
    n 为指向的数组的大小
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
    //直接定义一个指向数组的数组指针变量
    int(*PArrayType)[5];

    //定义一个数组,相当于一级指针
    int myArray2[5];

    //数组指针指向这个数组,相当于二级指针
    PArrayType = &myArray2;
    

    int i = 0;

    for (i = 0; i < 5; i++) {
        (*PArrayType)[i] = i + 1;
    }

    for (i = 0; i < 5; i++) {
        printf("%d", (*PArrayType)[i]);
    }
    system("pause");
}
void main2() {
    char *MyArray[] = {"1111","2222","3333"};
    //数组指针,一个指针,指向了数组

    //定义了一个数组数据类型
    typedef int(MyArrayType)[5];

    int i = 0;

    //用类型定义变量,相当于int myArray[5]
    MyArrayType myArray;

    //定义一个指针变量,这个变量指向数组
    MyArrayType *pArray;

    //定义一个数组,相当于一级指针
    int myArray2[5];

    //数组指针指向这个数组,相当于二级指针
    pArray = &myArray2;

    for (i = 0; i < 5; i++) {
        (*pArray)[i] = i + 1;
    }

    for (i = 0; i < 5; i++) {
        printf("%d", (*pArray)[i]);
    }
    system("pause");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值