c语言学习

c语言学习

预备知识

gcc
编译器, 预处理(后缀名.i; 去除注释, 处理以#开头的, #ifdef #include) -> 编译(.o目标文件) -> 汇编 -> 链接

步骤

  1. 预编译 gcc -E 文件名 -o .i为后缀的文件
  2. 编译, 生成汇编 gcc -S 预编译的.i文件 -o .s为后缀的汇编语言
  3. 汇编过程, gcc -c .s为后缀的汇编语言 -o .o为后缀的目标代码
  4. 链接, gcc .o为后缀的目标代码 -o 要链接的文件名

想要调试 需要加上 -g
gcc -o 要编译的文件名 编译的c文件

  1. 枚举: 吧一个一个的可能性都列举出来,然后一个一个的去试,直到找到能够满足我们条件的
  2. 解释: 借助一个程序, 并且这个程序能够理解我们写的程序, 然后按照要求指定
  3. 编译: 借助一个程序, 并且这个程序能够将我们写的程序翻译成计算机能够懂的语言, 然后直接交给计算机执行就好了

两个整数的计算结果只能是整数,当浮点数和整数放在一起运算时,会将整数转换成浮点数,然后再进行浮点数的运算

表达式是一系列运算符和算子的运算
运算符是指进行运算的动作, 比如加减运算符
算子是参与运算的值,可能是常数,也可能是变量,还有可能是一个函数的返回值

变量

  1. 变量必须先声明后使用
  2. 各条语句均为分号结束

通用规则: 在允许使用某种类型变量值的任何场合,都可以使用该类型的更复杂的表达式

#define指令可以把符号名(或者符号常量)定义为一个特定的字符串:
替换文本可以是任何字符序列,而不仅仅是数字
语法: #define 名字 替换文本

在c语言中,所有的函数参数都是通过 值 传递的

通常把extern声明放在一个单独的文件中(习惯上称为头文件),并在每个源文件的开头使用#include语句吧索要用的头文件包含进来
后缀名 .h 预定为头文件名的扩展名

extern关键字: 需要在一个源文件中引用另一个源文件中定义的变量

#define是在预编译阶段展开,不分配内存,const常量是编译运行阶段使用,分配内存

define(定义) 表示创建变量或分配储存单元
declaration(声明) 指的是说明变量的性质,但并不分配储存单元

printf()函数:

  1. %d 表示按照十进制整数打印
  2. %6d 按照十进制整数打印,至少6个字符宽
  3. %f 按照浮点数打印
  4. %6f 按照浮点数打印,至少6个字符宽
  5. %.2f 按照浮点数打印,小数点后面有两位小数字
  6. %6.2f 按照浮点数打印,至少6个字符宽,小数点后面有两位小数字

全局变量和局部变量在内存中的区别:

  1. 全局变量保存在内存的全局储存区中,占用静态的储存单元
  2. 局部变量储存在栈中,只有在所在的函数被调用时才会动态的为变量分配储存单元

变量和常量

变量,常量是程序处理的两种基本数据对象

变量声明
变量声明向编译器保证变量以指定的类型和名称存在,变量声明有两种情况

  1. 一种是需要建立储存空间的,如int a在声明时都已经建立了空间
  2. 另一种是不需要建立储存空间的,通过extern关键字声明的变量而不定义它,例如 extern int a 其中变量a可以在别的文件中定义

数据类型:
void类型: 指定没有可用的值

  1. 函数返回值为空
  2. 函数参数为空
  3. 指针指向void: 类型为void*的指针代表对象的地址,而不是类型.例如 内存分配函数void*malloc(size_t size); 返回指向void的指针,可以转换为任何数据类型

基本数据类型
char 字符型, 占一个字节,
int 整形, 反映了所用机器中整数的自然长度
float 单精度浮点型
double 双精度浮点型

short和long限定符(用于整型, int可以省略):
short int sh; 通常为16位
int 为16位或者32位,在short和long之间
long int counter; 通常为32位

类型限定符signed与unsigned(用于char或整型):
unsigned类型的数总是0或正值

const常量:

  1. long型常量以字母l或L结尾,
  2. 无符号常量(unsigned)以字母u或U结尾,
  3. 后缀ul或UL表明是unsigned long结尾
  4. 没有后缀的浮点型常量是double类型, 后缀为f或F表示是float类型;后缀是l或L 表明是long double类型

字符常量也叫字符串字面量, 就是用双引号括起来的0个或多个字符组成的字符序列

字符常量和仅包含一个字符的字符串之间的区别: 'x'和"x"是不同的, 前者是一个整数,其值是一个整数, 其值是字母x在机器字符集中对应的数值(内部表示值);后者是一个包含了一个字符(字母x)以及一个结束符'\0'的字符数组

任何变量的声明都可以使用const限定符限定,该限定符指定变量的值不能被修改; 对数组而言, const限定符指定数组所有元素的值都不能被修改

运算符
算数运算符, 关系运算符, 逻辑运算符

类型转换
类型转换:当一个运算符的几个操作数类型不同时, 就需要通过一些规则吧他们转换为某种共同的类型

自动类型转换: 吧"比较窄的"操作数转换为"比较宽的"操作数, 并且不丢失信息的转换

自动类型规则:

  • 如果其中一个操作数为long double, 则将另一个操作数转换为long double类型
  • 如果其中一个操作数为double类型, 则将另一个操作数转换为double类型
  • 如果其中一个操作数的类型为float, 则将另一个操作数转换为float类型
  • 将char与short类型的操作数转换为int类型
  • 如果其中一个操作数的类型为long, 则将另一个操作数也转换为long类型
  • 字符类型的变量都将被转换为整型变量

表达式float类型的操作数不会转换为double类型

强制类型转换: 强制类型转换只是生成一个指定类型的n的值, n本身的值并没有改变.

按位操作符

  • &(按位与): 按位与经常用于屏蔽某些二进制位
  • |(按位或): 按位或常用于将某些二进制位置为1
  • ^(按位异或): 按位异或运算符^ 当两个操作数的对应位不相同时将该位位置设为1,否则设置为0
  • 移位运算符<<(左移)>>(右移): 左移乘以2的几次方,右移除以2的几次方
  • ~取反: 加一取反

流程控制语句

  • if else 分支,尽可能减少循环语句中的判断次数
  • switch 多路判断
  • while
  • for
  • do{} while();

break和continue

goto语句: 最常见的用法是终止程序在某些深度嵌套的结构中的处理过程

函数与程序结构

函数是吧大的计算任务分解成若干个小的任务

c中的储存类

c中的储存类定义了程序中变量/函数的储存范围(可见性)和声明周期

  1. auto: 所有局部变量默认的储存类,只能用在函数中,就是说它只能修饰局部变量
  2. register: 储存类用于定义储存在寄存器而不是RAM中的变量; 这意味着它的最大尺寸等于寄存器的大小,且不能对他使用一元"&"运算符,(因为它没有内存位置)
  3. static: 全局声明的static变量或方法可以被任何函数或方法调用,前提是这些方法跟static变量或方法在同一文件中
    1. 修饰局部变量时,指示编译器在程序的生命周期内保持局部变量的存在,因此,使用static修饰的局部变量可以在函数调用之间保持局部变量的值
    2. 修饰全局变量,会使全局变量的作用域限制在声明它的文件中
  4. extern: 用于提供一个全局变量的引用,全局变量对所有文件都可用。当使用extern时,对于无法初始化的变量,会把变量名指向一个之前定义过的储存位置
    • 当有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用extern来得到已定义的变量或函数的引用。
    • 可以这么理解,extern是用来在另一个文件中声明一个全局变量或函数
    • extern修饰符通常用于当有两个或多个文件共享相同的全局变量或函数时使用

数组

数组表示一段连续的,固定长度(长度不可改变)且类型相同的储存空间

声明定义数组

  1. 指定长度和元素: int arr[5] = {1, 2, 3, 4, 5};
  2. 不指定长度,编译器会根据值的多少来计算出数组的长度: int arr[] = {1,2,3,4,5};
  3. 指定部分元素,其余的元素取默认值(整型是0,字符型是'\0',指针是NULL): int arr[5] = {1,2,3};
  4. 指定数组的默认值: int arr[5] = {0,0,0,0,0} 等价于=> int arr[5] = {0};

取值赋值

数组的下表是从0开始的,并且数组中的每一个元素都属于同一个数据类型

定义数组: 数组类型 数组名[长度]
取值: 数组名[下标]
赋值: 数组名[下标] = 值

多维数组

// 多维数组类似与矩阵
// 声明多维数组
int arr[2][3] = {{1,2,3}, {4,5,6}};
// 可以只是用一个大括号,编译器会自动算出
int arr1[][3] = {1,2,3,4,5,6};
// 可以不指定第一维的长度,编译器会自动算出,但是必须指定第二维或更高维度的长度
int arr2[][3] = {1,2,3,4,5,6};

// 遍历多维数组
// 1. 直接遍历
void print_a(int arr[][3], int n, int m) {
    int i,j;
    for(i = 0; i < n; i++) {
        for(j = 0; j < m; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
// 2. 将一维度指定为一个指针
void print_b(int (*arr)[3], int n, int m) {
    int i,j;
    for(i = 0; i < n; i++) {
        for(j = 0; j < m; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
// 3. 降维的方式
// 调用方式print_c(&arr[0][0], 第一维长度, 第二维长度)
void print_c(int *a, int n, int m) {
    int i,j;
    for(i = 0; i < n; i++) {
        for(j = 0; j < m; j++) {
            printf("%d ", *(a + i * m + j));
        }
        printf("\n");
    }
}

传递数组给函数

// 需要告诉编译器函数将要接受一个指针
// 所以需要以下三种方式来声明函数
// 1. 形参是一个指针
//    指针指向的是数组的第一个元素,
//    可以使用 *(param + 1), 来访问数组的第2个元素
void myFunction(int *param) {
    printf("%d ", *param);
    // 访问数组第二个元素
    printf("%d ", *(param + 1));
}

// 指向数组的指针
// 以下测试数组和 以指针方式传入函数
int main(){}
    int arr[5] = {1,2,3,4,5};
    printf("%p \n", arr);  // 打印数组的地址
    printf("%p \n", &arr[0]);  // 打印数组第一个元素的地址
    printf("%p \n", &arr[1]);  // 打印数组的第二个元素的地址
    void tranArr(int *p);
    tranArr(arr);
    return 0;
}
void tranArr(int *p) {
    // 1. 通过 p[下标] 来访问数组元素
    // 2. 通过 *(p + 下标) 来访问数组的当前下标的元素
    // printf("%d ", *p);
    // printf("%d ", *(p + 1));
    printf("%p \n", p);  // 打印指针的地址,可以看出与数组和数组第一个元素的地址一样
    printf("%p \n", &p[1]);  // 打印数组第二个元素的地址,可以看出与数组的第二个元素的地址一样
    for(int i=0;i<5;i++) {
        printf("%d ", p[i]);
    }
}

// 2. 指定长度的形参数组形式
void myFunction1(int arr[10]) {
}

// 3. 不指定长度的形参数组形式
void myFunction2(int arr[]) {
}

字符数组

数组中的元素都是字符(以单引号括起来的并且只有一个单词或符号);取值,赋值和整型数组都一样

声明字符数组
char carr[5] = {'a','b','c','d'};
char carr1[5] = {'\0'};

字符串
c语言中使用字符数组表示字符串,字符串是由一个一个字符组成,\0标志着字符数组的结束

声明字符串

// 声明一个字符串 abc; 注意: \0是字符串的结束标识符
char str1[4] = {'a','b','c','\0'};
char str2[] = {"abc"};
char str3[] = "abc";

枚举enum

声明枚举类型 语法: enum 枚举名 {枚举元素1, 枚举元素2, 枚举元素3, ...}

以上仅仅是对枚举类型的声明,要想使用还需要定义枚举变量,c中枚举是当作int或unsigned int 来处理的

// 1. 先声明枚举类型后定义枚举变量
enum week {
    MON = 1,
    TUE,  // 以下会在MON的值基础上自动加1
    WED,
    THU,
    FRI
};
enum week w = MON;  // 声明定义枚举变量,并赋值为 MON

// 2. 声明并定义枚举变量
enum week1 {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI = 10
} we;

// 3. 省略枚举名称,直接定义枚举变量
enum {
    MON,
    TUE,
    WED,
    THU,
    FRI
} wee;

// 可以将整型变量强转为枚举类型
enum week {
    MON,TUE,WED
}
int main() {
    int a = 1;
    // 1. 定义枚举变量
    enum week wee = FRI;
    // 2. 强转
    wee = (enum week)a;
}
posted @ 2021-03-07 12:36  final老A  阅读(78)  评论(0编辑  收藏  举报