C基础笔记

1、C语言编译步骤

#include<stdio.h>

// 定义一个宏
#define PI 314  /*定义一个宏*/

int main(){
	printf("Hello");
	
	#if 0
	printf("条件编译成立");
	#endif
	
	#if 12
	printf("条件编译成立");
	#endif
	
	#if abc
	printf("条件编译成立");
	#endif
	
	printf("PI 的值 %lf \n", PI);
	
	return 0;
}
  • 1)预编译

    • 带#的语句就是预处理指令,预处理指令在预处理的时候处理了
    • 头文件展开:#include<stdio.h>包含文件stdio.h(预处理时将stdio.h文件拷贝至预处理文件中)
    • 删除注释:注释有两种方法:// 、 /**/
    • 宏替换:#define 代表是声明一个宏,在预处理时会将宏替换
    • 条件编译:条件不成立时:#if 0 -> #endif;条件成立时:#if 1 -> #endif
    • 预处理时不会检查语法错误
      在这里插入图片描述
  • 2)编译

    • 将预处理文件编译生成汇编文件
    • 检查语法错误
      在这里插入图片描述
  • 3)汇编

    • 将汇编文件编译生成二进制文件.o
      在这里插入图片描述
  • 4)链接

    • 设置运行环境,堆栈等,链接其他库
      在这里插入图片描述

2、指针

2.1、特殊的指针

(1)野指针、空指针、万能指针
在这里插入图片描述

(2)const修饰指针

int main()
{
    int a = 100;
    int b = 200;

    // 此处const修饰的是 * , 意味着不能通过 *p 去修改所指向空间的内容
    const int *p1 = &a;
    int const *p2 = &a;
    // *p1 = 200; // ERROR


    // 此处const修饰的是 p3 , 意味着 p3 保存的地址不能被修改
    int * const p3 = &a;
    // p3 = &b; // ERROR


    // 表示既不能修改*指向的内容,也不能修改p4指向的地址
    //const int * const p4 = &a;
    int const * const p4 = &a;
    // p4 = &b;  ERROR
    //*p4 = b;  ERROR

    return 0;
}

(3)多级指针

	int a = 100;

    int *p = &a;
    int **q = &p;

    // 多级指针
    // 下面的取值都是相同的, (如果 * 与 & 相遇,相抵消)
    printf("**q=%d, *(*q)=%d, *p=%d, *(&a)=%d, a=%d",**q, *(*q), *p, *(&a), a);

(4)数组与指针
①指针结合数组

int main()
{

    // num 是一个数组名,实际代表的是数组的起始地址,也就是首元素的地址
    int nums[10]; //= {[5]=666};
    //for(int i=0; i<10; i++)
    //{
    //	printf("%u \n", nums[i]);
    //}

    printf("*********************\n");

    printf("nums数组长度 %u\n", sizeof(nums));
    printf("int长度 %u\n", sizeof(int));

    printf("%u\n", nums);
    printf("%u\n", &nums);
    printf("%u\n", &nums[0]);

	// 指针加一,跨过一个步长
    // nums + 1 与 &nums[0] + 1 表示跨过一个步长(即int的跨度(sizeof(int))),&num + 1 表示跨过一列(即元素个数*元素类型的长度)
    printf("%u\n", nums+1);
    printf("%u\n", &nums + 1);
    printf("%u\n", &nums[0]+1);

    return 0;
}

运行结果
在这里插入图片描述
②指针运算(数组)
两指针相减表示跨过多少个元素
两指针相加没有任何意义

int nums[] = {5,3,9,4,7} ;
printf("第5个元素到第2个元素的步长:%d\n", &nums[4] - &nums[1]);
printf("nums[4]的地址:%d\n", (int *) (&nums+1)-1);

运行结果
在这里插入图片描述

