0%

Linux C编程

Linux C编程

C语言的源代码文件是一个普通的文本文件,但扩展名是c.而且源代码文件是不能直接执行的,需要编译,编译后的可执行文件只能在指定操作系统下运行.

Linux编译后的可执行程序只能在linux运行,windows编译后的程序只能在windows下运行

64位的linux编译后的程序只能在64位linux下运行,32位linux编译后的程序只能在32位的linux运行.

64位的windows编译后的程序只能在64位windows下运行,32位windows编译后的程序可以在64位的windows运行.

头文件包含

include有两种写法

#include <文件名>,如果文件在系统目录下,那么需要用<>

#include “文件名”,如果文件在当前目录下,那么用””

System系统调用

在使用system之前需要包含stdlib.h这个头文件,system主要的功能是通过程序执行另外一个程序

system("命令");

如果在命令行执行一个程序,那么这个程序的调用者就是操作系统,如果在代码中通过system执行一个程序,那么这个程序的调用者就是自己写的代码本身.

C语言所有的库函数调用,只能保证语法是一致的,但不能保证执行结果是一致的,同样的库函数在不同的操作系统下执行结果可能是一样的,也可能是不一样的。

POSIX标准

POSIX是一个标准,只要符合这个标准的函数,在不同的系统下执行的结果就可以一致。

Unix和Linux很多库函数都是支持POSIX的,但windows支持的比较差。

如果将unix代码移植到linux一般代价很小,如果把windows代码移植到unix或者linux就比较麻烦.

C语言处理过程

C代码编译成可执行程序经过4步:
1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接: C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去

预处理:

gcc -E hello.c -o hello.i

编译:

gcc -S hello.i -o hello.s

汇编:

gcc -c hello.s -o hello.o

链接:

gcc hello.o -o hello

查看程序所需动态库:

ldd hello   //linux

交换文件说明:
1) vi写文件,没有保存就关闭,自动生成一个后缀为. swp交换文件, 保存了之前写的内容
2)先恢复,再删除.swp交换文件

vi -r {your file name} //恢复
rm {your file name}.swp //删除

常量和变量

关键字

C的关键字共有32个

  • 数据类型关键字(12个)

    char, short, int, long, float, double,unsigned, signed, struct, union, enum, void

  • 控制语句关键字(12个)

    if, else, switch, case, default,for , do, while, break, continue, goto, return

  • 存储类关键字(5个)

    auto,extern,register, static,const

  • 其他关键字(3个)

    sizeof, typedef, volatile

数据类型

数据类型的作用:编译器预算对象(变量)分配的内存空间大小

image-20210111193755278

常量

  • 在程序运行过程中,其值不能被改变的量
  • 常量一般出现在表达式或赋值语句中

变量

  • 在程序运行过程中,其值可以改变
  • 变量在使用前必须先定义,定义变量前必须有相应的数据类型

标识符命名规则:

  • 标识符不能是关键字

  • 标识符只能由字母、数字、下划线组成

  • 第一个字符必须为字母或下划线
  • 标识符中字母区分大小写

变量特点:

  • 变量在编译时为其分配相应的内存空间
  • 可以通过其名字和地址访问相应内存

声明和定义区别:

  • 声明变量不需要建立存储空间,如: extern int a;(a不能赋值)
  • 定义变量需要建立存储空间,如: int b;
  • -般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为”声明”.

进制

C语言如何表示相应的进制数

十进制 以正常数字1-9开头,如123
八进制 以0(零)开头,如0123
十六进制 以0x开头,如0x123
二进制 C语言不能直接书写二进制数

计算机内存数值存储方式

在计算机系统中,数值一律用补码来存储。

原码:

最高位为符号位:0代表正数,1代表负数​

原码存储导致2个问题:

  1. 0有两种存储方式
  2. 正数和负数相加结果不正确

反码:

反码是为了算补码.正数的原码和反码是一样的,负数的反码在原码基础上,符号位不变,其它位取反(0为1,1变0 ).

反码存储导致1个问题:

  1. 0有两种存储方式

补码:

正数的原码,反码,补码都一样

负数的补码为其反码加1

十进制数,站在用户角度看,原码;二进制,八进制,十六进制,要站在计算机角度看,补码.

原码求补码:

  1. 正数原,反,补码均相同
  2. 负数原码最高位不变,其他位取反得反码
  3. 反码加1得补码

补码求原码(同上面类似):

  1. 正数原,反,补码均相同
  2. 负数补码最高位不变,其他位取反得反码
  3. 反码加1得原码
