C语言笔记:指针与数组

news2024/11/18 21:41:40

 ACM金牌带你零基础直达C语言精通-课程资料

 本笔记属于船说系列课程之一,课程链接:ACM金牌带你零基础直达C语言精通icon-default.png?t=N7T8https://www.bilibili.com/cheese/play/ep159068?csource=private_space_class_null&spm_id_from=333.999.0.0

你也可以选择购买『船说系列课程-年度会员』产品『船票』,畅享一年内无限制学习已上线的所有船说系列课程:船票购买入口icon-default.png?t=N7T8https://www.bilibili.com/cheese/pages/packageCourseDetail?productId=598

做题网站OJ:HZOJ - Online Judge

Leetcode :力扣 (LeetCode) 全球极客挚爱的技术成长平台

1.地址

        (重点)可以发现,对应字节的地址,他是连续起来的;所以可以之后得到了,变量的起始地址,也可以获得后面字节的地址;

代码理解:

#include<stdio.h>



int main() {
    int a = 1;
    //%p是地址的格式占位符
    //&a就是对变量a进行取地址
    //取的地址也是对应字节的起始地址
    printf("&a = %p\n", &a);

    return 0;
}

执行结果:

这里的结果一般来说是不一样的,但是只要是0x开头的16进制就说明没有问题

16进制

在程序中十六进制的表示,以及使用:
 

#include <stdio.h>
#include <limits.h>
int main() {
    //如果要用十六进制对变量赋值
    //需要在赋值前加上0x
    int a = 0x6f;
    //打印a变量对应10进制的值
    printf("a(10) = %d\n", a);
    //打印a变量对应16进制的值
    //%x是十六进制对应的格式占位符
    printf("a(16) = %x\n", a);
    //打印a变量对应16进制的值
    //%X是十六进制对应的格式占位符
    //但是对应小写和大写的作用是
    //打印时十六进制不是会有字母吗
    //然后这里的大小写对应就是把那些字母规定打印时是小写还是大写
    printf("a(16) = %X\n", a);
    //用16进制表示整形最大值和最小值
    printf("MAX_INT = %d\n", INT_MAX);
    printf("MIN_INT = %d\n", INT_MIN);
    int max_int = 0x7fffffff;
    int min_int = 0x80000000;
    printf("max_int = %d\n", max_int);
    printf("min_int = %d\n", min_int);
    return 0;
}

执行结果:

对应的最大值2进制转换的十六进制0x7fffffff如何来的:

通过这张图,也可以推出0x80000000如何来的

地址是一个几位二进制数据:

 先看一段程序:

#include<stdio.h>


int main() {
    int a;
    double b;
    char c;
    //获取每个类型的地址长度
    //sizeof获取参数的对应字节长度
    printf("sizeof(int &) = %lu\n", sizeof(&a));
    printf("sizeof(double &) = %lu\n", sizeof(&b));
    printf("sizeof(char &) = %lu\n", sizeof(&c));
    return 0;
}

执行结果:

可以发现每个类型对应的地址都是8位,那么一定就是每个类型对应的地址都是8位吗?

来看32位和64位系统:

通过对32位和64系统的理解后,可以发现地址不应的是8位的,要看操作系统是多少位有关系的。

2.数组

定义和使用:

程序中如何使用数组,以及概念对应的代码:

#include<stdio.h>

//数组定义和使用
void test1() {
    int a[5] = {0};
    for (int i = 0; i < 5; i++) {
        //a[i] 就可以访问数组对应下标为i的位置
        //也可以进行赋值
        a[i] = 2 * i;
    }
    for (int i = 0; i < 5; i++) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return ;
}