③[]并非数组专属

	int nums[] = {5,3,9,4,7} ;
    int len = sizeof(nums) / sizeof(int);
    int *p = nums;

    for (int i=0; i<len; i++) {
        // [] 并非数组专属,[] == p()
        printf("nums[%d]=%d, *(p+%d)=%d\n", i, nums[i], i, *(p+i));
    }

运行结果
在这里插入图片描述
④指针数组

int main()
{
    //指针数组,数组中的每一个元素都是指针(地址)
    int a = 10;
    int b = 20;
    int c = 30;

    int *nums[] = {&a, &b, &c};

    // 定义一个二级指针,该指针存储指针数组第一个元素的位置
    int **k = &nums[0];

    for(int i=0; i<sizeof(nums)/sizeof(nums[0]); i++){
        // 实际可以理解为:**(k+i) == *nums[k+i] == {10,20,30}[k+i]
        printf("%d\n", **(k+i));
    }

    return 0;
}

运行结果
在这里插入图片描述
⑤指针作为形参,可以改变实参值

// 交换 a 和 b 的值
//如果输入的是(int a, int b),a与b交换的结果只会作用于局部
void swap2(int *a, int *b)
{
    int k = *a;
    *a = *b;
    *b = k;
}

运行结果
在这里插入图片描述
⑥数组作为函数的形参会退化为指针
错误传参:void print_arr(int nums[])
正确写法:void print_arr(int *nums, int len) // 传入指针(即首元素地址),数组长度

⑦指针作为返回值需要注意
1.局部变量在用完后内存自动回收
2.全局变量生命周期会延续到程序结束

(5)指针与字符数组
①指针与字符串

int main()
{
    //指针与字符串
    //定义一个字符数组,字符数组内容为 helloworld\0
    char a[] = "helloworld";
    //定义一个指针用来保存数组首元素的地址
    char *p = a;

    printf("p = %s \n", p);  // 打印一个字符串,要的是首个字符的地址
    printf("p+2 = %s\n", p+2);
    printf("*(p+3) = %c\n", *(p+4));

    printf("a的字节:%d\n", sizeof(a));
    printf("p的字节%d\n", sizeof(p));

    printf("a的有效长度:%d\n", strlen(a));
    printf("p的长度:%d\n", strlen(p));

    // 替换字符串中的某个元素
    *p = 'm';
    *(p + 2) = 'j';
    printf("%s\n", p);

    // 取第一个字符后的内容
    p++;
    printf("%s\n", p);

    return 0;
}

运行结果
在这里插入图片描述

②字符串常量

	char a2[] = "helloworld";
    char *p2 = a2;
    // 字符串常量存在文字常量区,“”在使用时取的是字符串首元素地址
    // 文字常量区的内容是不可以改变的
    p2 = "xjkhg";

    printf("p2 = %s\n", p2);

    printf("a2的字节:%d\n", sizeof(a2));
    printf("p2的字节%d\n", sizeof(p2));

    printf("a2的有效长度:%d\n", strlen(a2));
    printf("p2的长度:%d\n", strlen(p2));

运行结果
在这里插入图片描述

③字符串指针作为形参

    int n = strlen(str1);
    int i = 0;

    //while(i < strlen(str2)){   // 通过有效长度判断
    while(*(str2+i) != 0){       // 通过 \0 判断
        //*(str1+n+i) = *(str2+i);  // 赋值
        str1[n+i] = str2[i];        // 给地址
        i++;
    }

    *(str1 + n + i) = 0;
    return str1;
}


int main()
{
    char str1[] = "hello";
    char str2[] = "1234";
    my_concat(str1, str2);

    printf("%s\n", str1); // "hello1234"
    return 0;
}
在这里插入代码片

s注意: 字符指针数组作为main函数传参,第一个元素保存的是程序路径地址,保存的是字符串首元素地址

④const修饰的字符串数组
同const修饰指针
char buf = “hello”;
const char *p = buf; //不能修改指针指向空间的内容
char * const p = buf; // 不能修改指针指向空间的地址
const char * const * p = buf; //不能修改指针指向空间的地址和内容