#include<stdio.h>
int main()
{
char a=0x81;//a是值为十六进制数的字符,在计算机看来是补码
printf("%d\n",a);//%d为输出十进制数,在用户看来是原码
return 0;
}

上面程序运行结果如下:

按照十六进制转十进制,0x81等于129而不是-127

分析:

  1. 0x81在计算机角度看来应为补码存储,其二进制形式为10000001
  2. 输出%d即输出十进制数,十进制数在用户角度为原码
  3. 由(1)(2)知,该程序即将0x81的补码转换为原码
  4. 其原码为11111111,即-127

有符号和无符号的区别:

  • %d,默认以有符号的方式打印
  • %u,默认以无符号的方式打印
  • 有符号,最高位是符号位,如果是1代表为负数,如果为0代表为正数
  • 无符号,最高位不是符号位,是数的一部分,无符号不可能是负数

数据类型范围:

char 1个字节

image-20210112214358475

数值越界:

通过以下代码解释

#include<stdio.h>
int main()
{
//情况1 有符号
//char 范围 -128~127
char a=127+2;
/* a=129,转换二进制为1000 0001,
二进制(1000 0001)以补码形式存储,
输出为%d,要求得原码(11111111),
即-127
*/
printf("%d\n",a);//输出-127
//情况2 无符号
//无符号 范围 0~255
unsigned char b=255+2;
/* b=257,转换二进制为1 0000 0001,
舍弃最高位为0000 0001,
二进制(0000 0001)以补码形式存储,
转为原码(0000 0001),
即1
注意编译时会警告,之后./文件名运行即可
*/
printf("%u\n",b);
return 0;
}

运行结果如下:

image-20210113191321188

sizeof关键字

  • sizeof不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节
  • sizeof的返回值为size_ t
  • size_t 类型在32位操作系统下是unsigned int,是一个无符号的整数
#include<stdio.h>
int main()
{
/*数据类型的作用:告诉编译器,
定义此类型变量需要分配多大空间*/ printf("sizeof(char)=%lu\n",sizeof(char));
int a;
printf("sizeof(a)=%lu\n",sizeof(a));
return 0;
}

运行结果如下:

%d,%o,%x,%u等均以四个字节形式打印.

short占两个字节,short a;与short int a;等价.

数据类型 占用空间
short (短整型) 2字节
int (整型) 4字节
1ong(长整形) Windows为4字节,Linux为 4字节(32位),8字节(64位)
long long (长长整形) 8字节

字符型变量

字符型变量用于存储一个单一字符,在C语言中用char 表示,其中每个字符变量都会占用1个字节。在给字符型变量赋值时,需要用一对英文半角格式的单引号(‘ ‘)把字符括起来。
字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的ASCII 编码放到变量的存储单元中。char 的本质就是一个1 字节大小的整型。

使用

man ascii

查看ASCII码

转义字符

字符在单引号内,原则上’ ‘内部只有一个字符,转义字符除外,不能char a=’abc’

转义字符由反斜杠\组成的多个字符

例子

#include<stdio.h>
int main()
{
char a='\r';//光标移到句首
char b='\b';//退格
printf("12345%c6789\n",a);//先打印12345,之后光标移到句首再依次打印6789,其中1234被覆盖,留下5,结果为67895
printf("abcde%cfghi\n",b);//先打印abcde之后打印\b,退一格,e就没了,结果为abcdfghi
return 0;
}

浮点型(实型)

实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。在C语言中,浮点型变量分为两种:单精度浮点数(float)、双精度浮点数(double),但是double型变量所表示的浮点数比float 型变量更精确。

数据类型 占用空间 有效数字范围
float 4字节 7位有效数字
double 8字节 15~ 16位有效数字

float存储不准确


#include<stdio.h>
int main()
{
float a=100.9;
printf("%f\n",a);
return 0;
}

类型限定符

限定符 含义
extern 声明一个变量,extexn 声明的变量没有建立存储空间。extern int a;
const 定义一个常量,常量的值不能修改。const int a 10;
vo1atile 防止编译器优化代码
register 定义寄存器变量,提高效率。 register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那么register就生效, 如果没有空闲寄存器,那么register无效。

输出

字符串常量与字符常量的不同:
‘a’为字符常量,”a”为字符串常量

image-20210113191321188

每个字符串的结尾,编译器会自动的添加一一个结束标志位’\0’,即”a” 包含
两个字符’a’和’\0’。

%%在屏幕输出一个%,后面的d也会输出

%5d,以5个字符输出,没有的字符以空字符填充,默认是右对齐

%05d,以5个字符输出,没有的字符以0填充,默认是右对齐

