操作系统:Windows10 编译器:gcc 12.2.0 github仓库链接:https://github.com/Nazarite31/ClangTutorial/tree/main/fromEntryToAdvanced/ch1
基本概念
.c -> 源文件 .h -> 头文件
main函数
#include <stdio.h>
/* main函数,程序的入口, 一个工程中有且仅有一个*/
int main(int argc, char* argv[])
{
printf("Hello World!\n");
printf("Hello Nazarite!\n");
return 0;
}
基本数据类型
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("char %llu bytes\n", sizeof(char));
printf("short %llu bytes\n", sizeof(short));
printf("int %llu bytes\n", sizeof(int));
printf("long %llu bytes\n", sizeof(long));
printf("long long %llu bytes\n", sizeof(long long));
printf("float %llu bytes\n", sizeof(float));
printf("double %llu bytes\n", sizeof(double));
return 0;
}
printf函数
1.使用必须包含头文件stdio.h 2.常见格式字符 %c -> 打印字符格式数据 %d -> 打印十进制整型格式数据 %x -> 打印十六进制整型格式数据 %o -> 打印八进制整型格式数据 %f -> 打印浮点型格式数据 %lf -> 打印双精度浮点型格式数据 %p -> 打印地址
变量
1.定义变量 int age = 150; float weight = 45.5f; char ch = 'w'; 2.局部变量:定义在代码块({})内部的变量,作用域是变量所在的局部范围; 3.全局变量:定义在代码块({})之外的变量,作用域是整个工程;
#include <stdio.h>
int main(int argc, char* argv[])
{
int a = 10;
{
printf("%d\n", a); //ok
}
printf("%d\n", a); //ok
return 0;
}
#include <stdio.h>
int main(int argc, char* argv[])
{
{
int a = 10;
printf("%d\n", a); //ok
}
printf("%d\n", a); //error
return 0;
}
局部变量的生命周期:进入作用域生命周期开始,出作用域生命周期结束 全局变量的生命周期:整个程序的生命周期
#include <stdio.h>
int global = 2019; //global
void test(void)
{
// printf("test func: local=%d\n", local); //error: 'local' undeclared (first use in this function)
printf("test func: global=%d\n", global);
}
int main(int argc, char* argv[])
{
int local = 2018; //local variable
test();
/* 局部变量和全局变量同名的时候,局部变量优先使用 */
int global = 2020; //local variable
// global = 2022; //global is changed
printf("main func: local=%d\n", local);
printf("main func: global=%d\n", global);
test();
return 0;
}
常量
#include <stdio.h>
/*
* 常量分类:
* (1)字面常量
* (2)const修饰的常变量
* (3)#define定义的标识符常量
* (4)枚举常量
*/
#define MAX 10
enum Sex
{
MALE, //枚举常量
FEMALE, //枚举常量
SECRET //枚举常量
};
enum Color
{
RED=5,
GREEN,
BLUE
};
int main(int argc, char* argv[])
{
3; //字面常量
3.14; //字面常量
// 3 = 5; //error, 字面常量本身不能被修改
const int a = 10; // const表示常属性,a为const修饰的常变量
printf("a=%d\n", a);
// a = 20; //error: assignment of read-only variable 'a' const修饰的常变量本身不能被修改
// printf("a=%d\n", a);
/* 证明const修饰的变量本质上还是变量,只是具备常属性,因此被称为常变量 */
// int n = 10; //error: variable-sized object may not be initialized
// const int n = 10; //error: variable-sized object may not be initialized
// int arr[n] = {0};
printf("MAX=%d\n", MAX); //#define定义的标识符常量
int arr[MAX] = {1, 2, 3};
for (int i = 0; i < MAX; i++)
{
printf("%d ", arr[i]);
}
puts("\n");
// MAX = 30; //error,标识符常量本身不能被修改
enum Sex jack_sex = MALE;
printf("Jack sex is %d\n", jack_sex); //默认MALE的值是0
printf("Jane sex is %d\n", FEMALE); //默认FEMALE的值是1
printf("Mike sex is %d\n", SECRET); //默认SECRET的值是2
enum Color c1 = RED;
enum Color c2 = GREEN;
enum Color c3 = BLUE;
printf("RED is %d\n", c1);
printf("GREEN is %d\n", c2);
printf("BLUE is %d\n", c3);
c1 = BLUE; // 通过枚举类型创建的变量可以被修改
// BLUE = 6; //error,枚举常量本身不能被修改
return 0;
}
字符串
#include <stdio.h>
#include <string.h>
/*
* 字符串:由双引号引起来的一串字符称为字符串字面值,简称字符串
* 字符串的结束标志是'\0'的转义字符
* ASCII编码值:'\0'是0,'0'是48,a'是97,'A'是65
* 在计算字符串长度时,'\0'不算作字符串内容
* strlen()函数用于计算字符串长度
*/
// 注意:
// '\0' 字符串的结束标志,不可见,值是0
// EOF(end of file) 文件结束标志,值是-1
int main(int argc, char* argv[])
{
char arr1[] = "abc"; // 将字符串存放在字符数组中
char arr2[] = {'a', 'b', 'c'};
char arr3[] = {'a', 'b', 'c', '\0'};
printf("char arr1: %s\n", arr1); // %s格式符用于打印字符串
printf("char arr2: %s\n", arr2); // 没有结束标志,打印结果不一定是abc,可能后面会带一些随机值
printf("char arr3: %s\n", arr3); // abc
printf("char arr1 length: %llu\n", strlen(arr1)); // 3
printf("char arr2 length: %llu\n", strlen(arr2)); // 结果不是3,是随机值,遇到'\0'截止
printf("char arr3 length: %llu\n", strlen(arr3)); // 3
//转义字符
printf("c:\test\32\test.c"); // 结果不是预想的路径,\t表示水平制表符
printf("c:\\test\\32\\test.c"); // \\用于表示反斜杠,防止被解析成转义序列符
printf("\?\?)"); // \?用于在书写多个连续问号时,防止被解析成三子母符
printf("%c\n", '\''); // \'用于表示字符常量'
printf("%s\n", "\""); // \"用于表示一个字符串内部的双引号
printf("%llu\n", strlen("c:\test\32\test.c")); // 结果是13,不是14, \32是1个字符
printf("%llu\n", strlen("c:\test\382\test.c")); // 结果是15, \382是3个字符
printf("%llu\n", strlen("c:\test\x61\test.c")); // 结果是13, \x61是1个字符
// '\32'是八进制32转化为十进制后(26)所代表的字符
// \ddd,ddd表示1-3个八进制的数字
// \xdd, dd表示2个十六进制数字
printf("%c\n", '\32');
printf("%s\n", "\382");
printf("%c\n", '\x61');
return 0;
}
注释
注释使用的场景:
1.代码中存在不需要的代码,可以直接删除,也可以注释掉 2.代码中某些代码比较难懂,可以添加注释文字
1.C语言风格注释/* xxx */ 缺陷:不能嵌套注释 2.C++风格注释 // xxxxxx 可以注释一行,也可以注释多行
分支语句
#include <stdio.h>
int main(int argc, char* argv[])
{
int isStudyHard = 0;
printf("Go to school\n");
printf("Do you study hard?(0|1) >");
scanf("%d", &isStudyHard);
if (isStudyHard == 1)
{
printf("You have good grades.");
} else {
printf("You have bad grades.");
}
return 0;
}
循环语句
#include <stdio.h>
/* Ten thousand hours theory */
int main(int argc, char* argv[])
{
int hours = 0;
printf("Start learning a new skill...");
while (hours < 10000)
{
if (hours % 100 == 0)
{
printf("study time is %dh, not enough!\n", hours);
}
hours++;
}
printf("Success as a master!");
return 0;
}
函数
#include <stdio.h>
//自定义函数
int SumTwoNums(int v_num1, int v_num2)
{
int res = v_num1 + v_num2;
return res;
}
int main(int argc, char* argv[])
{
int num1 = 10;
int num2 = 20;
printf("%d + %d = %d\n", num1, num2,
SumTwoNums(num1, num2)); // 库函数
return 0;
}
数组
#include <stdio.h>
/*
* 数组:一组相同类型元素的集合
* 连续存储
* 下标从0开始依次递增至n-1,n为数组长度
*/
int main(int argc, char* argv[])
{
// 创建一个存有10个int类型元素的数组并赋初值
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("arr[4]=%d\n", arr[4]); // 通过下标访问数组元素
// 遍历数组,数组元素是连续存储的,每个元素占sizeof(int)
for (int i = 0; i < 10; i++)
{
printf("%p --> %d\n", &arr[i], arr[i]);
}
printf("size of int: %llu\n", sizeof(int));
int n = 10; int arr[n] = {0}; // error,数组的创建时数组大小的指定必须用常量,不能使用变量
return 0;
}
操作符
#include <stdio.h>
/*
* 算术运算符 + - * / %
* 移位操作符 >> <<
* 位操作符 & | ^
* 赋值操作符 = += -= *= /= &= ^= |= >>= <<=
* 单目操作符 ! - + & sizeof ~ -- ++ * (type)
* 关系操作符 > >= < <= != ==
* 逻辑操作符 && ||
* 条件操作符 exp1 ? exp2 : exp3;
* 逗号表达式 exp1, exp2, exp3, ...expN
* 下标引用操作符 []
* 函数调用操作符 ()
* 结构成员操作符 . ->
*/
int main(int argc, char* argv[])
{
int a = 11; // 1011
int b = 5; // 0101
// -----------------算术操作符----------------
int div = a / b; // 求商
int mod = a % b; // 取模
printf("div=%d, mod=%d\n", div, mod);
// -----------------移位操作符----------------
int shiftLeftOneBit = a << 1; // 将a转化为2进制后,向左移动1位,去除左侧超出int的1位,右侧补1位0
int shiftLeftTwoBit = a << 2;
int shiftRightOneBit = a >> 1;
int shiftRightTwoBit = a >> 2;
printf("shift left one bit result is %d\n", shiftLeftOneBit);
printf("shift left two bit result is %d\n", shiftLeftTwoBit);
printf("shift Right one bit result is %d\n", shiftRightOneBit);
printf("shift Right two bit result is %d\n", shiftRightTwoBit);
// -----------------位操作符----------------
printf("%d bit and %d result is %d\n", a, b, a & b); // 0001
printf("%d bit or %d result is %d\n", a, b, a | b); // 1111
printf("%d bit xor %d result is %d\n", a, b, a ^ b); // 1110,注:^不代表次方,是异或
// -----------------赋值操作符----------------
a += 10; // 等价于a = a + 10;
// -----------------单目操作符----------------
printf("!a=%d\n", !a); // 单目操作符!表示逻辑反操作
int c = -a; // 单目操作符-表示负值
c = +a; // 单目运算符+表示正值,通常省略
printf("%p\n", &c); // 单目运算符&表示取地址
printf("%d\n", *(&c)); // 单目运算符*表示间接访问操作符(解引用操作符)
int d = 0;
printf("%d\n", ~d); // 单目运算符~表示对一个数的二进制位取反
/* 整数在内存中存储的是二进制的补码,打印的是该数的原码
* 对于正数:
* 原码 = 反码 = 补码
* 对于负数:
* 原码 -(除符号位取反)-> 反码 -(加1)-> 补码
* 补码 -(减1)-> 反码 -(除符号位取反)-> 原码
* 11111111111111111111111111111111 // ~b在内存中存储的二进制形式
* 11111111111111111111111111111110
* 10000000000000000000000000000001 // ~b的原码
*/
int e = 1;
int f = e++; // 后置++,先使用,再++
printf("e=%d, f=%d\n", e, f);
e = 1;
f = ++e; // 前置++,先++,再使用
printf("e=%d, f=%d\n", e, f);
e = 1;
f = e--; // 后置--,先使用,再--
printf("e=%d, f=%d\n", e, f);
e = 1;
f = --e; // 前置--,先--,再使用
printf("e=%d, f=%d\n", e, f);
// sizeof计算变量或类型所占的空间大小(以字节为单位)
printf("%llu %llu\n", sizeof(c), sizeof(int));
printf("%llu\n", sizeof a); // 对于变量,括号可以省略,sizeof是操作符,不是函数
// printf("%llu\n", sizeof int); // 对于类型,括号是不可省略
int arr[] = {1, 2, 3};
printf("%llu\n", sizeof(arr) / sizeof(arr[0])); // 也可以用于计算数组元素个数
// (类型)变量 表示强制类型转换
int h = (int)3.14; // double -> int
h++;
// -----------------逻辑操作符----------------
// C语言中0为假,非0即为真
printf("%d %d %d\n", 0 && 0, 0 && 3, 3 && 5); // 逻辑与&&
printf("%d %d %d\n", 0 || 0, 0 || 3, 3 || 5); // 逻辑或||
// -----------------条件操作符(三目操作符)----------------
int x = 10, y = 20, max = 0;
max = (x >= y ? x : y);
printf("max=%d\n", max);
return 0;
}
关键字
#include <stdio.h>
// 常见关键字
// auto break case char const continue default do double else enum
// extern float for goto if int long register return short signed
// sizeof static struct switch typeof union unsigned void volatile while
// 标识符不能和关键字冲突
void test(void)
{
int x = 1;
x++;
printf("x = %d\n", x);
}
void test_static(void)
{
static int y = 1; // y是一个静态局部变量
y++;
printf("static y = %d\n", y);
}
extern Add(int, int);
int main(int argc, char* argv[])
{
// -------------auto------------------
auto int a = 0; // 局部变量又称自动变量,在前面都有auto,通常省略
// -------------break------------------
// break使用场景:(1)循环(2)switch case语句
// -------------register------------------
// 计算机中存储数据的位置:(1)寄存器(2)高速缓存(3)内存(4)硬盘
// 对于经常使用的变量b,使用register关键字可以建议编译器将b定义成寄存器变量
// 是否采纳取决于编译器
register int b = 10;
// -------------signed/unsigned------------------
signed int c = -2; // int定义的变量默认是有符号的,signed是省略的
unsigned int d = 10; // 定义无符号整数
// -------------typedef------------------
// 类型重定义
typedef unsigned int u_int;
u_int e = 10; // e和f的类型都是unsigned int,u_int只是别名
// -------------static------------------
// 场景一:static修饰局部变量 静态局部变量
// 局部变量的生命周期变长,出了作用域仍然存在,直至程序结束生命周期才结束
int i = 0;
while (i < 5)
{
test(); // 2 2 2 2 2
test_static(); // 2 3 4 5 6
i++;
}
// 场景二:static 修饰全局变量 静态全局变量
// 改变了全局变量的作用域,只能在本源文件内使用,不能在其他源文件内使用
// 同级目录下test.c文件的内容:int g_num = 30;
extern int g_num;
printf("g_num = %d\n", g_num);
// 同级目录下test.c文件的内容:static int g_num = 30;
extern int g_num; // 编译报错: undefined reference to `g_num'
printf("g_num = %d\n", g_num);
// 场景三:static修饰函数 静态函数
// 改变了函数的链接属性,由外部链接属性变成了内部链接属性,只能在本源文件内使用,不能在其他源文件内使用
printf("%d\n", Add(3, 5));
return 0;
}
define
#include <stdio.h>
// #define定义标识符常量
#define N 100
// #define定义宏 - 带参数
#define MAX(X, Y) (X >= Y ? X : Y)
int main(int argc, char *argv[])
{
int a = N;
printf("a = %d\n", a);
printf("max = %d\n", MAX(10, 20));
return 0;
}
指针
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 10;
printf("%p\n", &a);
// 指针变量:用来存放地址的变量
int *p = &a;
printf("&p: %p\n", p); // p的类型是int*
// * 解引用操作符
printf("*p: %d\n", *p);
*p = 20; // 修改p指向对象a的值
printf("*p: %d\n", *p);
char c = 'w';
char *pc = &c;
*pc = 'h';
printf("%c\n", c);
// 指针大小在32位平台是4 Bytes,64位平台是8 Bytes
printf("%d bytes\n", sizeof(p));
printf("%d bytes\n", sizeof(pc));
printf("%d bytes\n", sizeof(char *));
printf("%d bytes\n", sizeof(short *));
printf("%d bytes\n", sizeof(int *));
printf("%d bytes\n", sizeof(long *));
printf("%d bytes\n", sizeof(long long *));
printf("%d bytes\n", sizeof(float *));
printf("%d bytes\n", sizeof(double *));
return 0;
}
结构体
#include <stdio.h>
#include <string.h>
/*
* 对于复杂对象,使用结构体创建自定义的类型
*/
// 创建一个结构体类型
struct Book
{
char name[20];
short price;
};
int main(int argc, char *argv[])
{
// 利用结构体类型创建一个该类型的结构体变量
struct Book b1 = {"C语言程序设计", 55};
printf("书名: 《%s》\n", b1.name);
printf("价格: %d元\n", b1.price);
b1.price = 15;
printf("修改后的价格:%d元\n", b1.price);
// b1.name = "C++程序设计"; // 数组名本质上是一个地址
strcpy(b1.name, "C++程序设计");
printf("修改后的书名:《%s》\n", b1.name);
// 结构体指针
struct Book *pb = &b1;
printf("书名:《%s》,价格:%d元\n", (*pb).name, (*pb).price);
printf("书名:《%s》,价格:%d元\n", pb->name, pb->price);
// .操作符 结构体变量.成员
// ->操作符 结构体指针->成员
return 0;
}