关于Linux C的初级编程,在麦子学院上对应于5小节的课程:
- c语言语法概述
- c语言内存操作
- c语言函数使用
- linux 操作系统基础
- GNU工具简介
因为内容有点多,本篇文章主要总结了前3节的C语言基础部分。
后2节的linux部分请看下一篇文章:https://blog.csdn.net/QiHsMing/article/details/85961964
c语言语法概述
GCC的使用及其常用选项介绍
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection
GCC的常用命令:
详细教程请查看:https://blog.csdn.net/typename/article/details/8170213
命令 | 描述 | |
---|---|---|
1 | gcc mian.c | 指定输出可执行文件的名字执行完上面这句命令,会在当前目录下输出一个名为a.out的可执行文件。 |
2 | gcc -o main main.c | 使用 -o选项可以指定输出的可执行文件名称, -->预处理+编译 +汇编 +链接 |
3 | gcc -Wall main.c -o main | 让所有编译警告都显示出来 |
4 | gcc -E main.c > main.i | 只输出预编译结果 |
5 | gcc -S main.c > main.s | 输出汇编代码 -->预处理+编译 |
6 | gcc -C main.c | 输出编译后的代码,生成.maino文件 -->预处理+编译 +汇编 |
7 | gcc -save-temps main.c | 输出所有的中间代码(a.out main.c main.i main.o main.s) |
8 | gcc -Wall main.c -o main -lpthread | 链接共享库(动态链接库)指定编译选项 -lxxx,比如使用多线程需要链接 -lpthread共享库 |
9 | gcc -Wall -v main.c -o main | 打印输出有个执行过程的信息 使用-V选项 |
10 | gcc -Wall -ansi main.c -o main | 指定编译选项-ansi,支持ISO C89 programs. |
11 | gcc -Wall -funsigned-char main.c -o main | 指定编译选项 -funsigned-char选项将char类型解释为unsigned char类型 |
12 | gcc -Wall -fsigned-char main.c -o main | 指定编译选项 -fsigned-char选项将unsigned char类型解释为 char类型 |
13 | gcc -Wall -D MY_MACRO main.c -o main | 指定-D选项 开启编译时的宏 |
14 | gcc -Wall -Werror main.c -o main | 将编译警告转换成错误 |
15 | gcc main.c @opt_file | 通过文件指定编译选项,指定@编译选项 |
c语言常见错误
c语言常见错误有以下几类:
- 预处理错误:一般是找不到头文件。
- 编译错误:语法错误,内存错误。
- 链接错误:原材料不够,原材料多了。
预处理
关键字 | 用法 | 意义 |
---|---|---|
include | #include 头文件 | 包含头文件 |
define | #define 宏名 宏体 | 相当于常量,不进行语法检查 |
ifdef else endif | #ifdef 宏名 #else 宏体 #endif | 常与gcc -D同时使用 |
条件编译的使用代码:
#include <stdio.h>
int main()
{
ifdef ABC
printf(“====%s====”,__FILE__);
#endif
printf("Hello World !");
return 0;
}
编译时,使用gcc -D命令:
gcc -DABC -o test 003.c
预定义宏
__FUNCTION__ //函数名
__LINE__ //行号
__FILE__ //文件名
预定义宏的使用代码:
#include<stdio.h>
int fun()
{
int a;
printf(" the %s,%s,%d\n",__FUNCTION__,__FILE__,__LINE__);
return 0;
}
int main()
{
fun();
return 0;
}
宏展开下的#与##的使用
# 字符串化
## 宏序列化
格式:
#define ABC(x) #x
#define ABC(x) day##x
#的使用代码:
#include <stdio.h>
#define ABC(x) #x
int main()
{
printf(ABC(ab\n)); //相当于printf("ab\n") 输出ab\n
return 0;
}
##的使用代码:
#include <stdio.h>
#define DAY(x) day##x
int main()
{
int day1 = 10;
int day2 = 20;
printf("the day is %d\n",DAY(1)); //输出10
return 0;
}
C语言常用关键字及运算符操作
关键字
关键字概念:编译器预先定义了一定意义的字符串。
sizeof:编译器给我们查看内存空间容量的一个工具。
return:定义函数的返回值。
数据类型关键字:限制内存大小的关键字。
常用的数据类型有
数据类型 | 位宽 |
---|---|
char | 8bit |
short | 16bit |
int | 32bit |
long | 64bit |
float | 23bit |
double | 64bit |
自定义数据类型:struct,union,enum,typedef。
struct结构体:元素之间的和
struct 结构体名{
成员1
成员2
...
}
union联合体:元素之间的累积,首地址共用。
union结构体名{
成员1
成员2
...
}
enum枚举:被命名的整形常量的集合。
enum 枚举名 { 常量列表}
例:
enum week{
Mondey = 0, Tuesday = 1, Webnesday = 2, Thursday, Friday, Saturday,Sunday
};
typedef:数据类型的别名。
逻辑结构:
逻辑结构包括:
a. 判断语句:if-else 、switch-case
b. 循环语句:do-while、for
c. 中断:continue、break、goto
类型修饰符:对内存资源存放位置的限定,资源属性中位置的限定。 有:register,static_const,volatile。
运算符
a.算术操作运算:+、-、、/、%、mod。
b.逻辑运算:&&、||、>、>=、<、<=、!、?:
c.位运算:~、&、|、^、<<、>>
赋值运算:=、+=、-=、&=
内存访问符:()、[]、{}、->、&、
C语言内存空间的使用
指针
指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。
指针变量 的大小由它的数据类型决定。指针的数据类型不同,可以导致取地址的内容不同。
例:
#include <stdio.h>
int main()
{
int a = 0x12345678;
int *p1;
char *p2;
p1 = &a;
p2 = &a;
printf ("the p1 is %x\n",*p1);
printf ("the p2 is %x\n",*p2);
}
编译后,运算结果:
指针+修饰符
修饰指针的修饰符有:const、volatile、typedef。
1)const :常量,只读。
写法1:const char *p; 通常修饰常用的字符串
写法2:char * const P; 通常修饰硬件资源
写法3:const char *const p;通常修饰地址不可变且内容不可变的ROM
例1:指针指向的内容被非法访问了
#include <stdio.h>
int main()
{
char *p = "Hello World !";
printf("the one is %x\n",*p);
*p = 'a';
printf("the %x\n",p);
}
运行结果:
例2:指针操作数组
#include <stdio.h>
int main()
{
char *p = "Hello World !";
char buff[] = {"Hello World !"};
char *p1 = buff;
printf("the one is %x\n",*p1);
*p1 = 'a';
printf("the %s\n",p1);
}
运行结果:
例3:const 修饰的指针变量不能改变
#include <stdio.h>
int main()
{
const char *p = "Hello World !";
char buff[] = {"Hello World !"};
char *p1 = buff;
printf("the one is %x\n",*p1);
*p = 'a';
printf("the %s\n",p1);
}
运行结果:
volatile:防止优化指向内存地址
typedef:为指针变量起别名
指针+运算符
在指针中的运算符有:(1)加减运算符 (2)逻辑运算符
(1)加减运算符:指针的加法减法运算,实际上加的是一个单位,单位的大小可以使用sizeof(p[0]),运算符有:++ 、–。
例:指针+运算符加减
#include <stdio.h>
int main()
{
int a = 0x123456789;
int b = 0x99991199;
int *p1 = &b;
char *p2 = (char *)&b;
printf("the p1+1 is %x,%x,%x\n",*(p1+1),p1[1],*p1+1);
printf("the p2+1 is %x\n",p2[1]);
}
运行结果:
(2)逻辑运算符:运算符有:>=、<=、==、!=
多级指针
多级指针:指向存放地址的地址空间。
例:
#include <stdio.h>
int main(int argc,char **argv)
{
int i = 0;
while(argv[i] != NULL){
printf("the argv[%x] is %s\n",i,argv[i]);
i++;
}
return 0;
}
运行结果:
数组
数组:内存分配的一种形式。
数组空间的初始化
一般原则,数组空间的赋值是按照标签逐一处理,但这样赋值的工作量比较大;让编译器进行自动处理,就是空间初始化。
数组初始化方式:
方式一: char buf[] =“abc”;
方式二:char buf[10] = “abc”;
指针数组:数组中存放的是一组指针地址
数组名的指针保存
例1:定义一个指针,指向int a[10]的首地址
int a[10];
int *p a;
例2:定义一个指针,指向int b[5][6]的首地址
int b[5][6];
int (*p)[5]
结构体字节对齐
合理的内存对齐可以提高访问效率。为使CPU能够对数据进行快速访问,数据的起始地址应具有“对齐”特性。比如4字节数据的起始地址应位于4字节边界上,即起始地址能够被4整除。
关于结构体字节对齐具体参考:https://www.cnblogs.com/clover-toeic/p/3853132.html
例:
#include <stdio.h>
struct csi{
char a;
short b;
int c;
};
struct cis{
char a;
int c;
short b;
};
int main()
{
struct csi buf1;
struct cis buf2;
printf("csi : cis is %lu:%lu\n",sizeof(buf1),sizeof(buf2));
return 0;
}
运行结果:
内存分布
内存分布:https://www.cnblogs.com/yanhewu/p/8360541.html
https://blog.csdn.net/xyh930929/article/details/79496659
典型的C语言程序内存表示分区共有5个部分:
① 正文段 Text segment
② 已初始化数据段(数据段)Initialized data segment
③ 未初始化数据段(bss)Uninitialized data segment
④ 堆 Stack
⑤ 栈 Heap
内存区域 | 存放 | 作用 |
---|---|---|
text 段 | 字符串常量和函数体的二进制代码 | 通常可共享,只读 |
data段 | 已初始化全局变量、静态变量 | 通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 |
bss段 | 未初始化全局变量、静态变量 | 通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS段属于静态内存分配。(注意:即使是赋值为0也是未初始化!) |
Stack | malloc内存分配 | 用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。 |
Heap | 局部变量、在函数调用时的形参和返回值 | 为最近被调用的函数分配自动变量和临时变量的存储空间 |
具体分布图
C语言函数的使用
函数的概述:一堆代码的集合,具有复用性。
函数的三要素:①函数名②参数③返回值
1. 形参与实参
在代码中,函数有两种形式:调用者和被调者。
调用者:
函数名(要传递的数据) //实参
被调者:
函数名(接收的数据) //形参
{
函数的具体实现
}
函数实参形参拷贝举例:
运行结果:
2. 值传递、地址传递和连续空间传递
传递:实参和形参的数据传递
① 值传递:将实参的数据copy给形参,不改变实参的数据,函数调用完后,形参数据销毁。
值传递的作用:上层调用者保护自己的空间值不被修改。
值传递示例:
运行结果:
② 地址传递:将实参的地址传给形参。形参的数据被修改的同时,实参的数据也会被修改。
地址传递的作用:上层调用者,让下层函数修改自己的空间值。
注意:static修饰的变量进行传参的,都是地址传递。
地址传递示例:
运行结果:
③连续空间传递:包括数组空间传递和结构体空间传递,连续空间的传递大都是地址传递。
数组空间传递:都是地址传递
int abc[10]; //定义
实参:
fun(abc);
形参:
void fun(int *p) 等价于 void fun(int p[10])
结构体空间传递:
struct abc {int a,int b,int c}; //定义
struct abc buf; //别名
实参:
fun(buf); //值传递
fun(&buf); //地址传递
形参:
void fun(struct abc a) //值传递
void fun(struct abc *a) //地址传递
面试常见的知识点:
1)宏定义,可以是表达式,编译过程中已经计算了表达式
#define SECOND_OF_YEAR (365*24*3600)UL
2)数据声明
用变量a给出下面的定义
①一个整形数
int a;
②一个指向整型数的指针
int *a
③一个指向指针的指针,它指向的指针是指向一个整型数
int **a
④一个有10个整型数的数组
int a[10]
⑤一个有10个指针的数组,该指针是指向一个整型数的
int *a[10]
⑥一个指向有10个整型数数组的指针
int (*a)[10]
⑦一个指向函数的指针,该函数有一个整形参数并返回一个整形数
int * fun(int a);
⑧一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整形数
int (*fun[10])(int a)
3)修饰符: