C语言编译步骤
预处理
- 预处理:在C语言中,带#号的符号,就是预处理指令,那么在预处理阶段,就会根据预处理指令进行预处理操作。
#include <stdio.h>
// 头文件展开:那么在预处理阶段中生成的预处理文件,就需要包含stdio.h这个文件里的信息。
// (预处理时将stdio.h的文件拷贝到预处理文件中)
- 在预处理时生成的注释会被删除
// hello world
- 在预处理时,宏定义会被直接替换
// C语言文件 hello.c
#define PI 3.14
....
printf(PI);
....
// 预处理生成的运处理文件(宏被替换) hello.i
printf(3.14);
- 在预处理中不会检测语法错误。
- 在预处理中可以进行条件编译,如 #if #endif
// 在C语言中
#if Win32
printf("win32");
#endif
// 在预处理文件中 .i文件,只要有 Win32就会printf("win32");,如果没有就不会编译这段代码
编译
将预处理文件(.i)
编译生成汇编文件(.s)
,并且会检查语法错误
gcc -S hello.i -o hello.s
汇编
将汇编文件 编译 生成 二进制文件
gcc -C hello.s -o hello.o
链接
设置运行环境,链接其他库
gcc hello.o -o hello.exe
System库函数
字符串
字符串大小写转换
- 将小写变为大写
#include <stdio.h>
int main(void) {
char a = 'a';
a = a - 32;
// %c 是以字符的方式在控制台输出
printf("c = %c", a); // 输出:A
return 0;
}
字符串常量
字符串常量是由一对双引号括起来的字符序列。
其中'a'
为字符常量、"a"
为字符串常量
分析"china"字符串常量为例。
- C语言在每一个字符串常量的结尾加一个字符串结束标志,以便系统来判断字符串是否结束。
- C语言规定以字符
'\0'
作为字符串的结束标志。 "china"
的内存单元是 6个字符,而不是5个字符。即大小为6个字节,最后一个字符为'\0'
,然而在控制台输出时不输出'\0'
,因为'\0'
在控制台不显示
混合运算
#include <stdio.h>
int main(void) {
int value = 5;
int res1 = value / 2;
float res2 = (float)value / 2; // (float)value 就是将int类型的强制转化为浮点类型的
printf("res1 = %f, res2 = %f \n", res1, res2); // 输出:res1 = 2.500000, res2 = 0.000000
// %f,在控制台输出浮点类型的,默认小数点后面 5 位
}
常见的数据输入/输出函数
scanf使用方法
int scanf(const char* format, ...);
- format是一个字符串, …是可变参数,参数的数目与format中的%数目一致
- %d 一个整形数、%f 一个浮点数、%c 一个单一的字符
- 发生错误时,返回EOF。其中
#define EOF (-1)
。 什么情况下scanf发生错误。gcc编译器,行首输入ctrl + D + 回车
scanf函数原理
行缓冲:在这种情况下,当在输入和输出中遇到换行符时,将执行真正的I/O操作。这是我们输入的字符先存放到缓存区,等按下回车键换行时才进行实际的的I/O操作。典型代表是标准输入缓冲区(stdin)和标准输出缓冲区(stdout)
如上面的例子:我们向标准输入缓冲区中放入的字符为 '10\n'
,输入 \n
(回车)后,scanf才会开始匹配。scanf函数中的%d匹配整数型10,然后放入变量value中,接着进行打印输出,这是'\n'
仍然在标准输入缓冲区当中,如果第二个scanf函数为scanf("%d", &i)
,依然会出现阻塞。因为scanf函数在读取整数型、浮点型、字符串时,会忽略'\n'
(回车符)、空格符等字符(忽略是指scanf函数执行时,会首先删除这些字符,在进行阻塞)。 如果是scanf("%c", &c)
不会忽略任何字符。
- scanf通过%s读取字符串时,在遇到空格之后,就会匹配结束,这样就没办法将一行字符串存存储到字符数组中。
#include <stdio.h>
int main(void) {
char c1[20], c2[20];
scanf("%s%s", c1, c2);
printf("%s----%s", c1, c2);
return 0;
}
输入:
nihao helloworld
输出:
nihao----helloworld
#include <stdio.h>
int main(void) {
int value;
char c;
scanf("%d", &value);
scanf("%c", &c);
printf("value = %d\n", value);
printf("c = %c\n", c);
return 0;
}
输出结果:
10
value = 10
c =
运行结束。
scanf函数的循环读取
#include <stdio.h>
int main(void) {
int value;
while ((getchar()) && scanf("%d", &value) != EOF) {
printf("value = %d\n", value);
}
return 0;
}
// getchar()清空缓存区
- 练习:输入helloworld,输出HELLOWORLD
#include <stdio.h>
int main(void) {
char c;
while (scanf("%c", &c) != EOF) {
if(c != '\n'){
printf("%c", c - 32);
}else {
printf("\n");
}
}
return 0;
}
多种数据类型的混合输入
混合输入时,每次在%c之前加入一个空格
#include <stdio.h>
int main(void) {
int i;
float f;
char c;
scanf("%d %c%f", &i, &c, &f); // 输入:5 m 98.5
// %4.1f 表示占4个空格(包括小数点),并且显示小数点后面1位
printf("i = %d, c = %c, f = %4.1f", i,c,f); //输出:i = 5, c = m, f = 98.5
return 0;
}
C数组
一维数组
// 1. 声明数组
double c[10];
// 2. 初始化数组,用大括号初始化
double c[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 3. 访问数组
c[9];
// 其中c就是c[0]的地址
一维数组在内存中的存储
数组里面其实存储的是地址。如果修改数组里的值,会先去找到地址,通过地址去找到值,在进行修改。
字符数组
// 定义数组并初始化(实际不会采取这种方式)
char c[10] = {'h', 'e', 'l', 'l', 'o'};
// 实际中
// c[10]最长存储9个字符,最后一个字符来存储 '\0'
char c[10] = "hello";
- 有趣的测试
#include <stdio.h>
int main(void) {
char c[5] = {'h', 'e', 'l', 'l', 'o'};
char d[5] = "how";
printf("%s----%s\n", c,d); // 输出:hello JrU----how
// 为什么会输出 hello JRU呢?
// c[5]最多存储4个字符,如果存储5个字符会把结束符替换掉
// 因为c没有结束符,它只有结束符'\0'才会结束输出
return 0;
}
gets函数和put函数
gets函数从STDIN(标准输入)读取字符并把它们加载到str(字符串中),直到遇到换行符\n或到达EOF。比如说:我们在控制台输入 how are you 回车
,这里其实有11个字符,包括空格,并且最后有'\0'
,不会存储'\n'
由于安全性问题,gets被遗弃了
puts函数就是将字符串写入到标准输出STDOUT中。在控制台显示
#include <stdio.h>
int main(void) {
char c1[20], c2[20];
gets(c1); // 输入 how are you 回车
puts(c1); // 输出 how are you
return 0;
}
str系列的函数
需要引用 #include <string.h>
size_t strlen (const char *__s)
定义:计算字符串的长度
注意:长度不会计算'\0'
的
#include <stdio.h>
#include <string.h>
int main(void) {
char c1[20] = "hello";
printf("字符数组c1的长度为%d", strlen(c1));
// 输出: 字符数组c1的长度为5
return 0;
}
- char *strcpy (char *dest, const char *src)
定义:复制src到dest
注意:有const修饰的表示这个地方可以放一个字符串常量
#include <stdio.h>
#include <string.h>
int main(void) {
char str[20];
strcpy(str, "helloworld");
puts(str); // 输出 helloworld
return 0;
}
- int strcmp (const char *__s1, const char *__s2)
定义:比较两个字符串的大小,比较的是ASCII码,如果相等为0,大于为1,小于为-1
#include <stdio.h>
#include <string.h>
int main(void) {
printf("比较两个字符串的大小为%d\n", strcmp("hello", "hello")); // output 比较两个字符串的大小为0
printf("比较两个字符串的大小为%d\n", strcmp("how", "hello")); // output 比较两个字符串的大小为1
return 0;
}
- char *strcat (char * __dest, const char * __src)
定义:拼接字符串,将src字符串拼接到dest字符串的后面
注意:_dest数组必须容纳拼接之后数组的大小
#include <stdio.h>
#include <string.h>
int main(void) {
char str[20] = "hello";
puts(strcat(str, "world"));
return 0;
}
指针
指针的本质就是地址
指针初始化
#include <stdio.h>
#include <string.h>
int main(void) {
int i = 5;
// & 取地址
int *p = &i;
printf("%d\n", i); // 直接访问
printf("%d\n", *p); // 间接访问
return 0;
}
指针的传递
#include <stdio.h>
#include <string.h>
void changeI(int* p){ // 形参 p = &i;
*p = 25;
}
int main(void) {
int i = 5;
changeI(&i); // 实参
printf("i = %d\n", i);//output i = 25
return 0;
}
指针的偏移
#include <stdio.h>
#include <string.h>
int main(void) {
int str[5] ={1, 2, 3,4,5};
int * p = str; // 数组名就是这个数组的起始地址
int i = 0;
for(i = 0; i < 5; i++) {
printf("%d\t", *(p + i)); // output 1 2 3 4 5
}
}
指针与自增、自减运算符
#include <stdio.h>
#include <string.h>
int main(void) {
int arr[5] = { 2, 3, 5, 8, 10};
int *p = arr;
注意:任何时候都先把后++去掉,第二步才会算后++
int j = *p++; // int j = *p; p++;
printf("arr[0] = %d, j = %d, *p = %d\n", arr[0], j, *p); // arr[0] = 2, j = 2, *p = 3
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void) {
int arr[5] = { 2, 3, 5, 8, 10};
int *p = arr;
int j = (*p)++; // int j = *p; (*p)++;
printf("arr[0] = %d, j = %d, *p = %d\n", arr[0], j, *p); // arr[0] = 3, j = 2, *p = 3
return 0;
}
指针与一维数组
一维数组的数组名就是地址,是起始地址.
#include <stdio.h>
int main(void) {
int arr[5] = { 2, 3, 5, 8, 10};
int *p = arr; // arr 就代表了地址
printf("*p=%d\n", *p); // *p=2
int j = *p++;
printf("j= %d\n", j); // j= 2
printf("*p=%d\n", *p); // *p=3
}
指针与动态内存申请(重要)
动态内存申请需要引入#include <stdlib.h>
// 从对空间中申请多少个字节的空间
// __size就是字节大小
void *malloc (size_t __size);
- 栈空间和堆空间的差异
栈空间会随着函数执行的结束而释放。
字符指针和字符数组的初始化
#include <stdio.h>
int main(void) {
char * p = "hello"; // 将字符串型常量的首地址赋给p
char c[10] = "hello"; // 等价于 strcpy(c, "hello");
c[0] = 'H';
p[0] = 'H'; // 非法。 不可以对字符串常量数据进行修改
p = "world"; // 将字符串world的地址赋值给p
c = "world"; // 非法 (数组名并不是一个变量)
}
结构体
1. 结构体的使用
#include <stdio.h>
// 声明一个结构体
struct student {
int num; // 结构体成员
char name[20];
char sex;
int age;
float score;
};
int main(void) {
// 初始化结构体 struct student就可以理解为一个数据结构,如int、float等
struct student s = {12, "libai", '0', 15, 100.0};
// 通过 . 的方式,来获取结构体成员
printf("%d %s %c %d %5.2f", s.num, s.name, s.sex, s.age, s.score);
return 0;
}
2. 结构体数组
#include <stdio.h>
// 声明一个结构体
struct student {
char name[20]; // 结构体成员
char sex;
int age;
};
int main(void) {
struct student s[3];
int i = 0;
for(i = 0; i < 3; i++) {
// 使用scanf时,遇到字符时,前需要加一个空格
// 为什么?详见上面
scanf("%s %c%d", s[i].name, &s[i].sex, &s[i].age);
}
for(i = 0; i < 3; i++) {
printf("%s %c %d\n", s[i].name, s[i].sex, s[i].age);
}
return 0;
}
3. 结构体大小
struct teacher{
int age; // 4
char sex; // 1
char name[5]; // 3 + 4 要对齐
};
int main(void) {
printf("%lu\n", sizeof(struct teacher));
return 0;
}
4. 结构体指针
#include <stdio.h>
struct teacher{
int age; // 4
char sex; // 1
char name[25]; // 3 + 24
};
int main(void) {
struct teacher t = {18, 'm', "wangli"};
struct teacher *p = &t;
// 方式一:因为 . 的优先级比 * 高,所以要加括号
printf("%d %c %s\n", (*p).age, (*p).sex, (*p).name);
// 方式二:使用指针符号 ->
printf("%d %c %s\n", p->age, p->sex, p->name);
return 0;
}
5. 对结构体的思考
#include <stdio.h>
struct teacher{
int age;
char sex;
char name[25];
};
int main(void) {
struct teacher t[3] = {18, 'm', "wangli", 19, 'f', "lisi"};
struct teacher *p = t;
int age;
age = p->age++; // 1. age = p->age; 2. p->age++;
printf("age=%d, p->age=%d\n", age, p->age); // 18,19
age = p++->age; // 1. aeg = p->age; 2. p++;
printf("age=%d, p->age=%d\n", age, p->age); // 19, 19
return 0;
}
typedef的使用
typedef的作用就是起别名
如下列中
teacher 等价于 struct teacher
p 等价于 struct teacher*
如果我们声明变量,如下:
teacher t1 = { 18, '5', "lisi"}
p t2 = &t1
typedef struct teacher{
int age;
char sex;
char name[25];
}teacher, *p;
C++引用
C++引用是一种内存地址别名,它允许我们使用一个名称来引用另一个变量。引用通常用作函数参数和返回值类型,以便可以避免复制大型数据结构。
在C++中,使用&符号来声明一个变量的引用。例如,以下代码定义了一个整数变量a,并为其创建了一个引用b:
int a = 5;
int &b = a;
这将创建一个名为b的引用,它指向变量a的内存地址。现在,如果我们修改b的值,实际上是在修改a的值,因为b和a共享同一块内存空间。例如
b = 10; // 现在 a 的值也变成了 10
注意,引用必须在定义的同时初始化,且不能被重新绑定到另一个对象。此外,引用可以被用作函数参数,以便可以在函数内部操作原始变量而不需要进行复制。
C++引用用于函数参数
引用通常被用作函数参数,以便可以在函数内部操作原始变量而不需要进行复制。通过引用传递参数比通过值传递参数更高效,因为它避免了在函数调用时的数据拷贝。
void increment(int& x) {
x++;
}
这个函数接受一个整数的引用,并将其值增加1。现在,如果我们调用该函数并传递一个整数变量作为参数,该变量的值将被修改:
int a = 5;
increment(a); // 现在 a 的值变成了 6
请注意,当我们将引用作为参数传递给函数时,实际上是将原始变量的内存地址传递给了函数。因此,在函数内部对引用所引用的变量的任何修改都会影响到原始变量
评论区