//动态数组的定义和使用
void test2() {
    //通过执行程序时来定义数组想要的大小
    int n;
    scanf("%d", &n);
    //动态数组是无法初始化的
    int a[2 * n];
    for (int i = 0; i < 2 * n; i++) {
        a[i] = i * i;
    }
    for (int i = 0; i < 2 * n; i++) {
        printf("a[%d] = %d\n", i, a[i]) ;
    }
    return ;
}
//初始化进行对数组赋值
void test3() {
    // int a[5] = {0}; 这种初始化是将每个位置初始化为0
    int a[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)  {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return ;
}
//数组定义时不在[]中输入大小
void test4() {
    int a[] = {1, 2, 3, 4, 5, 6, 7};
    //sizeof(a)获取数组字节大小
    //sizeof(int)获取int类型字节大小
    //相除就可以得到数组元素的个数
    int len = sizeof(a) / sizeof(int);
    printf("sizeof(a) / sizeof(int) = %d\n", len);
    for (int i = 0; i < len; i++) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return ;
}
//数组的地址理解
void test5() {
    int a[5] = {1, 2, 3, 4, 5};
    printf("a = %p\n", a);
    for (int i = 0; i < 5; i++) {
        printf("&a[%d] = %p\n", i, &a[i]);
    }
    return ;
}

int main() {
    printf("test1()\n");
    test1();
    printf("test2()\n");
    test2();
    printf("test3()\n");
    test3();
    printf("test4()\n");
    test4();
    printf("test5()\n");
    test5();
    return 0;
}

执行结果:

通过test5()发现数组的地址也不是连续的啊,但是对于数组a的类型是整形,整形对应的字节是4字节,对于变量的地址就是他的起始地址,所以相差为4刚好满足连续条件,所以数组的元素是连续的。

素数筛:

代码实现:


#include<stdio.h>

int prime[100] = {0};

void init_prime(int n) {
    //初始化0和1不是素数
    prime[0] = prime[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (prime[i]) continue;//如果被标记了,说明不是素数,就不用再去标记
        for (int j = 2 * i; j <= n; j += i) {
            prime[j] = 1;//标记当前数为合数
        }
    }
    return ;
}


int main() {
    //求1-n的素数
    int n;
    printf("input n(0 < n && n <= 100) :");
    scanf("%d", &n);
    init_prime(n);
    for (int i = 0; i <= n; i++) {
        if (prime[i]) continue;//被标记说明i是合数不打印
        printf("%d\n", i);
    }
    return 0;
}

执行结果:

优化后代码:

#include<stdio.h>

int prime[100] = {0};

void init_prime(int n) {
    prime[0] = prime[1] = 1;
    //优化1
    //证明:假如 n = a * b,a和b中一定有一个是小于等于根号下n的
    //那么n也被小于等于根号n的素数给标记了
    //举个例子100,根号下100就是10
    //那么10下面的2,就已经把100标记了
    for (int i = 2; i * i <= n; i++) {
        if (prime[i]) continue;
        //优化2
        //证明: j = a * i, 当a < i时,j 已经被a这个素数标记了
        //那么这里用a的话,就重复标记了
        //例子a = 2
        //j = 2 * i
        //当i = 2标记了6,循环是 j += i,标记顺序是4,6,8
        //当i = 3时又标记了6,循环是 j += i,标记顺序是6, 9,12
        for (int j = i * i; j <= n; j += i) {
            prime[j] = 1;//标记当前数为合数
        }
    }
    return ;
}


int main() {
    //求1-n的素数
    int n;
    printf("input n(0 < n && n <= 100) :");
    scanf("%d", &n);
    init_prime(n);
    for (int i = 0; i <= n; i++) {
        if (prime[i]) continue;//被标记说明i是合数不打印
        printf("%d\n", i);
    }
    return 0;
}

执行结果:

这里为什么要去优化,因为这样循环的次数减少,那么程序的执行时间就会更少。在后续的算法与数据结构课中就是学习这个的重点.

二分查找算法:

二分查找是通过移动头指针和尾指针来进行查找的,当查找值确定不在另一个区域时,直接将头或尾指针进行移动来排除那块区域,进行下一次查找,而每次的位置就是头尾指针的中间,最后终止条件就是头指针的位置大于等于尾指针.

并且在使用二分查找算法时,一定要满足单调的条件!

代码演示:

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

//顺序查找
void find_1(int arr[], int x) {
    int cnt1 = 0;
    int flag1 = 0;
    for (int i = 0; i < 10; i++) {
        cnt1 += 1;
        if (arr[i] != x) continue;
        flag1 = 1;
        break;
    }
    if (flag1) {
        printf("Ok, find numbe, cnt = %d\n", cnt1);
    } else {
        printf("Not find cnt = %d\n", cnt1);
    }
}

//二分查找
void binary_search(int arr[], int n, int x) {
    int flag = 0;
    int cnt1 = 0;
    int head = 0, tail = n - 1;//定义头尾指针;
    while (head <= tail) {//当尾指针小于头指针时说明,没有找到结束循环
        cnt1++;
        int mid = (head + tail) >> 1;//mid在头尾指针的中间
        //数组是单调递增
        //中间的位置都大于x说明在后半区域没有x,所以去掉
        if (arr[mid] > x) tail = mid - 1;//因为mid位置也不等于x,可以把mid位置去掉,也就是-1
        //中间的位置都小于x说明在前半区域没有x,所以去掉
        else if (arr[mid] < x) head = mid + 1;
        else {
            //说明找到了
            flag = 1;
            break;
        }
    }
    if (flag) {
        printf("Ok, find numbe, cnt = %d\n", cnt1);
    } else {
        printf("Not find cnt = %d\n", cnt1);
    }
    return ; 
}

int main() {
    //产生一个随机种子
    srand(time(0));
    int arr[10] = {0};
    for (int i = 1; i < 10; i++) {
    //rand()获取一个随机数
        arr[i] = arr[i - 1] + (rand() % 10);
    }
    for (int i = 0; i < 10; i++) {
        i && printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    int x;
    scanf("%d", &x);
    find_1(arr, x);
    binary_search(arr, 10, x);
    return 0;
}

 执行结果:

cnt表示查找次数,根据查找次数来看,顺序查找对比二分查找的效率要低.

多维数组:

代码演示:

#include<stdio.h>


int main() {
    int b[3][4];
    int cnt = 1;
    for (int i = 0; i < 3; i++) {//对行进行循环
        for (int j = 0; j < 4; j++) {//对列进行循环
            b[i][j] = cnt++;
        }
    }
    for (int i = 0; i < 3; i++) {//对行进行循环
        for (int j = 0; j < 4; j++) {//对列进行循环
            j && printf(" ");
            printf("%3d", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}

执行结果:

字符数组:

重点:字符串是通过\0结尾!!

代码讲解:

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


int main() {
    char str1[10] = "abc";//只能在定义字符时使用
    printf("str1 = %s\n", str1);
    //str1 = "def"这样是不行的
    strcpy(str1, "def");
    printf("str1 = %s\n", str1);
    char str2[] = "hello\0 world";
    //用strlen判断str2的长度
    //通过执行结果发现,strlen是统计到\0字符为止
    //打印字符串也打印到\0字符结束
    printf("strlen(str2) = %lu\n", strlen(str2));
    printf("str2 = %s\n", str2) ;
    //通过发现\0也占用位置,str2的长度为13位
    printf("sizeof(str2) = %lu\n", sizeof(str2));
    str2[5] = 'a';
    printf("str2 = %s\n", str2) ;//通过修改后发现,没有\0,可以打印完str2
    char str3[] = "abcdef", str4[] = "abc";
    //执行结果为100
    //因为strcmp通过比较,str3和str4发现前3位相同
    //第4位分别为'd'和'\0', 不相同返回的就是'd' - '\0'的值
    //'d'对应的ASCLL值是100,\0对应的是0,那么结果就是100
    printf("strcmp(str3, str4) = %d\n", strcmp(str3, str4));
    str3[3] = '\0';// abc\0ef
    //通过这个比较发现,strcmp比较也是在\0结束
    printf("strcmp(str3, str4) = %d\n", strcmp(str3, str4));

    int arr[10];
    for (int i = 0; i < 10; i++) {
        arr[i] = i * i;
    }
    for (int i = 0; i < 10 ; i++) {
        i && printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    //第一个参数放置地址
    //第二个参数放置初始化的值
    //第三个参数放置数组的字节大小
    memset(arr, 0, sizeof(int) * 10);
    for (int i = 0; i < 10 ; i++) {
        i && printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    //全赋值为-1
    memset(arr, -1, sizeof(int) * 10);
    for (int i = 0; i < 10 ; i++) {
        i && printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    //全赋值为2
    memset(arr, 2, sizeof(int) * 10);
    for (int i = 0; i < 10 ; i++) {
        i && printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    //通过执行发现,结果和预想的不一样,因为memset是通过对每个字节赋值
    //对一个元素进行分析用二进制表示,int 类型有4字节,一个字节8位
    //00000010 00000010 00000010 00000010
    //拿结果就是2 + 2 ^ 9 + 2 ^ 17 + 2 ^ 25 = 33686018
    //和输出的结果是一样的
    return 0;
}

执行结果:

指针

1.指针变量也是变量

指针p里面存储的值是变量a的地址,但是指针p也有自己的地址;

对于指针p进行取值,对应的值就是变量a的值;

进行代码讲解:

#include<stdio.h>



int main() {
    int *p1;
    double *p2;
    char *p3;
    int a = 123;
    double b = 45.6;
    char c = 'h';
    //p1中存存储着a的地址
    //例如只要有地址,你不会找不到这个位置
    //可以理解位p1指向a
    p1 = &a;
    p2 = &b;
    p3 = &c;
    //查看指针是否指向对应的地址
    printf("p1 = %p, &a = %p\n", p1, &a);
    printf("p2 = %p, &b = %p\n", p2, &b);
    printf("p3 = %p, &c = %p\n", p3, &c);
    //查看指针变量的地址
    printf("&p1 = %p\n", &p1);
    printf("&p2 = %p\n", &p2);
    printf("&p3 = %p\n", &p3);
    //取值运算符
    //是取对应指针里面存储的地址里面所存储的值
    printf("*p1 = %d\n", *p1);
    printf("*p2 = %lf\n", *p2);
    printf("*p3 = %c\n", *p3);
    return 0;
}

代码执行结果:

另一份代码,指针的3种使用:

1.将相应的实参的地址进行传入,想修改参数的值

2.在设计程序时,需要传出参数时,也就是运行完调用函数时,参数得到传出

3.指针可以用来接受数组

#include<stdio.h>

void add_once(int x) {
    x += 1;
    return ;
}

//这里参数是指针
void add_two(int *p) {
    *p += 1;//那么这里进制修改的是指针指向地址的值,那么传出函数地址里的值也是改变后的 
    return ;
}

//当需要函数进行对参数传出时,可以使用指针来进行实现
void f(int n, int *sum_addr) {
    *sum_addr = (1 + n) * n / 2;
    return ;
}

//在这p可以当作数组来使用
void output(int *p, int n) {
    //[]表示运算符,p[i] = *(p + i);
    for (int i = 0; i < n; i++) {
        printf("p[%d] = %d\n", i, p[i]);
    }
    return ;
}

int main() {
    int a = 123;
    //这里用到了实参和形参
    //形参的改变是不会影响实参的值
    printf("a = %d\n", a);
    add_once(a);
    printf("a = %d\n", a);
    //这里用到了取地址
    //参数传入的是地址
    //因为指针存储的值是地址
    add_two(&a);
    printf("a = %d\n", a);

    //传出参数
    int b = 10, sum;
    f(b, &sum);
    printf("sum = %d\n", sum);

    int arr[10] = {9, 8, 5, 3, 6, 2, 1, 0, 4, 7};
    //arr数组名表示数组的首地址
    output(arr, 10);
    return 0;
}

2.理解p + 1(p为指针)

在程序中理解一下:

#include<stdio.h>


int main() {
    //1
    int a, *p1 = &a;
    double b, *p2 = &b;
    //可以发现地址+1,是根据类型对应字节大小进行+的
    printf("&a = %p\n", &a);
    printf("p1 + 0 = %p\n", p1 + 1);
    printf("p1 + 1 = %p\n", p1 + 2);
    printf("p1 + 2 = %p\n", p1 + 3);
    printf("p1 + 3 = %p\n", p1 + 4);
    printf("&b = %p\n", &b);
    printf("p2 + 0 = %p\n", p2 + 1);
    printf("p2 + 1 = %p\n", p2 + 2);
    printf("p2 + 2 = %p\n", p2 + 3);
    printf("-------------------\n");
    //2
    int arr[4] = {1, 2, 3, 4};
    int *p3 = arr;
    for (int i = 0; i < 4; i++) {
        //同通过测试发现,p3 + i 和 &arr[i] + i的地址一样
        printf("p3 + %d = %p\n", i, p3 + i);
        printf("&arr[0] + %d = %p\n", i, &arr[i] + i);
        //通过测试会发现,p3[i]和*(p3 + 1)效果一样
        printf("p3[%d] = %d\n", i, p3[i]);
        printf("*(p3 + %d) = %d\n", i, *(p3 + i));
    }
    printf("-------------------\n");
    //3
    int *p4[10];//指针数组, 每个位置可以存储一个地址
    int arr1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = 0; i < 10; i++) {
        p4[i] = &arr1[i];
        printf("*p[%d] = %d, arr1[%d] = %d\n", i, *p4[i], i, arr1[i]);
    }
    printf("-------------------\n");
    //4 
    int (*p5)[10] = 0x0;//表示p4指向10个整形元素的指针
    int arr2[30][10];//arr[i]代表10个整形元素
    p5 = arr2;//p4 + 1也相当于跳10个整形元素,就相当于p4 + 4 == arr[1];
    printf("p5 + 0 = %p\n", p5 + 0);
    printf("p5 + 1 = %p\n", p5 + 1);
    printf("p5 + 2 = %p\n", p5 + 2);
    return 0;
}

执行结果:

1.可以发现,对应指针+1,那么地址就会加上相应类型字节大小,int 就+4,double 就+8

2.可以发现指针和数组首地址相加是一样的,并且p[i]和*(p + i)的结果也是一样的

3.通过测试发现,指针数组每个位置都可以存地址,并且使用和正常数组一样

4.int (*p)[10] 是指针p指向10个整形元素大小的指针,如果发生偏移的10个整形的字节,也就是40

3.理解*p(p为指针)

假如这里的type就是int 类型,那么指针p取值的字节长度就是橙色部分对应的值,那么现在将指针p转换为char类型的指针,在对p进行取值,那么就只取1字节,就是橙色部分的1/4,那对应的值;

例子:

代码解释:

#include<stdio.h>



int main() {
    int n = 0x61626364;
    int *int_p = &n;
    char *p = (char *)&n;
    printf("*int_p = %d\n", *int_p);
    printf("*(p + 0) = %c\n", *(p + 0));
    printf("*(p + 1) = %c\n", *(p + 1));
    printf("*(p + 2) = %c\n", *(p + 2));
    printf("*(p + 3) = %c\n", *(p + 3));
    return 0;
}

        图中是变量n的二进制存储方式,如果指针是int类型就取4个字节, 如果指针为char类型那么取值就一个字节一个字节的取;为什么阅读顺序和电脑顺序不同,这就是操作系统的知识,由于现在的计算机大部分都是小端系统,数字的地位存储在内存的低位,就是比如数字地位在阅读时是64,那么就存在内存的地位,也就是指针的首地址,然后依次往后存储;

        也可以这样来理解,数字的地位在我们阅读时是从左向右,高位在左的,比如123,1是高位;而计算中读取方式是321这样;

代码执行结果:

        根据执行结果来看,可以得到内存的地位存储的是数字的地位, 也可以证明根据指针取值是根据类型进行取值的,什么类型取什么类型对应字节大小;

例子:

代码实现:

#include<stdio.h>


int main() {
    int a, b, c, d;
    scanf("%d.%d.%d.%d", &a, &b, &c, &d);
    //unsigned int代表无符号整形,那么就没有符号位了
    unsigned int n;
    //由于n是int类型,所以对他的地址需要强转为char *类型
    char *p = (char *)&n; 
    p[3] = a;
    p[2] = b;
    p[1] = c;
    p[0] = d;
    //%u为无符号整形的占位符
    printf("n = %u\n", n);
    return 0;
}

执行结果:

4.指针的几种等价形式(重要):

 等价形式转换:

1-5每条形式中,都是等价互换的,如果理解不了其他的形式,可以记住自己能理解的

下面是代码演示:

#include<stdio.h>



int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7};
    int *p = arr;
    for (int i = 0; i < 3; i++) {
        //(i + 5)[&p[1] - 2]
        //第一次转换(i + 5)[p + 1 - 2]
        //第二次转换*(i + 5 + p + 1 -2), 这里用到了[]运算符规则
        //最终结果p[i + 4];
        //所以下面代码等价于printf("%d\n", (i + 5)[&p[1] - 2]);
        printf("%d\n", (i + 5)[&p[1] - 2]);
        //所以最终打印的是p[4], p[5], p[6]的值;
    }
    return 0;
}

执行结果:

5.数组指针与函数指针

指针理解:

两句话理解指针:

数组指针:

第四种就是容易搞错的,这个指针指向的是有10个int元素的指针,不是指针数组;

函数指针:

        这里的*写在函数名前面,并且要加上小括号;

        根据上面的定义得到了一个函数类型的指针,add就是指针,那么它指向的类型是返回值为int,并且两个参数都是int类型的函数;

代码演示:

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

void test1() {
    printf("function test1\n");
    return ;
}

void test2() {
    printf("function test2\n");
    return ;
}

void test3() {
    printf("function test3\n");
    return ;
}
//这里的函数中指针p,指向的就是返回值为void,参数列表为空的函数
void (*p)();

int main() {
    srand(time(0));//产生随机种子
    p = test1;//让指针指向test1
    p();
    p = test2;
    p();
    p = test3;
    p();
    printf("---------------\n");
    //定义一个函数指针数组,初始化0,1,2位置分别为test1,test2,test3
    void (*arr[3])() = {test1, test2, test3};
    for (int i = 0; i < 10; i++) {
        //rand() 取随机数,%3取随机数0,1,2
        arr[rand() % 3]();
    }
    return 0;
}

6.内存管理方法(常用)

下面是对每个函数的使用:

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

void get_value(int *arr) {
    for (int i = 0; i < 10; i++) {
        arr[i] = rand() % 100;
    }
    return ;
}

void output(int *arr, int n) {
    for (int i = 0; i < 10; i++) {
        if (i == 5) printf("\n");
        else i && printf(" ");
        printf("arr%d[%d] = %.2d", n, i, arr[i]);
    }
    printf("\n-------------------------\n");
    return ;
}

int main() {
    srand(time(0));
    //利用malloc开辟40个字节大小的空间,int为4字节,乘10那就是40个字节大小
    //由于malloc返回的是void *类型的地址然后
    //需要强转为int *类型的地址
    //让后赋值给一个指针变量
    //然后指针变量就可以当作拥有10个int类型的元素进行使用
    //也就是int类型的数组,然后大小为10
    int *arr1 = (int *)malloc(sizeof(int) * 10);
    get_value(arr1);
    output(arr1, 1);
    //使用方法和malloc差不多
    //但是calloc会将每个字节初始化为0
    int *arr2 = (int *)calloc(sizeof(int), 10);
    output(arr2, 2);
    get_value(arr2);
    output(arr2, 2);
    //因为你是在堆区借用的一块区域
    //所以在不使用后需要还回去
    //因为你不还回去,系统就一直以为你在占用这块区域
    //所以在使用后必须要换回
    free(arr1);
    free(arr2);

    char s1[100] = "hello world";
    char s2[100];
    char s3[100];
    //第一个参数目标地址
    //第二个参数被copy地址
    //第三个参数copy字节数量
    //为什么要多一个字节,因为字符串还有一个\0
    memcpy(s2, s1, 12);
    memmove(s3, s1, 12);
    printf("s2 = %s\n", s2);
    printf("s3 = %s\n", s3);
    //这里需要使用到copy功能,建议用memmove
    //因为在copy时内存重叠,memcpy可能会造成错误
    //而memmove会避免这种情况,所以建议使用memmove
    memcpy(s2 + 4, s2, 12);
    memmove(s3 + 4, s3, 12);
    printf("s2 = %s\n", s2);
    printf("s3 = %s\n", s3);
    return 0;
}

运行结果:

7.const常量

用const定义变量后,变量就是常量了,然后变量在定义时初始化的值之后是不能被改变的;

代码解释:

#include<stdio.h>


int main() {
    const int a = 123;
    const int b = 456;
    const int *p1 = &a;
    //这样的定义方式是p1是一个指针指向整形常量
    //const是指向a变量的
    //意思就是p1指向的空间的值是不能改变的
    //但是p1的值是可以改变的
    //*p1 = 1;这局代码就是不可以的
    printf("*p1 = %d\n", *p1);
    p1 = &b;
    printf("*p1 = %d\n", *p1);
    //这样定义方式是p2是一个指针指向常量整形
    //可以改变p2的值,但是p2指向区域里存储的值是不能改变
    int const *p2 = &a;
    printf("*p2 = %d\n", *p2);
    p2 = &b;
    printf("*p2 = %d\n", *p2);
    int n = 789, m = 1000;
    //p3是一个常量指针指向整形
    //可以改变p3指向区域里存储的值
    //但是p3的值是不能改变的
    //比如p3 = &m,就不可以
    int *const p3 = &n;
    printf("*p3 = %d\n", *p3);
    *p3 = 1000;
    printf("*p3 = %d\n", *p3);
    return 0;
}

执行结果:

8.typedef:将变量名变成类型别名

蓝色是typedef关键字,然后绿色是类型,红色是变量别名

代码理解:

#include<stdio.h>

//此处我将long long附上一个别名ll
//那么我在这行代码下面定义long long 类型就可以使用ll来定义
typedef long long ll;
//Arr2Dim10是类型名
typedef int (*Arr2Dim10)[10];
//Fucn就是类型名
typedef void (*Func)();

void add() {
    int a = 1, b = 1;
    printf("a + b = %d\n", a + b);
    return ;
}

int main() {
    //用ll定义一个long long 类型的变量
    ll a = 123; 
    //用sizeof测量是否定义的是long long 类型
    printf("sizeof(a) = %lu\n", sizeof(a));
    int arr[5][10];
    arr[0][1] = 1231;
    //这里定义了p就是数组指针
    //用p指向arr二维数组
    Arr2Dim10 p = arr;
    printf("p[0][1] = %d\n", p[0][1]);
    //这里定义了Func类型定义了一个函数指针,指向了add函数
    Func p2 = add;
    p2();
    return 0;
}

执行结果:

本章小结:

        对于地址理解到他在计算机中的作用,以及是如何对应变量的;然后是理解数组的定义和使用,以及多维数组和字符串的使用,还有字符串的细节问题;最后理解指针是如何使用和定义,以及对于指针的各种方式是如何去理解。结合代码带去自己的疑问然后去实现,看代码实现的结果和你的想法有多少差异,这样对于疑问可以加深映像。

        最后有些很多问题在笔记中没有写出来,只有百分之70左右的内容在笔记上,对于视频中的一些问题,可以在自己的笔记上添加。

        加油,看到这里你已经超过百分之95的人了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1541435.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过dbeaver链接dm8数据库

一、环境说明 windows 11 vmware 17 ubuntu 22 tt:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.3 LTS Release: 22.04 Codename: jammytt:~$ docker info Client:Version: 24.0.5Context: d…

【进程概念】环境变量PATH(结合代码,超极容易理解)

文章目录 什么叫环境变量常见环境变量查看环境变量测试PATH修改PATH HOME和环境变量相关的命令环境变量是如何被组织的解释main函数里的参数如何在程序中获得环境变量1.命令行第三个参数2.通过第三方变量environ获取 通过系统调用putenv和getenv获取或设置环境变量环境变量通常…

带3090显卡的Linux服务器上部署SDWebui

背景 一直在研究文生图&#xff0c;之前一直是用原始模型和diffuser跑SD模型&#xff0c;近来看到不少比较博主在用 SDWebui&#xff0c;于是想着在Linux服务器上部署体验一下&#xff0c;谁知道并没有想象的那么顺利&#xff0c;还是踩了不少坑。记录一下过程&#xff0c;也许…

画架构图实践技巧

画架构图是架构师的一门必修功课。 对于架构图是什么这个问题&#xff0c;我们可以按以下等式进行概括&#xff1a; 架构图 架构的表达 架构在不同抽象角度和不同抽象层次的表达&#xff0c;这是一个自然而然的过程。 不是先有图再有业务流程、系统设计和领域模型等&#…

ForkJoinPool在生产环境中使用遇到的一个问题

1、背景 在我们的项目中有这么一个场景&#xff0c;需要消费kafka中的消息&#xff0c;并生成对应的工单数据。早些时候程序运行的好好的&#xff0c;但是有一天&#xff0c;我们升级了容器的配置&#xff0c;结果导致部分消息无法消费。而消费者的代码是使用CompletableFutur…

P8623 [蓝桥杯 2015 省 B] 移动距离 Python

[蓝桥杯 2015 省 B] 移动距离 题目描述 X 星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为 $1,2,3, \cdots $ 。 当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。 比如&#xff1a;当小区排号宽度为 6 6 6 时&#xff0c;开始情形如…

echarts 折线图 数据点过密,显示重叠该如何解决

echarts 折线图 数据点过密&#xff0c;显示重叠该如何解决 有这样一个图表&#xff0c;显示的数据比较多&#xff0c; 当把 label 显示出来的时候&#xff0c;这些 label 重叠了&#xff0c;我想让它间隔一下。 结果是这样的&#xff1a; 只需要在 label.formatter 上处理 …

Go 限流器-漏桶 VS 令牌桶 常用包原理解析

本文主要介绍两个包Uber漏桶&#xff0c;time/rate令牌桶 可以了解到&#xff1a; 使用方法漏桶/令牌桶 两种限流思想 and 实现原理区别及适用场景应用Case 背景 我们为了保护系统资源&#xff0c;防止过载&#xff0c;常常会使用限流器。 使用场景&#xff1a; API速率限制…

clickhouse突然启动不起来问题排查

场景&#xff1a; 在实现postgreql数据迁移到clickhouse中&#xff0c;想使用MaterializedPostgreSQL的功能实现&#xff0c;但是中途clickhouse突然挂了&#xff0c;就再启动不了了。 现象&#xff1a; systemctl start clcikhouse-server启动报错 [rootlocalhost clickhous…

spring注解驱动系列--AOP探究二

上篇中记录了AnnotationAwareAspectJAutoProxyCreator的创建以及注册&#xff0c;主要是 1、EnableAspectJAutoProxy 注解会开启AOP功能 2、然后这个注解会往容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件。 3、之后在容器创建过程中&#xff0c;注册后置处理器&a…

反序列化漏洞简单知识

目录&#xff1a; 一、概念&#xff1a; 二、反序列化漏洞原因 三、序列化漏洞的魔术方法&#xff1a; 四、反序列化漏洞防御&#xff1a; 一、概念&#xff1a; 序列化&#xff1a; Web服务器将HttpSession对象保存到文件系统或数据库中&#xff0c;需要采用序列化的…

基于java+springboot+vue实现的研究生志愿填报辅助系统(文末源码+Lw+ppt)23-600

摘 要 二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这…

java 实现发送邮件功能

今天分享一篇 java 发送 QQ 邮件的功能 环境&#xff1a; jdk 1.8 springboot 2.6.3 maven 3.9.6 邮件功能依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>&…

深度学习pytorch——多层感知机反向传播(持续更新)

在讲解多层感知机反向传播之前&#xff0c;先来回顾一下多输出感知机的问题&#xff0c;下图是一个多输出感知机模型&#xff1a; 课时44 反向传播算法-1_哔哩哔哩_bilibili 根据上一次的分析深度学习pytorch——感知机&#xff08;Perceptron&#xff09;&#xff08;持续更新…

pytorch 实现多层神经网络MLP(Pytorch 05)

一 多层感知机 最简单的深度网络称为多层感知机。多层感知机由 多层神经元 组成&#xff0c;每一层与它的上一层相连&#xff0c;从中接收输入&#xff1b;同时每一层也与它的下一层相连&#xff0c;影响当前层的神经元。 softmax 实现了 如何处理数据&#xff0c;如何将 输出…

【Godot4.2】基础知识 - Godot中的2D向量

概述 在Godot中&#xff0c;乃至一切游戏编程中&#xff0c;你应该都躲不开向量。这是每一个初学者都应该知道和掌握的内容&#xff0c;否则你将很难理解和实现某些其实原理非常简单的东西。 估计很多刚入坑Godot的小伙伴和我一样&#xff0c;不一定是计算机专业或编程相关专…

WSL使用

WSL使用 WSL安装和使用 Termianl和Ubuntu的安装 打开Hype-V虚拟化配置Microsoft Store中搜索Window Terminal并安装Microsoft Store中搜索Ubuntu, 选择安装Ubuntu 22.04.3 LTS版本打开Window Terminal选择Ubuntu标签栏, 进入命令行 中文输入法安装 查看是否安装了fcitx框架…

2023第13届上海生物发酵展8月7-9日举办

2024第13届国际生物发酵产品与技术装备展&#xff08;上海展&#xff09; 2024年8月7-9日|上海新国际博览中心 主办单位&#xff1a; 中国生物发酵产业协会 承办单位&#xff1a; 上海信世展览服务有限公司 院校支持&#xff1a; 北京工商大学 大连工业大学 华东理工大…

FakeLocation报虚拟位置服务连接失败,请重启设备再试

虚拟位置服务连接失败&#xff0c;请重启设备再试 最近遇到一个手机软件报的bug“虚拟位置服务连接失败&#xff0c;请重启设备再试” 因为我的实体“虚拟机”已经root&#xff0c;按道理是不可能报这个错的 折腾了2天&#xff0c;终于解决了 原来是这样&#xff0c;安装最新…

React腳手架已經創建好了,想使用Vite作為開發依賴

使用Vite作為開發依賴 安裝VITE配置VITE配置文件簡單的VITE配置項更改package.json中的scripts在根目錄中添加index.html現在可以瀏覽你的頁面了 安裝VITE 首先&#xff0c;在現有的React項目中安裝VITE npm install vite --save-dev || yarn add vite --dev配置VITE配置文件 …