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
- 将汇编文件编译生成二进制文件.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);