%-5d,以5个字符输出,没有的字符以空字符填充,-代表指定为左对齐

0和-不能同时使用

printf

遇到”\0”停止

puts()

#include<stdio.h>
int main()
{
char buf[]="hello";
puts(buf);//把buf内容输出到屏幕,自动在屏幕加换行
printf("%s",buf);//没有加换行
return 0;
}

fputs()

#include<stdio.h>
int main()
{
char buf[]="hello";
//向stdout(代表屏幕,标准输出)输出buf的内容
fputs(buf,stdout);//没有加换行
return 0;
}

image-20210129141616052

输入

scanf

scanf()的缺陷,不做越界检查,不允许有空格

#include<stdio.h>
int main()
{
char a[5]={0};
printf("请输入字符串:\n");
scanf("%s",a);
printf("a=%s\n",a);
return 0;
}

gets()

gets()从键盘读取字符串,放在指定的数组
gets()允许有空格,不做越界检查,此函数不安全

fgets()

#include<stdio.h>
int main()
{
char buf[10];
//从stdin(代表标准输入,键盘)读取内容
/*如果输入内容大于sizeof(buf)-1,只取
sizeof(buf)-1,放在buf所在数组;
当不足sizeof(buf)-1,会把换行符读进去*/
fgets(buf,sizeof(buf),stdin);
printf("buf=%s\n",buf);
return 0;
}

fgets()允许有空格

当不足sizeof(buf)-1,会把换行符读进去

#include<stdio.h>
int main()
{
char buf[10];
fgets(buf,sizeof(buf),stdin);
printf("buf=#%s#\n",buf);
return 0;
}

输入字符

#include<stdio.h>
int main()
{
char a;
printf("请输入字符:\n");
scanf("%c",&a);//假设输入c
printf("a='%c'\n",a);
char b;
printf("请输入字符:\n");
scanf("%c",&b);//假设输入d
printf("b='%c'\n",b);
return 0;
}

上面这个程序看似简单,结果为a=c,b=d,其实不然,结果如下:

无需输入d直接出结果

原因:

当用户输入字符时,编译器默认把输入的内容先放在一块内存中(缓冲区) , scanf()自动在缓冲区读内容(只读一个字符).第一次输入时输入了c\n(回车),scanf取走了c,留下\n还在缓存区,第2次scanf ,由于缓冲区还有内容,所以直接取内容,无需再输入

所以,上面的程序可以这样写

#include<stdio.h>
int main()
{
char a;
printf("请输入字符:\n");
scanf("%c",&a);//假设输入c
printf("a='%c'\n",a);
//吃掉上一步的\n,不做处理
char b;
scanf("%c",&b);
char t;
printf("请输入字符:\n");
scanf("%c",&t);//假设输入d
printf("t='%c'\n",t);
//或者都在一个scanf中写
return 0;
}

输入字符串

#include<stdio.h>
int main()
{
char a[100]={0};
printf("请输入字符串a:\n");
scanf("%s",a);//假设输入hello world
printf("a=%s\n",a);
printf("请输入字符串b:\n");
char b[100]={0};
scanf("%s",b);
printf("b=%s\n",b);
return 0;
}

运行结果如下:

原因:

当输入hello world时,第一个scanf取走第一个空格前的字符串,即hello;第二个scanf取第一个空格后第二个空格前的内容,\\n不取

switch语句

#include<stdio.h>
int main()
{
int num;
printf("请输入:\n");
scanf("%d",&num);
//1、switch是关键字, switch() 后面没有分号
//2、switch()中() 里只能放整型变量或字符型变量
switch(num)
{
case 1:
printf("1楼\n");
break;
case 2:
printf("2楼\n");
break;
case 3:
printf("3楼\n");
break;
default:
printf("其他楼层\n");
break;
}
}

goto语句

goto(只能跳转到同一作用域)任意地方都能使用,无条件跳转,不能滥用,代码会很乱

#include<stdio.h>
int main()
{
goto hello;
printf("1111111111\n");
printf("2222222222\n");
hello:
printf("3333333333\n");
}

image-20210122205440917

类型转换:

1.隐式转换

double a;
int b=1;
//编译器自动转换,把b转 换为double类型后, 再给a赋值(隐式转换)
a= b;
printf("%lf\n",b) ;

2.强制类型转换

int a=10;
//a只有在此语句执行时,才强制转换为double类型,其它地址还是为int类型
printf ("%lf\n",(double)a) ;//(类型)变量名

转换原则:占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。

3.浮点型和整型打印问题
a)不要直接通过%d,打印一个浮点型变量,得不到想要的结果,要想得到结果,强制类型转换

