操作系统: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;
}