栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

嵌入式工程师

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

嵌入式工程师

嵌入式工程师 c++ 命名空间 命名空间的使用

命名空间的定义必须定义在全局范围

命名空间下可以存放变量、函数、结构体、类

命名空间可以重名,重名命名空间相当于合并操作

命名空间可以嵌套命名空间

无名命名空间,意味着命名空间中的都是静态的,作用域为整个文件

命名空间是可以取别名的

namespace newA=A;
全局变量的检测增强

c++编译器对于全局变量的声明和定义,检测会增强

c语言下的全局变量的声明和定义

int a;//定义
int a;//声明
int a;//声明
//编译不通过
int a;
int a;
int a;
c++中所有变量和函数都必须有类型
void foo(x,y){
  return 100;
}

void test01(){
  foo(1);
  foo(1,2);
  foo(1,2,3);
}
更严格的类型转换

在c++中,不同的变量一般是不能直接赋值的,需要相应的强转

struct类型的增强

在c++中使用结构体类型,可以不写struct关键字

三目运算符
//编译出错
(a 
(a 
const修饰的变量 

在c语言中const修饰的局部变量保存在栈区

void test01(){
  const int a= 10;
  int *p = &a;
  *p = 100;
  printf("a=%d",a);//输出a=100
}

const int b=10;//保存在常量区,无法修改
void test02(){
  int *p = &b;
  *p = 100;
  printf("b=%d",b);//出错
}
void test011(){
  const int a=10;
  //对const 修饰的局部变量取地址,编译器会产生一个临时变量保存a的地址
  int *p = (int*)&a;
  *p = 100;
  cout<
内联函数

1、为什么要有内联函数

第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误

第二个问题是c++特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类的成员函数

宏函数和内联函数的区别

内联函数和宏函数一样,省去编译的开销

#define MYADD(a,b) a+b
inline int myadd(int a,int b)
{
  return a+b;
 
}
void test01(){
  int a=10;
  int b=20;
    //int c=MYADD(a,b)*5;//a+b*5 替换发生在预处理阶段
    int c=myadd(a,b)*5;//替换发生在编译阶段
    cout< 
类的成员函数默认默认编译器会将它做成内联函数 
内联函数与编译器 

以下情况编译器可能考虑不会将函数进行内联编译:

不能存在任何形式的循环语句

不能存在过多的条件判断语句

函数体不能过于庞大

不能对函数进行取址操作

函数的重载

函数的重载:

在c++中函数的名字是可以重名的,也就是可以有多个相同的函数存在

实现函数重载的条件:

同一作用域

参数个数不同

参数类型不同

参数顺序不同

编译器编译会修改函数的函数名

#if __cpluspus
extern "C " {
#endif
  int myadd(int a,int b);
#if __cplusplus
}
#endif
结构体
struct student
{
  int age;
  int id;
  char name[256];
  void print(){
    cout<
对象的构造与析构
class Person{
  public:
  	int age;
  	string name;
  Person(){
  }
  Person(int age,string name){
    this.age=age;
    this.name=name;
  }
}

test(){
  Person(1,"lucy");//匿名对象
  Person();
  Persong(p1);//匿名对象不能使用括号调用拷贝构造
}
//隐式调用构造函数
void test1(){
  Person p={1,"lucy"};
  Person p2 = p1;//隐式调用拷贝构造
}
调用时机

1、旧对象初始化新对象

2、传参

3、返回对象

析构顺序
class Phone{
  public:
  Phone(string pho_name){
    this.pho_name=pho_name;
    cout<<"Phone的构造"< 

构造先调用里面,析构先析构外面

explicit关键字

c++提供了关键字explicit,禁止通过构造函数进行的隐式转换,声明为explicit的构造函数不能在隐式转换中使用。

友元
class Building{
  friend void print_Building(Building &b);
  public:
  Building(string hall,string bedroom){
    this.bedroom = bedroom;
    this.hall = hall;
  }
  string hall;
  private:
  string bedroom;
};
void print_Building(Building &b){
  cout< 

类成为另一个类的友元

class Building;
class Goodgay
{
  public:
  Goodgay(string hall,string bedroom);
  void visit();
  Building *b;
}

class Building
{
  friend void Goodgay::visit();
  public:
  Building(string hall,string bedroom){
    this->bedroom = bedroom;
    this->hall = hall;
  }
  string hall;
  private:
  string bedroom;
};
Goodgay::Goodgay(string hall,string bedroom){
  b = new Building(hall,bedroom);
}
void Goodgay::visit(){
  cout<hall<<" "<bedroom< 
成员变量和函数的存储 
class person{
  public:
  int a;//普通的成员变量
  static int b;//静态成员不存在类的实例化的对象中
  void show(){//普通成员函数不存在类实例化的对象中
    cout<
makefile makefile语法规则
目标:依赖文件列表
命令列表

1、目标:

通常是要产生的文件名称,目标可以是可执行文件或其他obj文件,也可是一个动作的名称

2、依赖文件:

是用来输入从而产生目标的文件

一个目标通常有几个依赖文件(可以没有)

3、命令:

make执行的动作,一个规则可以含几个命令(可以没有)

有多个命令时,每个命令占一行

简单的makefile实例

##main.c
#include
#include"main.h>
int main(void){
  printf("hello make worldn");
  printf("PI=%lfn",PI);
  return 0;
}
## main.c
#define PI 3.1415926
main:main.c main.h
	gcc main.c -o main
clean:
	rm main
make命令格式
make [-f file] [targets]
[-f file]:
	make默认在工作目录中虚招名问GUNmakefile、makefile、Makefile的文件作为makefile输入文件 -f可以指定以上名字意外的文件作为makefile输入文件
2.[targets]:
	若使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,然后退出
	制定了make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。


假想目标

前面makefile中出现的文件称之为将向目标

假想目标并不是一个真正的文件名,通常是一个目标集合或者动作

可以没有依赖或者命令

一般需要显示的使用make + 名字 显示调用

all exec1 exec2

clean:

rm *.o exec

make clean

makefile变量

makefile 变量类似C语言中的宏,当makefile被make工具解析时,其中的变量会被展开

变量的作用:

​ 保存文件名列表

​ 保存文件目录列表

​ 保存编译器名

​ 保存编译器参数

​ 保存表一起的输出

​ 。。。

makefile的变量分类:

1、自定义变量

在makefile文件中定义的变量

2、系统环境变量

make工具解析makefile前,读取系统发环境变量并设置为makefile的变量

3、预定义变量(自动变量)

自定义变量语法
定义变量:
	变量名=变量值
引用变量:
	$(变量名)或${变量名}
makefile的变量名:
makefile的变量名可以以数字开头

注意:

​ 变量是大小写敏感的

​ 变量一般都是makefile的头部定义

​ 变量几乎可在makefile的任何地方使用

cc=gcc
#cc=arm-linux-gcc
obj=main.0 printf1.0
target=main
cfalgs=-Wall -g

$(target):$(obj)
	$(cc) $(obj) -o $(target) $(cflages)
main.o:main.c main.h printf1.h
	$(cc) -c main.c -o main.o $(cflags)
printf1.o:printf1.c main.h
	$(cc) -c printf1.c -o printf1.o $(cflags)
clean:
	rm $(obj) $(target)
预定义变量

makefile中有许多预定义变量,这些变量具有特殊的含义,可在makefile中直接使用。

$@ 目标名
$< 依赖文件列表中的第一个文件
$^ 依赖文件列表中出去重复文件的部分
AR 归档维护程序的程序名,默认值为ar
ARFLAGS 归档维护程序的选项
AS 汇编程序的名称,默认值为as
ASFLAGS 汇编程序的程序
CC C编译器的名称,默认值为cc
CFLAGS C编译器的选项
CPP C与编译器的名称,默认值为$(CC) -E
CPPFLAGS C预编译的选项
CXX c++编译器的名称,默认值为g++
CXXFLAGS c++编译器的选项
CC=gcc
obj=main
obj1=sub
obj2=sum
OBJ=main.o sub.o sum.o
CFLAGS=-Wall -g

$(obj):$(OBJ)
	$(CC) $^ -o $@

$(obj).o:$(obj).c
	$(CC) $(CFLAGS) -c $< -o $@
$(obj1).0:$(obj1).c
	$(CC) $(CFLAGS) -c $< -o $@
$(obj2).o:$(obj2).c
	$(CC) $(CFLAGS) -c $< -o $@
clean:
	rm *.o $(obj) a.out -rf

最精简版

CC=gcc
obj=main
OBJ=main.o sub.o sum.o
CFALGS=-Wall -g

$(obj):$(OBJ)
	$(CC) $^ -o $@
%*.o:%*.c
	$(CC) $(CFLAGS) -c $< -o $@
	
clean:
	rm *.o $(obj) a.out -rf
c 内存分配

在32位系统下,每个进程的寻址范围是4G

在32位操作系统中,虚拟内存被分为两个部分,3G的用户空间和1G内核空间,其中用户空间是当前进程所私有的,内核空间,是一个系统中所有的进程所公有的

在运行程序的时候,操作系统会将虚拟内存进行分区

  • 堆,在动态申请内存的时候,在堆中开辟内存
  • 栈,主要存放局部变量
  • 静态全局区
    • 未出世化的静态全局区,静态变量或全局变量,没有初始化,存在此区
    • 初始化的静态全局区,全局变量、静态变量,辅国智的,存在此区
  • 代码区,存放程序代码
  • 文字常量区,存放常量
普通全局变量

作用范围:

​ 全局变量的作用范围,是程序的所有地方,在不同的文件中需要声明 extern int num;

生命周期:

​ 程序运行的整个过程,一直存在,知道程序结束

静态全局变量

作用范围:

​ 只能在定义它的源文件中有效

生命周期:

​ 在程序运行的整个过程中一直存在

静态的局部变量

作用范围:

​ 在它定义的函数或符合语句中有效

生命周期:

​ 第一次调用函数的时候,开辟空间赋值,函数结束后,不是放,以后再调用函数的时候,就不在位其开辟空间,也不赋初值

外部函数

普通函数,都是外部函数,函数可以再程序的任何一个文件中调用

内部函数

static修饰的函数,在定义的源文件中有效

C语言编译过程 选择性编译
#ifdef AAA
	代码段一
#else
	代码段二
#endif
#ifndef AAA
	代码段一
#else
	代码段二
#endif
//常用于防止头文件重复包含
#if 表达式
	程序段一
#else
	程序段二
#endif;
函数指针

函数指针的定义

返回值类型 (*函数指针变量名)(形参列表);

int (*p)(int,int);//定义了一个函数指针变量p,p指向的函数必须有一个整型的返回值,有两个整型参数
int max(int x,int y){}
int min(intx,int y){}
p=max;p=min;

函数指针数组

int(*p[10])(int,int);

定义了一个函数指针数组,有10个元素p[0]~p[9],每个元素都是函数指针变量,指向的函数,必须有整型的返回值,两个整型参数。

将一个函数以参数的形式传递给另一个参数,即函数回调

易混淆的指针

第一组

int *a[10]

这是个指针数组,数组a中有10个整数的指针变量

int (*a)[10]

数组指针变量,它是个指针变量。它占4个字节,村地址编号。

它指向一个数组,它加1的化,指向下个数组

int **p;

这个是个指针的指针,保存指针变量的地址。

第二组:

int *f(void)

他是个函数的生命,生命的这个函数返回值为int*类型.

int (*f)(void)

f是个函数指针变量,存放函数的地址。

字符串处理函数 获取字符串的长度

头文件:#include

函数定义: size_t strlen(const char*s);

字符串拷贝函数
#include
char *strcpy(char*dest ,const char *src){
  //功能:将src赋值给dest
}
字符串追加函数

头文件: #include

函数定义: char* strcat(char*dest, const char *src);

字符串比较函数

strcmp/strncmp

函数定义: int strcmp(const char *s1,const char *s2);

字符查找函数

头文件:#include

函数定义:char *strchr(const char *s,int c);

在字符指针s直线的字符串中,找ascii为c的字符

注意,是首次匹配,如果说s指向的字符串中有多个ascii为c的字符,则找的是第一个字符:找到了返回找到的字符的地址,到不到返回null

函数定义:char *strrchr(const char *s, int c);

函数的说明:末次匹配

在s指向的字符串中,找最后一次出现的ascii为c的字符

返回值:末次匹配的字符的地址

字符串匹配函数

#include

char *strstr(const char *haystack,const char *needle);

函数说明:

在haystack指向的字符串中查找needle自小那个的字符串,也是首次匹配返回值:

找到了:找到的字符串的首地址

没找到:返回null

字符串转换函数

atoi/atol/atof

头文件:#include

函数的定义:int atoi(const char *nptr);

函数的功能:

将nptr指向的字符产转换成整数

字符串切割函数

头文件:#include

函数定义:char* strtok(char *str,const char *delim);

函数的功能:

字符串的切割,按照delim指向的字符串中的字符,切割str指向的字符串。起始就是在str指向的字符串中发现了delim字符串中的字符,就将其编程’’,调用一次strtok之切割一次,切割一次之后,再去切割的时候strtok的第一个参数串null,意思是接着上次切割的位置继续切割

#include
#include
int main(int argc,char *argv[])
{
  char s[] = "111:22222:33:444444:55555";
  char *ret;
  
  ret = strtok(s,":");
  printf("ret = %sn",ret);//ret = 111
  
  //后面所有切割时都要将strtok的第一个参数串Null
  ret = strtok(NULL,":");
  printf("ret = %sn",ret);//ret = 22222
}
格式化字符串操作函数

int sprintf(char *buf,const char *format, …);

//输出到buf制定的内存区域

例:

char buf[20];

sprintf(buf,“%d:%d:%d”,2013,10,1);

printf(“buf=%sn”,buf);

int sscanf(const char * buf,const char *format, …);

//从buf制定的内存区域中读取信息

例: int a, b,c;

sscanf(“2013:10:1”,“%d:%d:%d”,&a,&b,&c);

printf(“%d %d %dn”,a,b,c);

const

const修饰普通局部变量,不能通过变量修改值,可以通过变量的地址修改值

const修饰指针变量的类型,不能通过指针变量修改地址里的值

const 修改指针变量,不能更改指针变量保存的地址

如果const既修饰指针变量的类型,又修斯和指针变量,则只能通过原本变量修改值

位段

在结构体中,以位为单位的成员,称之为位段(位域)

struct packed_data{
  unsigned int a:2;
  unsigned int b:6;
  unsigned int c:4;
  unsigned int d:4;
  unsigned int i;
}data;
//注意:不能对位段成员取地址
共用体

在进行 某些算法的时候,需要使用集中不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠

枚举
enum week{
  mon, tue,wed, thu ,fri, sat, sun
}
int main(int argc,char *arg[]){
  enum week day=mon;
  printf("day = %dn",day); //day = 1
  day = fri;
  printf("day = %dn",day);//day = 4
}
文件

行缓冲

标准io库函数,往标准输出(屏幕)输出东西的时候是行缓冲的

所谓行缓冲就是缓冲区碰到换行符的时候才刷新缓冲区

如果不刷新缓冲区,无法对文件执行读写操作

全缓冲

标准io库函数,往普通文件读写数据的,是全缓冲的,

碰到换行符也不刷新缓冲区,即缓冲区满了,才刷新缓冲区

刷新缓冲区的情况

1、缓冲区满了,刷新缓冲区

2、认为刷新缓冲区fflush(文件指针)

3、程序正常结束会刷新缓冲区

无缓冲

在读写文件的时候通过系统调用 io(read write),对文件进行读写数据,这个时候是无缓冲的,即写数据会立马进入文件,读数据会立马进入内存

文件在磁盘上存储的时候,物理上都是用二进制来存储。标准io库函数,对文件操作的时候,不管文件的编码哥哥是(字符编码、或二进制),二十按字节对文件机型读写,即把文件看成一个字节流

文件指针

C语言中有三个特殊的文件指针无需定义,在程序中可以直接使用

stdin: 标准输入 ,默认为当前终端(键盘)

stdout:标准输出,默认为当前终端(屏幕)

stderr:标准错误输出设备文件,默认为当前终端(屏幕)

打开文件fopen

函数定义:

FILE *fopen(const char *path,const char *mode);

读写文件操作

int fgetc(FILE *stream);

函数说明:

fgetc从stream所标示的文件中读取一个字节,将字节值返回

返回值:

以t的方式:读到文件结尾返回EOF

以b的方式:读到文件结尾,使用feof判断结尾

函数的定义:

int fputc(int c,FILE *stream)

函数的说明:

fputc将c的值写到stream所代表的文件中。

返回值:

如果输出成功,则返回输出的字节值;

如果输出失败,则返回一个EOF

EOF是在stdio.h文件中定义的符号常量,值为-1

一次读写一个字符串

函数定义:

char *fgets(char *s,int size,FILE *stream);

函数的说明:

从stream所致的文件中读取字符,在读取的时候碰到换行符或这碰到文件的末尾停止读取,或者是读取了size-1个字节停止读取,在读取的内容后面会加一个,作为字符串的结尾

返回值:

成功返回目的数组的首地址,即s

失败返回NULL

函数的定义:

int fputs(const char *s,FILE *stream);

函数的说明:

将s指向的字符串,写道stream所代表的文件中

返回值:

成功返回写入的字节数

读文件

size_f fread(void *ptr,size_t size, size_t nmemb,FILE *stream);

函数说明:

fread函数从stream所表示的文件中读取数据,一块是size个字节,共nmemb块,存放到ptr指向的内存里

返回值:

实际读取的块数。

int num;
num = fread(str,100,3,fp);
//从fp所代表的文件中读取内容存放到str子项的内存中,读取的字节数为,每块100个字节,3块。
//返回值num,如果督导300个字节返回值num为3,如果读到大于200小于300个字节

写文件fwrite

函数定义

size_t fwrite(void *ptr ,size_t size,size_t nmemb,FILE * stream);

函数说明:

fwrite 函数将ptr指向的内存里的数据,向stream所表示的文件中写入数据,一块是size个字节,共nmemb块

返回值:

实际写入的块数

随机读写

rewind、fseek函数

int fseek(FILE *stream,long offset, int whence);

//int fseek(文件类型指针,位移量,起始点)

函数功能:

移动文件流的读写位置

参数:

whence 起始位置

文件开头 SEEK_SET 0

文件当前位置 SEEK_CUR 1

文件末尾 SEEK_END 2

系统编程 文件描述符

文件描述符是非负整数。打开现存文件或新建文件时,系统(内核)会返回一个文件描述符。文件描述符用来指定一打开的文件。

#define STDIN_FILENO 0 //标准输入的文件描述符

#define STDOUT_FILENO 1 //标准输出的文件描述符

#define STDERR_FILENO 2 //标准错误的文件描述符

open函数

打开一个文件

#include

#include

#include

当文件存在时使用:

int open(const char *pathname,int flag);

当文件不存在时使用:

int open(const char*pathname,int flags,mode_t mode);

参数:

pathname:文件的路径及文件名

flags: open函数的行为标志

mode :文件权限(可读、可写、可执行)的设置

函数调用出错后输出错误信息
//通过全局变量errno
#include
//errno是一个全局变量,当函数调用失败后,可以通过errno获取错误码
void perror(const char*s);
//功能:输出函数调用失败的错误信息
//参数:
//	s:打印错误信息的提示 消息
#include
#include
#include
#include
#include
int main(int argc,char const *argv[]){
  int fd;
  fd=open("file.txt",O_RDONLY);
  if(fd==-1){
    //printf("errno=%dn",errno);
    perror("fail to open");
    return -1;
  }
  printf("fd=%dn",fd);
  return 0;
}
close函数

关闭一个文件

#include

int close(int fd);

参数:

​ fd:指定文件的文件描述符

返回值:

​ 成功:0

​ 失败:-1

注意:一个进程最多创建1024个文件描述符

write函数

#include

ssize_t write(int fd,const void *addr,size_t count);

参数:

fd:文件描述符

addr:数据首地址

count:写入数据的字节个数

返回值:

成功返回实际写入数据的字节个数

失败返回-1,可以利用perror查看原因

read函数

#include

ssize_t read(int fd,void *addr,size_t count);

参数:

fd:文件描述符

addr:内存首地址

count:读取的字节个数

返回值:

成功返回实际读写到的字节个数

失败返回-1,可以利用perror区查看原因

remove库函数

删除文件

#include

int remove(const char*pathname);

参数:

pathname:文件的路径

失败返回-1,可以利用perror查看原因

成功返回0

进程的创建

linux环境下,创建进程的主要方法是调用以下两个函数

#include

#include

pid_t fork(void);

pid_t vfork(void);

创建一个新进程

pid_t fork(void)

功能:

fork()函数用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程成为父进程

返回值:

成功:子进程中返回0,父进程中返回子进程id。

失败:返回-1

进程挂起

#include

unsigned int sleep(unsigned int sec);

功能:

进程观其指定的描述,知道指定地点实践用完或者收到型号才解除挂起

返回值:

若进程挂起到sec指定的实践则返回0,若有信号终端则返回剩余的描述

注意:

进程挂起指定的秒数后程序病不会执行,系统知识将进程切换到就绪态

进程的等待

父子进程有时需要简单的简称见同步,如父进程等待子进程的结束

linux提供以下两个等待函数wait()、waitpid()

#include

#incude

pid_t wait(int *status);

功能:

等待子进程终止如果子进程终止,此函数会回收子进程的资源。

调用wait函数的进程会挂起,知道它的一个子进程退出或收到一个不能被忽略的信号使才被唤醒。

若调用进程没有子进程或它的子进程已经结束,该函数立即返回

返回值:

如果执行成功则返回子进程的进程号。

出错返回-1,失败袁勇存于errno中。

取出子进程的退出信息

WIFEXITED(status)

如果子进程使正常终止的,取出的字段值非零

WEXITSTATUS(status)

返回子进程的退出状态,退出状态保存在status变量的8~16位。在用此宏前应先用宏WIFEXITED判断子进程是否正常退出,正常退出才可以使用此宏。

注意:

此status是个wait的参数指向的整型变量。

pid_t waitpid(pid_t pid, int *status, int options)

功能:

等待子进程终止,如果子进程终止了,此函数会回收子进程的资源

返回值:

如果执行成功则返回子进程id

出错返回-1,失败原因存在与errno中

status参数中包含子进程退出时的状态信息。

options参数能进一步控制waitpid的操作:

0:

​ 同wait,阻塞父进程,等待子进程退出。

WNOHANG:

​ 没有任何已经结束的子进程,则立即返回

WUNTRACED:

​ 如果子进程暂停了则此函数马上返回,并且不予以例会子进程的结束状态

返回值:

成功:

​ 返回状态改变了的子进程的进程号;如果设置了选项WNOHANG并且pid指定的进程存在则返回0.

出错:

​ 返回-1.当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这是errno被设置为ECHAILD.

特殊进程

僵尸进程(Zombie Process)

进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵尸进程。

子进程已运行结束,父进程尾调用wait或waitpid函数回收子进程资源,子进程变为僵尸进程

孤儿进程(Orphan Process)

父进程运行结束,但子进程未运行结束

守护进程(Daemon process)

守护进程是个特殊的孤儿进程,这种进程脱离终端,在后台运行

进程的终止

在linux下可以通过 以下方式结束正在运行的进程:

void exit(int value);

void _exit(int value);

exit和_exit的区别:

exit为库函数,而_exit为系统调用

exit会刷新缓冲区,但_exit不会刷新缓冲区

进程退出清理

进程在推出前可以用atexit函数注册退出处理函数

#include

int atexit(void(*function)(void));

功能:注册进程正常结束前调用的函数,进程退出执行注册函数。

参数:function:进程结束前,动用函数的入口地址。一个进程可以多次调用atexit函数注册清理函数,正常结束前调用函数的顺序可注册是的顺序相反

进程的创建–vfork函数

#include

#include

pid_t vfork(void)

fork和vfork函数的区别:

vfork保证子进程先运行,在它调用exec或exit之后,父进程可以被调度运行。

vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全赋值到子进程中,因为子进程会立即调用exec或exit,于是也就不访问该地址空间。

相反,在子进程中调用exec或exit之前,它在父进程的地址空间中运行,在exec之后子进程会有自己的进程空间。

int main(int argc,char* argv[]){
  pid_t pid;
  
  pid= vfork();
  if(pid<0){
    perror("fail to vfork");
    exit(1);
  }
  if(pid == 0)
  {
    int i = 0;
    for(i = 0;i<5;i++){
      printf("this is son processn");
      sleep(1);
    }
    exit(0);
  }else{
    while(1)
    {
      printf("this is father processn");
      sleep(1);
    }
  }
}
进程的替换

exec函数族,是由六个exec函数组成的。

1、exec函数族提供了六种在进程中启动另一个程序的方法。

2、exec哈桑农户族可以更具只从的文件名或目录名查找可执行文件

3、调用exec函数的进程并不创建新的进程,故调用exec前后,进程的进程号并不会改变,其执行的程序完全由新的程序替换,而新的程序则从其main函数开始执行

#include
int execl(const char *pathname,const char *arg0,...,null);
int execlp(const char *filename,const char *arg0,...,null);
int execle(const char *pathname,const char *arg0,...,null,char *const envp[]);
int execv(const char *pathname,char*const argv[]);
int execvp(const char *filename,char *const argv[]);
int execve(const char *pathname,char *const argv[],char *const envp[]);

l(list):

参数地址列表,以空指针结尾。

参数地址列表

char *arg0,char *arg1,…,char *argn,null

v(vector):

存有各参数地址的执政数组的地址。

使用时先构造一个指针数组,指针数组存各参数的地址,然后将该指针数组地址作为函数的参数。

p(path)

按path环境变量指定的目录搜索可执行文件。

以p结尾的exec函数取文件名作为参数。当指定filename作为参数时,若filename中包含/,则将其视为卢金明,并直接到指定的路径中执行程序。

e(environment)

存有环境变量字符串地址的指针数组的地址。execle和execve改变的是exec启动的函数的环境变量(新的环境变量完全由enviroment指定),其他四个唤术启动的程序则使用系统默认的环境变量。

注意:

exec函数族与一般的函数不同,exec函数族中的函数执行成功后不会返回。只有调用失败了,他们才会返回-1.失败后从源程序的调用点接着往下执行。

在平时的编程中,如果用到了exec函数族,一定要加错误判断语句。

kill函数

#include

#include

int kill(pid_t pid,int signum);

功能:

给指定进程发送信号

参数:

pid:见下页

signum:信号的编号

返回值:

成功返回0,失败返回-1

pid的取值有四种情况:

pid>0:将信号传送给进程ID为pid 的进程

pid=0:将信号传送给当前进程所在进程组中的所有进程

pid=-1:将信号传送给系统内所有的进程。

pid<-1:将信号传给指定进程组的所有进程。这个进程组号等于pid的绝对值

alarm函数

#include

unsigned int alarm(unsigned int seconds);

功能:

在seconds秒后,向调用进程发送一个SIGALRM信号,SIGALRM信号的默认动作是终止调用alarm函数的进程。

返回值:

若以前没有设置过定时器,或设置的定时器超市,返回0;否则返回定时器剩余的秒数,并重新设定定时器。

#include
#include
int main(int argc,char *argv[])
{
  alarm(5);
  while(1){
    printf("hello world");
    sleep(1);
  }
  return 0;
}

raise函数

#include

int raise(int signum);

功能:

给调用进程本身发送一个信号。

参数:

signum:信号的编号

返回值:

成功返回0,失败返回-1

abort函数

#include

void abort(void)

功能:

向进程发送一个SIGABRT信号,默认情况下进程会退出

注意:

即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止,且在终止前会刷新缓冲区,关文件描述符。

pause函数

#include

int pause(void);

功能:

将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到。

返回值:

指导捕获到信号,pause函数才返回-1,且errno被设置成eintr

signal函数

进程接收到信号后的处理方式

1、执行系统默认动作

2、忽略此信号

3、执行自定义信号处理函数

#include

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);

功能:

注册信号处理函数(不可用于SIGKILL、SIGSTOP信号),即确定收到信号后处理函数的入口地址。

参数:

signum:信号编号

handler的取值:忽略该信号:SIG_IGN

​ 执行系统默认动作:SIG_DFL

​ 自定义信号处理函数:信号处理函数名

返回值:

成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址。

失败:返回SIG_ERR

#include
#include
#include
#include
void handler(int sig);
int main(int argc,char const *argv[])
{
  #if 0
  	if(signal(SIGINT,SIG_DFL)==SIG_ERR)
    {
      perror("fail to signal");
      exit(1);
    }
  if(signal(SIGQUIT,SIG_DFL)==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  #endif
  
  #if 0
  if(signal(SIGINT,SIG_IGN)==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  if(signal(SIGQUIT,SIG_IGN)==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  if(signal(SIGTSTP,SIG_IGN)==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  #endif
  if 0
    if(signal(SIGINT,handler)==SIG_ERR)
    {
      perror("fail to signal");
      exit(1);
    }
  	if(signal(SIGQUIT,handler)==SIG_ERR)
    {
      perror("fail to signal");
      exit(1);
    }
  	if(signal(SIGTSTP,handler)==SIG_ERR)
    {
      perror("fail to signal");
      exit(1);
    }
  #endif
  while(1)
  {
    printf("hello worldn");
    sleep(1);
  }
  return 0;
}

void handler(int sig){
  if(sig==SIGINT)
  {
    printf("SIGINT正在处理n");
  }
  if(sig==SIGQUIT)
  {
    printf("SIGQUIT正在处理n");
  }
  if(sig==SIGTSTP)
  {
    printf("STGTSTP正在处理n");
  }
}
#include
#include
#include
#include
void *ret_handler;
void hanler(int sig)
{
  printf("*******************n");
  printf("nihao biejingn");
  printf("welcome to 1000phonen");
  printf("*******************n");
  if(signal(SIGINT,ret_handler)==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  
}
int main(int argc,char const *argv[])
{
  if((ret_handler=signal(SIGINT,handler))==SIG_ERR)
  {
    perror("fail to signal");
    exit(1);
  }
  while(1)
  {
    printf("hello worldn");
    sleep(1);
  }
  return 0;
}
可重入函数

可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误

可重入函数就是可以被中断的函数,当前函数可以在任何时刻中断,并执行另一块代码,当执行执行完毕后,回到原本的代码可以继续运行。

编写可重入函数:

1、不适用(返回)静态的数据、全局变量(除非用信号量互斥)

2、不调用动态内存分配、释放的函数

3、不调用任何不可重入的函数(如标准I/O函数)。

注:

即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存errno的值,结束时,在混服原值。因为,信号处理过程中,errno值随时可能改变

信号集 信号集概述

一个用户进程常常需要对多个信号进行处理。为了方便对多个信号进行处理,在linux系统中引入了信号集。

信号集是用来表示多个信号的数据类型

sigemptyset函数

初始化一个空的信号集

#include

int sigemptyset(sigset_t *set);

功能:

初始化由set指向的信号集,清除其中所有的信号即初始化一个空的信号集

参数:

set:信号集表示的地址,以后操作此信号集,对set进行操作就可以了。

返回值:

成功返回0,失败返回-1。

sigfillset函数

#include

int sigfillset(sigset_t *set);

功能:初始化信号集set,将信号集合设置为所有信号的集合。

参数:

信号集表示的地址,以后操作此信号集,对set进行操作就可以了。

返回值:

成功返回0,失败返回-1.

sigismember函数

判断某个集合中是否有某个信号

#include

int sigismember(const sigset_t *set,int signum);

功能:

判断signum标识的信号是否在信号集合set之中。

参数:

set:信号集标识符号的地址。

signum:信号的编号

返回值:

在信号集中返回1,不在信号集中返回0

错误,返回-1.

sigaddset函数

向某个集合中添加一个信号

#include

int sigaddset(sigset_t *set,int signum);

功能:

将信号signum加入到信号集合set之中。

参数:

set:信号集标识的地址。

signum:信号的编号

返回值:

成功返回0,失败返回-1.

sigdelset函数

从某个信号集中删除一个信号

#include

int sigdelset(sigset_t *set,int signum);

功能:

将signum所标识信号从信号集合set中删除

参数:

set:信号集标识的地址

signum:信号的编号

返回值:

成功:返回0

失败:返回-1

#include
#include
int main(int argc,char*argv[])
{
  sigset_t set;
  int ret = 0;
  
  sigemptyset(&set);
  ret = sigismember(&set,SIGINT);
  if(ret == 0 )
  {
    printf("SIGINT is not a member of sigprocmask nret=%dn",ret);
  }
  sigaddset(&set,SIGINT);
  sigaddset(&set,SIGQUIT);
  ret = sigismember(&set,SIGINT);
  
  if(ret ==1)
  {
    printf("SIGINT is a member of sigprocmask nret = %dn",ret);
  }
  return 0;
  
}
sigprocmask函数

创建一个阻塞集合

#include

int sigprocmask(int how ,const sigset_t *set ,sigset_t *oldset);

功能:

检查或修改信号阻塞集,根据how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存。

参数:

how:信号阻塞集合的修改方法。

​ SIG_BLOCK:向信号阻塞集合中添加set信号集

​ SIG_UNBLOCK:从信号阻塞集合中删除set集合

​ SIG_SETMASK:将信号阻塞集合设置为set集合

set:要操作的信号集地址。

oldset:保存原先信号集地址。

注:若set为null,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中。

返回值:

成功:返回0

失败:返回-1

#include
#include
#include
#include
int main(int argc,char *argv[])
{
  sigset_t set;
  int i=0;
  
  sigemptyset(&set);
  sigaddset(&set,SIGINT);
  while(1)
  {
    sigprocmask(SIG_BLOCK,&set,null);
    for(i = 0;i<5;i++)
    {
      printf("SIGINT signal is blockedn");
      sleep(1);
    }
    sigprocmask(SIG_UNBLOCK,&set,null);
    for(i=0;i<5;i++)
    {
      printf("SIGINT signal unblockedn");
      sleep(1);
    }
  }
  return 0;
}
无名管道的创建pipe函数

#include

int pipe(int filedes[2]);

功能:经由参数filedes返回连个文件描述符

参数:

filledes为int型数组的首地址,其存放了管道的文件描述符fd[0]、fd[1].

filedes[0]为读而打开,filedes[1]为写而打开管道,filedes[0]的输出是filedes[1]的输入

返回值:

成功:返回0

失败:返回-1

#include
#include
#include
int main(int argc,char const *argv[])
{
  int fd_pipe[2];
  if(pipe(fd_pipe)==-1)
  {
    perror("fail to pipe");
    exit(1);
  }
  printf("fd_pipe[0]=%dn",fd_pipe[0]);
  printf("fd_pipe[1]=%dn",fd_pipe[1]);
  if(write(fd_pipe[1],"hello world",12)==-1)
  {
    perror("fail to write");
    exit(1);
  }
  char buf[32]="";
  if(read(fd_pipe[0],buf,sizeof(buf))==-1)
  {
    perror("fail to read");
    exit(1);
  }
  printf("buf=[%s]",buf);
  return 0;
}
无名管道的读写规律 读写端存在,只读不写

如果管道中有数据,会正常读取数据;如果管道中没有数据,则读操作会被阻塞

读写端都存在,只写不读

如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数会被阻塞,默认无名管道的缓冲区为64k字节

只有读端

如果管道中有数据,则会被读取,没有数据read函数返回0;

只有写端

如果关闭读端,一旦执行写操作,就会产生一个信号SIGPIPE(管道破裂)

fcntl函数

非阻塞:如果是阻塞,管道中没有数据,read会一直等待,指导有数据才会继续运行,否则一直等待,如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正常运行读取数据,如果管道中没有数据,则read函数会立即返回,继续下面的代码运行

有名管道(FIFO)

有名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同,其特点是:

1、半双工,数据在同意时刻只能在一个方向上流动

2、写入FIFO中的数据遵循陷入先出的规则

3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须实现越好数据的格式,如多少字节算一个消息等

4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见。但FIFO中的内容却存放在内存中。

5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同

6、从FIFO读数据是一次性操作,数据一旦被堵,他就从FIFO之中被抛弃,释放空间以便写更过的数据。

7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。

8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。

有名管道的创建
方法一:
mkfifo 文件名
方法二:
int mkfifo(const char *pathname,mode_t mode);
功能:创建一个有名管道,产生一个本地系统可见的文件pathname
参数:
pathname:有名管道创建后生成的文件
mode:管道文件的权限,一般通过八进制数进行设置即可
#include
int main(int argc,char const *argv[])
{
  if(mkfifo("fifo_file",0664)==-1)
  {
    if(errno != EEXIST){
      perror("fail to mkfifo");
      exit(1);
    }
    
  }
  return 0;
}
线程等待

#include

int pthread_join(pthread_t thread,void**retval);

功能:等待子线程结束,并回收子线程

参数:thread:被等待的线程号

retval:用来存储线程退出状态的指针的地址

返回值:

成功返回0,失败返回非0

线程分离

linux线程执行和windows不同,pthread有两种状态:

可结合的或者分离的,线程默认创建为结合态。

如果线程时结合态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8k多)。只有当调用pthread_join之后这些资源才会被释放。

若是detached状态的线程,这些资源在线程函数退出或者pthread_exit时会自动被释放,使用pthread_detach函数将线程设置为分离态。

创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供线程分离函数

#inlcude

int pthread_detach(pthread_t thread);

功能:

使调用线程与当前进程分离,使其成为一个独立的线程,该线程终止时,系统将自动回收它的资源。

参数:

thread:线程号

返回值:

成功:返回0,失败返回非0.

计算机网络 socket

socket分类

SOCK_STREAM,流式套接字,用于TCP

SOCK_DGRAM,数据报套接字,用于UDP

SOCK_RAW,原始套接字,对于其他的协议操作时使用

文件描述符的赋值

dup和dup2是连个非常常用的系统调用,都是用来复制一个文件的描述符,是新的文件描述符也标识旧的文件描述符所标识的文件。

int dup(int oldfd);

int dup2(int oldfd,int newfd);

dup和dup2经常用来重定向进程的stdin、stdout和stderr.

dup函数

#include

int dup(int old);

功能:赋值oldfd文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符中最小可用的文件描述符。

参数:用复制的文件描述符oldfd;

返回值:

成功:新文件描述符

失败:返回-1,错误代码存于errno中

dup2函数

#include

功能:

赋值一根打开的文件描述符oldfd,并分配新的文件描述符nwefd,newfd也标识oldfd所标识的文件。

注意:

newfd是小于文件描述符最大允许值的非负证书,如果newfd是一个已经打开的文件描述符,则首先关闭该文件,然后再复制

参数:

要复制的文件描述符oldfd

分配的新文件描述符newfd

返回值:

成功:返回newfd

失败:返回-1,错误代码存在于errno中

有名管道的创建

方法一:用shell命令mkfifo创建有名管道

mkfifo 文件名

方法二:使用mkfifo函数

#include

#include

int mkfifo(const char *pathname,mode_t mode);

功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname

参数:panthname:有名管道创建后生成的文件,可以带路径

mode:管道文件的权限

成功:返回0

失败:返回-1

#incude
#include
#include
#include
#include
#include
int main(int argc,char const *argv[])
{
  if(mkfifo("fifo_file",0664)==-1)
  {
    perror("fail to mkfifo");
    exit(1);
  }
  return 0;
}
有名管道的基本读写操作

由于有名管道在本地创建了一个管道文件,所以系统调用的io函数基本都可以对有名管道进行操作,但是不能使用lseek修改文件的偏移量

注意:有名管道创建的文件知识起到标识的作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件知识一个标识。

#incude
#include
#include
#include
#include
#include
int main(int argc,char const *argv[])
{
  if(mkfifo("fifo_file",0664)==-1)
  {
    if(errono != EEXIST)
    {
        perror("fail to mkfifo");
    	exit(1);
    }

  }
  int fd;
  fd = open("fifo_file",O_RDWR);
  if(fd==-1)
  {
    perror("fail to open");
    exit(1);
  }
  //通过write函数向管道中写入数据
  if(write(fd,"hello world",strlen("hello world"))==-1)
  {
    perror("fail to write");
    exit(1);
  }
  char buf[32]="";
  if(read(fd,buf,sizeof(buf))==-1)
  {
    perror("fail to read");
    exit(1);
  }
  printf("buf=[%s]n",buf);
  return 0;
}
有名管道的读写规律(阻塞) 读写端都存在,只写不读 在一个进程中,只有读端 线程的创建

#include

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,

​ void * (* start_routine)(void *),void *arg);

功能:创建一个线程

参数:

thread:线程标识符地址。

attr:线程属性结构体地址。

start_routine:线程函数的入口地址

arg:传给线程函数的参数

返回值:

成功:返回0

失败:返回非0

与fork不同的是pthread_create创建吸纳成不与父线程在同一点开始运行,二十从指定的函数开始运行,该函数运行完后,该线程也就退出了。

线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。

线程函数的程序在pthread库中,故链接时要加上参数-lpthread

UDP编程流程

服务器:

创建套接字

将服务器的ip地址、端口与套接字进行绑定

接受数据

发送数据

客户端:

创建套接字

发送数据

接受数据

关闭套接字

UDP-创建套接字

int socket(int family,int type,int protocol);

功能:

创建一个用于网络通信的socket套接字

参数:

family:协议族(AF_INET、AF_INET6、PF_PACKET等)

type:套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAM等)

protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等)

返回值:

套接字

特点:

创建套接字时,系统不会分配端口

创建的套接字默认属性时主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的

头文件:

#include

#include
#include
#include
#include
int main(int argc,char const *argv[])
{
  int sockfd;
  if(sockfd=socket(AF_INET,SOCK_DGRAM,0)==-1){
    perror("fail to socket");
    exit(1);
  }
  printf("sockfd= %dn",sockfd)
  return 0;
}
UDP-发送、绑定、接收数据 IPv4套接字地址结构

头文件:#include

struct in_addr
{
	in_addr_t addr;//ip地址 4字节
};
struct sockaddr_in
{
  sa_family_t sin_family;//协议族 2字节
  in_port_t sin_port;//端口号 2字节
  struct in_addr sin_addr;//ip地址 4字节
  char sin_zero[8];//填充,不起任何作用
};

为了使不同格式地址能被传入套接字函数,地址需要强制转化成套接字地址结构

通用结构体sockaddr

#include

struct sockaddr
{
  sa_family sa_family;//2字节
  char sa_data[14];
}
两种地址使用场合

在定义源地址和目的地址结构的时候,选用struct sockaddr_in

当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换

bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
发送数据-sendto

ssize_t sendto(int sockfd,const void *buff,size_t nbytes,int flags,const struct sockaddr *to, socklen_t addrlen);

功能:

向to结构指针中指定的ip,发送UDP数据

参数:

sockfd:套接字

buf: 发送数据缓冲区

nbytes:发送数据缓冲区的大小

flags:一般为0

to:子痫目的主机地址结构体的指针

addrlen: to所指向的内容的长度

注意:

通过to和addrlen确定目的地址

可以发送0长度的UDP数据包

#include
#include//socket
#include
#include//exit
#include//sockaddr_in
#include//htons inet_addr
#include//close
#include
int main(int argc,char const *argv[])
{
  int sockfd;
  if(sockfd=socket(AF_INET,SOCK_DGRAM,0)==-1){
    perror("fail to socket");
    exit(1);
  }
  printf("sockfd= %dn",sockfd);
  
  //填充服务其网络信息结构体sockaddr_in
  struct sockaddr_in serveraddr;
  socklen_t addrlen = sizeof(serveraddr);
  serveraddr.sin_family = AF_INET;//协议族,AF_INET:ipv4网络协议
  serveraddr.sin_addr.s_addr=inet_addr("192.168.3.78");//ip地址
  serveraddr.sin_port = htons(8080);
  
  //发送数据
  char buf[128]="";
  while(1)
  {
    fgets(buf,128,stdin);
    buf[strlen(buf)-1]='';//把buf字符串中的n转化为
    if(sendto(sockfd,buf,N,0,(struct sockaddr*)&serveraddr,addrlen)==-1){
      perror("fail to sendto");
      exit(1);
    }
  }
  close(sockfd);
  return 0;
}
bind
#include
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//功能:将套接字与网络信息结构体绑定
//参数:
//sockfd:文件描述符,socket的返回值
//addr:网络信息结构体
//通用结构体(一般不用)
//struct aockaddr
//  网络信息结构体 sockaddr_in
//  #include
//  struct sockaddr_in
//    struct sockaddr_in
//      addrlen: addr的长度
//      返回值:
//      成功:0
//      失败:-1
接收数据-recvfrom

ssize_t recvfrom(int sockfd,void *buf,size_t nbytes, int flags,struct sockaddr *from,socklen_t *addrlen);

功能:

接收UDP数据,并将源地址信息保存在from指向的结构体中

参数:

sockfd:套接字

buf:接收数据缓冲区

nbytes:接收数据缓冲区的大小

flags:套接字标志(常为0)

from:源地址结构体指针,用来保存数据来源

addrlen:from所指内容的长度

注意:

通过from和addrlen参数存放数据来源信息

from和addrlen可以为NULL,表示不保存数据来源

返回值:

成功:接收到的字符数

失败:-1

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1012708.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号