⑤字符指针数组(综合性强)

int main()
{
    char a[] = "abcd";
    char b[] = "hijklm";
    char c[] = "xyz";

    // 定义一个字符指针数组,数组实际存储的是首元素地址
    char *nums[] = {a, b,c};

    //遍历
    for(int i=0; i< sizeof (nums)/sizeof (char *); i++){
        printf("--> %s, %s \n", nums[i], *(nums+i));
    }

    // 取 b 的第 3 个字符
    printf("%c, %c \n", nums[1][2], *(*(nums+1)+2));

    return 0;
}

运行结果
在这里插入图片描述
⑥字符串处理函数
1)拷贝与链接函数
在这里插入图片描述
2)比较函数
在这里插入图片描述
3)组包、拆包函数函数

	// 组包函数
    int a5 = 1994;
    int b5 = 12;
    int c5 = 01;
    char buf5[1024] = "";

    int len5 = sprintf(buf5, "%d年%d月%d日", a5, b5, c5);

    printf("%s\n", buf5);  // 1994年12月1日

	// 拆包函数
    int a6 = 0;
    int b6 = 0;
    int c6 = 0;
    char buf6[1024] = "1994 : 12 & 1";
    sscanf(buf6, "%d : %d & %d", &a6, &b6, &c6);
    printf("拆包:%d年%d月%d日\n", a6, b6, c6);

4)返回字符串中的 元素 或者 字符串 首次出现位置

char a7[] = "qianyu98k";
    // 返回字符串首元素出现地址,没找到则返回 NULL
    char *p7 = strchr(a7, 'a');
    printf("%s\n", p7);  // anyu98k

    // 返回字符串首字符串出现地址,没找到则返回 NULL
    char *p77 = strstr(a7, "yu");
    printf("%s\n", p77);  // yu98k

5)字符串切割

	char a8[] = "13288888888#qian:yu:xiang";
    // 切割函数,返回的是地址,没切割到,返回 NULL
    char *p8 = strtok(a8, "#:");
    printf("第一次切割:%s\n", p8);   // 13288888888
    char *p88 = strtok(NULL, "#:");
    printf("第二次切割:%s\n", p88);  // qian
    char *p888 = strtok(NULL, "#:");
    printf("第三次切割:%s\n", p888);  // yu

    char a9[] = "13288888888#qian:yu:xiang";
    char *p9[10] = {NULL};
    //开始切割
    int i9 = 0;
    do {
        if(0 != i9) {
            p9[i9] = strtok(NULL, "#:");
        } else {
            p9[i9] = strtok(a9, "#:");
        }
        printf("s1 = %s\n", p9[i9]);
    } while (p9[i9++] != NULL);

    int k9 = 0;
    while (p9[k9] != NULL) {
        printf("s2 = %s\n", p9[k9]);
        k9 ++;
    }

运行结果
在这里插入图片描述
6)字符转换
#include <stdlib.h>

	// 将字符转为整数,如果前面有空格则跳过
    char num10[] = " +1234";
    char num11[] = " 12.3456";
    printf("字符转int类型:%d\n", atoi(num10));
    printf("字符转float类型: %.2f\n", atof(num11));
    printf("字符转long类型: %ld\n", atoi(num10));

3、作用域

(1)变量类型
局部变量
a>作用域:在定义变量的{}内有效
b>生命周期:程序运行至变量定义处开辟空间,所在函数运行结束后释放空间
c>未初始化的值:随机

静态局部变量
a>作用域:在定义变量的{}内有效
b>生命周期:执行main函数之前就已经开辟空间,程序结束之后释放空间
c>未初始化的值:0

全局变量
a>作用域:整个工程,所有文件
b>生命周期:执行main函数之前就已经开辟空间,程序结束之后释放空间
c>未初始化的值:0