double a = 11.11 ;
printf("%d\n",(int)a) ;

b)不要直接通过%f或%lf,打印一个整型变量,得不到想要的结果,要想得到结果,强制类型转换

数组

  1. 数组内部的变量或数组,不初始化,它的值为随机数
  2. 部分初始化,其它自动初始化为0
    0~2(前3个元素)分别为1,2, 3, 其 它初始化为0
    int a1[10] = {1, 2, 3};
  3. 数组全部元素初始化为0
    int a2[10] = {0} ;
  4. 如果定义时同时初始化,第1个[]内部可以不写内容
    编译器会根据用户初始化的元素来确定数组的大小
    int a3[]={1,2,3,4,5,6,7,8,9,10};
  5. 如果第1个[]内容不写,必须初始化,否则语法错误

数组名

  • 数组名是常量,不能修改
  • 数组名是数组首元素地址
#include<stdio.h>
int main()
{
int a[10];
printf("a=%p,&a[0]=%p\n",a,&a[0]);//%p输出地址
printf("sizeof(a)=%lu\n",sizeof(a));
return 0;
}

二维数组

内存中没有多维,只有一维,多维数组是特殊的一维数组

//如果定义时,同时初始化,第1个[]可以不写内容
int a1[][4]={0,1,2,3,4,5,6,7,8,9,10,11};//正确
//如果第1个[]不写,必须初始化
int a3[][4]; //错误

字符数组

  1. C语言没有字符串类型,用字符数组模拟
  2. 字符串一定是字符数组,字符数组就不一定是字符串
  3. 如果字符数组以字符’\0’ (‘\0’等级于数字0)结尾,那么这个字符数组就是字符串
//1、C语言没有字符串类型,用字符数组模拟
char a[10];
//2、字符串一定是字符数组,字符数组就不一定是字符串
//3、如果字符数组以字符'\0'('\0'等级于数字0)结尾,那么这个字符数组就是字符串
char b[] = {'a','b','c'}; //字符数组,方括号中不能填数字,否则会自动补零,就变成字符串
char c[10] = {'a', 'b','c','\0'}; //字符串
char d[10] = {'a','b','c',0}; //字符串
#include<stdio.h>
int main()
{
char a[]={'a','b','c','d','e','f'};
printf("a=%s\n",a);//会乱码,没有结束符
char b[]={'a','b','c','\0','d','e','f'};
printf("b=%s\n",b);//输出abc,遇到\0就停止
return 0;
}

字符数组初始化(常用)

char a[10]="abcde";//最多写9个字符,留一个放结束符
printf("a=%s\n",a);
char b[]="abcde";
printf("b=%s\n",b);//sizeof()测数据类型大小,不会因为结束符提前结束
printf("sizeof(b)=%lu\n",sizeof(b));//输出6,b这个数组有5个字符,会自动补加结束符(隐藏)

\0后面最好不要跟数字,有可能组成转义字符

#include<stdio.h>
int main()
{
char a[]="\0abc";
printf("a=%s\n",a);//输出为空,含有结束符\0
char b[]="\0417abcde";
printf("b=%s\n",b);//输出非空,\0与其后数字组成转义字符\041,通过man ascii命令查到\041对应!(感叹号)
return 0;
}

函数的调用:产生随机数

当调用函数时,需要关心5要素:

  • 头文件:包含指定的头文件
  • 函数名字:函数名字必须和头文件声明的名字一样
  • 功能:需要知道此函数功能后再调用
  • 参数:参数类型要匹配
  • 返回值:根据需要接收返回值
#include<stdio.h>
#include<stdlib.h>//产生随机数的函数包含在此
#include<time.h>//获取当前系统时间的函数包含在此
int main()
{
//先设置种子,种子设置一次即可
//srand(10);
/*如果srand()参数一样,则每次产生的随机数一样,
每次启动计算机以后,种子就是定值了,
所以根据公式推算出来的结果
(也就是生成的随机数)就是固定的*/
/*time(NULL)用来获取系统当前时间,
由于时间会变,srand()也会改变*/
srand((unsigned int)time(NULL));
int i=0;
int num;
for(;i<5;i++)
{
num=rand();//rand()产生随机数
printf("num=%d\n",num);
}
return 0;
}

函数

strlen

strlen需要使用返回值,返回值就是字符串的长度,从首元素开始,到结束符为止的长度,结束符不算(遇到’\0’结束)

strcpy

拷贝原理:从首元素开始,到结束符(\0)为止的长度

strncpy

可以把”\0”拷贝过去,但是”\0”后面的就不能了

-------------本文结束感谢您的阅读-------------