静态全局变量
a>作用域:当前文件
b>生命周期:执行main函数之前就已经开辟空间,程序结束之后释放空间
c>未初始化的值:0

全局变量分文件问题:.h 文件中只声明,不定义,其他文件(.c)使用时定义就行了

静态函数:(static void function(){})只能被当前文件调用
普通函数:就是没有加任何修饰的函数,可以被整个工程调用

4、内存管理

(1)内存分布
在这里插入图片描述
(2)内存处理函数

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

int main() {
    // 总之:内存mem操作函数遇到 0 或者 \0 都不会结束操作,而str字符串处理函数遇到 \0 都会结束


    char s1[] = "abcdefgh";
    // 将s的内存区域的前n个字节以参数c填入
    // void memset(void *s, int c, size_t n)
    memset(s1, '0', 4);
    printf("s1 = %s \n", s1);


    //将数组src中的n个元素拷贝到数组dest中
    // void *memcpy(void dest, const void *src, size_t n)
    int s2[10] = {1,2,3,4,5,6,7,8,9,10};
    int s3[10] = {0};
    memcpy(s3, s2, sizeof(int)*4);
    printf("s3 = ");
    for(int i=0; i<sizeof(s3)/sizeof(s3[0]); i++){
        printf("%d ", s3[i]);
    }
    printf("\n");


    // memcmp 内存比较函数,用法差不多的

	return ;
}

(3)malloc与free

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

int main() {
    // malloc申请空间
    // 申请一个数组,数组有十个元素,每个元素int类型
    int *p1 = (int *)malloc(sizeof(int)*10);
    // 初始化数组
    memset(p1, 0, sizeof(int)*10);
    // 给第一个元素赋值
    *p1 = 1000;
    // 给第六个元素赋值
    *(p1+5) = 5000;
    printf("p1 = ");
    for(int i=0; i<10; i++){
        printf("%d ", *(p1+i));
    }
    printf("\n");

    // 释放申请过的的空间,只能释放malloc向堆区申请的空间,像 int a 这种就不能释放
    free(p1);

    return 0;
}

(4)内存泄漏与内存污染
内存泄漏:内存只申请,不释放,导致内存使用的空间一直增长
内存污染:向没有申请过的空间写入数据

(5)返回变量的地址
只有普通局部变量的地址不能返回,因为普通局部变量在所在函数结束之后就会立刻释放掉内存
静态局部、全局、静态全局,这些变量,只要程序不退出,就不会释放,所以这些变量的地址是可以返回操作的

(6)返回堆区的地址
传实参的地址,可以在调用时改变实参的值
能不能返回这个地址,需要看准备个地址所指向的空间有没有释放
一级指针的地址需要定义二级指针来存

5、结构体(复合类型)

(1)三种定义结构体的方法

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

// 结构体:将不同或者相同类型的数据存在一块内存中
// 定义结构体,结构体只是一个类型,一个模板,没有空间,不可以给结构体赋值
// 三种定义结构体的方法
struct student
{
    int id;
    int age;
    char name[128];
};

struct student2
{
    int id;
    int age;
    char name[128];
} b1, b2;

struct
{
    int id;
    int age;
    char name[128];
} c1, c2;

结构体的初始化

   // 初始化
    struct student a1 = {1, 18, "Xiang"};

    // 部分元素初始化
    struct student a2 = {.age=18};

结构体的成员操作

 	// 两种操作结构体成员的方法
    // 1.通过结构体变量操作结构体成员
    struct student a3;
    a3.id = 2;
    a3.age = 18;
    strcpy(a3.name, "Alice");
    printf("a3 = {%d, %d, %s}\n", a3.id, a3.age, a3.name);

    //2.通过结构体地址操作结构体成员
    (&a3)->id = 3;
    (&a3)->age = 20;
    strcpy((&a3)->name, "Bob");
    printf("a3 = {%d, %d, %s}\n", (&a3)->id, (&a3)->age, (&a3)->name);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值