C语言学习Part03

C语言Part03

区分指针运算
*s++ (*s)++
前者是指 指针S指向的地址进行偏移
后者是指 指针S指向的地址里存放的数据进行自增
    
    
    
string.h中的函数
#include<string.h>
// 在str所指的字符串的前n个字节中搜索第一次出现字符C的位置,并返回其内存地址,如果没有找到就返回NULL
void *memchr(const void *str, int c, size_t n);

void *memchr(const void *str, int c, size_t n){
    const char *s = (const char *)str;
    size_t i;
    for(i=0;i<n;i++){
        if(s[i] == c){
            return str+i;
        }
    }
    return NULL;
}

//把 str1 和 str2 的前 n 个字节进行比较   相等返回0

int memcmp(const void *str1, const void *str2, size_t n);
int memcmp(const void *str1, const void *str2, size_t n){
    const char *s1 = (const char *)str1;
    const char *s2 = (const char *)str2;
    for(size_t i=0;i<n;i++){
        if(s1[i] < s2[i]){
            return -1;
        }else if(s1[i] > s2[i]){
            return 1;
        }else{
            
        }
    }
    return 0;
}

//从 src 复制 n 个字节到 dest。   内存拷贝函数
void *memcpy(void *dest, const void *src, size_t n);
float a = 3.375;
int m = a; //m = 3
memcpy(&m,&a,4);  //m = 0x40580000
void *memcpy(void *dest, const void *src, size_t n){
    char *pd = (char *)dest;
    const char *ps = (const char *)src;
    for(size_t i=0;i<n;i++){
        pd[i] = ps[i];
    }
    return dest;
}

//另一个用于从 src 复制 n 个字节到 dest 的函数
//但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。
void *memmove(void *dest, const void *src, size_t n);

//复制字节 c(一个无符号字符)到参数 str 所指向的内存的前 n 个字节。
//所有的函数 int c 但实际上只使用c整数一个字节(最低的字节)
void *memset(void *str, int c, size_t n);

//在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 如果没有返回NULL
char *strchr(const char *str, int c);

char *strchr(const char *str, int c){
    while(*str!='\0' && *str != c){
        ++str;
    }
    if(*str == c){
        return str;
    }
    return NULL;
}

//在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。  r:reverse 逆序
char *strrchr(const char *str, int c);//如果没有找到返回NULL

//把 str1 和 str2 进行比较,结果取决于 LC_COLLATE 的位置设置。 "按拼音顺序来比较"  "中国" "中共"
int strcoll(const char *str1, const char *str2);
//根据程序当前的区域选项中的 LC_COLLATE 来转换字符串 src 的前 n 个字符,并把它们放置在字符串 dest 中。
size_t strxfrm(char *dest, const char *src, size_t n);

//检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。
//从str1中找出在str2中任意一个字符第一次出现在str1中下标位置  没找到,返回str1字符串长度
//不会把str2看作一个整体  相当于是一个字符集合
size_t strcspn(const char *str1, const char *str2);

//检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。
size_t strspn(const char *str1, const char *str2);


//检索字符串 str1 中第一个匹配字符串 str2 中字符的字符,不包含空结束字符。也就是说,依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。 如果str1中不包含str2中的字符,则返回NULL
char *strpbrk(const char *str1, const char *str2);

//从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。
//对于C语言标准库或者系统调用函数,对于出错的情况,基本上都会设置全局变量errno,可以通过strerror函数获取错误信息
//用不同的整数  代表不同的错误信息    strerror函数就可以通过整数(错误码)获取错误信息
char *strerror(int errnum);    //基本上会结合 errno 使用


//在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。 如果不存在则返回NULL
//查找子串的位置
char *strstr(const char *haystack, const char *needle);  //如果以朴素的思想来实现很简单  KMP

//分解字符串 str 为一组字符串,delim 为分隔符。  会把str中delim字符串的字符更改为'\0'
char *strtok(char *str, const char *delim);   
//返回拆分之后第一个字符串   后面每调用一次 strtok(NULL,delim) 都会返回下一个分组的字符串 直到NULL
ctype.h中的函数
函数名如果是下列函数时,返回值为真
isalnum(int c)字母或者数字
isalpha(int c)字母
isblank(int c)标准的空白字符(空格、水平制表符或者换行)或者任何其他本地化指定为空白符的字符
iscntrl(int c)控制字符,如ctrl+B ox7f以上的
isdigit(int c)数字
isgraph(int c)除空格之外的任意可打印字符
islower(int c)小写字母
isprint(int c)可打印字符
ispunct(int c)标点符号(除空格或字母数字字符以外的任何可打印字符)
isspace(int c)空白字符
isupper(int c)大写字母
isxdigit(int c)十六进制数字符

ctype.h头文件中的字符映射函数

函数名行为
tolower(int c)如果参数是大写字符,该函数返回小写字符;否则,返回原始参数
toupper(int c)如果参数是小写字符,该函数返回大写写字符;否则,返回原始参数
#include <stdio.h>
//把字符输出到stream文件中      stream如果取stdout 则表示输出到控制台
int fputc(int c, FILE *stream);
//把字符串输出到stream文件中    stream如果取stdout 则表示输出到控制台
int fputs(const char *s, FILE *stream);
//和fputc一样
int putc(int c, FILE *stream);
//输出一个字符
int putchar(int c);
//输出一个字符串 自动的换行
int puts(const char *s);

//从文件stream中读取一个字符    stream取stdin 表示从标准输入读取(控制台)
int fgetc(FILE *stream);
//从stream读取最多size-1个字符到s缓存中  一定保证有'\0'
char *fgets(char *s, int size, FILE *stream);
//和fgetc一样
int getc(FILE *stream);
//从标准输入中读取一个字符
int getchar(void);
//往输入流中放入一个字符c
int ungetc(int c, FILE *stream);

char *gets(char *s); //Never use this function.
字符串格式化IO
//遵循格式字符串 format 中的格式占位符
int scanf(const char *format, ...);          //格式化标准输入    是从控制台读取
int fscanf(FILE *stream, const char *format, ...);  //文件格式化标准输入  是从文件stream中读取
int sscanf(const char *str, const char *format, ...); //字符串格式化标准输入 从字符串str中读取


int printf(const char *format, ...);   //格式化标准输出     输出到控制台
int fprintf(FILE *stream, const char *format, ...); //文件格式化标准输出  输出到文件stream
int sprintf(char *str, const char *format, ...); //字符串格式化标准输出  输出到字符串str
int snprintf(char *str, size_t size, const char *format, ...); //字符串格式化标准输出
//指定了str最大存储空间为size个字节

//返回值  都 表示正确读取成功的次数(按占格式占位符)
//          表示成功写入的字节数

IO

标准输入scanf

  • 格式化标准输入

    int scanf(const char * fomat,…);

    scanf是标准输入缓冲区中按照格式字符串进行读取数据,如果缓冲区中没有数据,则会等待用户输入数据,弱国缓冲区有数据,则会直接读取不会等待用户输入

    用户输入时,会一口气把用户所有的输入包括ENTER 一起读入缓冲区中

    一般的格式占位符(读取数据类型的)会忽略空白字符以及换行

    而%c则不会忽略缓冲区中任何数据,导致读取换行

    get fgets都会读取\n

可以手动清除缓冲区内的数据

scanf("%*[^\n]")//从缓冲区中读取除了\n以外所有的字符并丢掉
scanf("%*c");从缓冲区读取任意一个字符
  • 标准输出

    //表示成功输出的字节数  
    int printf(const char *format, ...);
    
    %+-m.nd       %格式占位符标识 
        +-  左右对齐
        m   占字符宽度
        .   小数点
        n   小数部分宽度
    %#      前缀
    
    • 标准输出是输出到输入缓冲区,只有满足一定的条件时,输出缓冲区的内容才会输出到屏幕(控制台)
      • 换行 \n
      • 缓冲区满了 4kb
      • 强制刷新缓冲区 fflush(stdout);
      • IO转换 printf() 如果调用 scanf()
      • 程序结束

可变长参数

#include <stdarg.h>

void va_start(va_list ap, last);//last 是最后一个有名参数
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

  • 定义可变长参数列表时,至少有一个是确定的参数,为了能够解释出来实参

    ISO C requires a named argument before ...
    
  • 自定义可变长参数函数

    RET_TYPE funcname(TYEP argname, ...){
        //argname一般用于告知 ... 中有多少个参数 以及每一个参数的类型  scanf  printf
        //1.定义变量
        va_list ap;  //ap这样的变量不能直接赋值   va_list ap1;  
        //2.初始化
        va_start(ap,argname);  
        //va_copy(ap1,ap);    相当于赋值  不能直接=
        //3.每调用一次 获取一个参数
        type arg = va_arg(ap,type);   //type指定参数的类型
        
        //4.释放ap变量
        va_end(ap);
    }
    
  • 在调用可变长参数函数时,除了有名(带名字的参数)参数以外,其他(…)可以传递任意多个任意类型的参数

  • scanf/printf第一个有名参数 const char *format(格式字符串)

    • 格式字符串告诉了scanf/printf函数后面传递了多少个参数,每个参数的类型

const

const修饰变量 表示该变量只读 “理论上不可以修改”

如果直接对const修饰的变量进行修改会报错

const修饰的变量虽然只读 但是不能当作常量

const和指针

大部分情况下 const都是和函数的形参列表中和指针结合使用

这样是为了保护在函数中修改实参的值

const的位置不同修饰的内容也不同

const char*s1; 指针内存地址中的数据只读 常量指针
char * const s1;指针内存地址只读 不可改变指向  指针常量
const char * const s1; 即数据只读 也内存地址只读

const的作用

1.修饰变量 表示只读

2.在函数形参列表使用 防止实参被修改

注意:

当const type * 直接赋值给 type *时会有警告,可以用强制转换消除

const type a; &a 类型为 const type *

动态内存

栈内存 变量自动申请内存 自动释放

动态内存 堆内存 手动申请 手动释放

#include <stdlib.h>
/*
函数功能: 向操作系统申请size个字节大小的内存空间
返回值: 
	void * 
	成功返回size个字节大小的内存空间起始位置
	失败返回NULL
*/
void *malloc(size_t size);  
/*
函数功能: 向操作系统申请nmemb个size字节大小的连续内存空间   nmemb*size个字节的内存空间  并初始化数据为"0"
返回值:  
	void *:  万能指针  作为nmemb*size个字节的内存空间的起始位置
	失败返回NULL
参数:
	size:  单位内存大小
	nmemb: 单位内存的个数
	//int arr[5]  5个int类型的内存空间
*/
void *calloc(size_t nmemb,size_t size);//函数可以于malloc(nmemb*size)
/*
函数功能:
	realloc调整之前申请的动态内存ptr的大小为size个字节
	并且返回调整之后内存的起始位置
注意:
	调整内存大小可能会改变内存的位置,也有可能不会改变
	因此,一定要接收realloc函数的返回值
	ptr是之前通过malloc/calloc/realloc函数返回的内存地址,通过realloc之后,自动把内存中的数据拷贝到新内存中,并且返回新内存的起始位置
*/
void *realloc(void *ptr,size_t size);
/*
函数功能:
	free释放动态内存
	释放malloc/calloc申请的动态内存
	在程序中如果没有释放malloc/calloc申请的动态内存,则该内存会一直存在,造成内存泄漏
	只能释放动态内存地址,不能释放栈内存,全局数据区内存  试图free一个非堆内存地址(程序崩溃,核心已转储)
	如果free时释放的是malloc/calloc返回的起始位置的偏移位置,则一样是程序崩溃,核心已转储
	同一个动态内存不能多次free,如果多次free,则运行崩溃(double free)核心已转储
*/
void free(void *ptr);

realloc函数可以对之前申请的内存进行扩容和缩容

1.原内存后的内存是空闲的,则会直接在此基础上进行扩充

2.原内存后内存已经被使用,则会申请一片新的内存,将原先的内存自动释放,并返回新内存的起始地址

申请动态内存时要申请几个字节就是用几个字节,不要越界使用,这是因为即便用户申请的内存很小,小于24字节,系统还是会分配24字节给用户,但这片内存中有着至少八个字节(64位)的内存时用来控制用户申请的这片内存,其记录了内存的大小,起始位置,是否空闲等等

用户在第一次申请内存时,系统会直接分配至少33页的动态内存,一页动态内存是4096个字节

我们申请的内存都是虚拟内存,系统会将物理内存和我们的虚拟内存进行映射,以此来使用。

释放动态内存只是将该内存标记为闲置状态,只有当该内存后所有的内存处于空闲状态,才会进行页回收 也就是取消映射。

申请动态内存时会失败的,失败时返回NULL

由于操作系统不知道申请的内存存储的数据类型所以返回的是void*类型的地址 用户接受时需要进行强转,将其转化为自己数据的类型。主要是因为void *不可以解引用

申请的动态内存是连续的空间,所以可以像数组一样用下标[]访问每个数据

内存碎片

频繁申请和释放内存,会产生内存碎片。

申请1字节内存,至少会分配24字节,在使用过程,只能使用1byte,剩下的23byte就是内存碎片

在申请n字节内存空间时,有一块m字节的内存块是处于空闲的(m>n),操作系统会把这一块m字节的内存块分配给用户使用,而用户只能使用n个字节,剩下m-n个字节就成为了内存碎片

内存泄漏

申请动态内存后没有释放

内存泄漏会导致程序运行越来越卡,如果内存不够,最后崩溃

所以,使用动态内存最好是在需要大量内存的情况下申请。

关于函数返回指针

函数返回指针,也就是返回一个内存地址,

局部变量会自动申请释放内存,所以不能返回局部变量的内存地址

可以返回存储在动态内存中的变量,因为动态内存需要手动释放,而且是在返回后释放

可以返回函数的实参

可以返回全局变量或静态变量的内存地址

可以返回NULL

变量

变量的分类

根据变量定义的位置 将变量分为全局变量,局部变量,块变量

作用域 取决于定义的位置

存储位置 取决于定义的位置和 关键字static register的修饰

生命周期 取决于存储位置

全局变量

定义在全局域(函数外部)

在本文件任何函数内都可以使用

是否可以在其他文件中访问 取决于全局变量是否是静态变量和是否用extern声明

静态全局变量只能在本文件内使用

普通全局变量可以在其他文件通过extern进行声明并访问

全局变量的生命周期:从程序运行到结束

全球变量的存储位置:全局数据区(数据段)

全局变量如果没有初始化,自动初始化为零;

局部变量

定义在函数内部

作用域:只能在函数内部使用

静态局部变量static

​ 生命周期:从程序开始到结束

​ 程序运行时就会为静态变量在全局数据区分配内存空间,等到函数调用时不会再执行静态变量的定义语句

​ 存储位置:全局数据区

普通局部变量:

​ 生命周期:函数调用期间 调用时才会申请内存,函数调用结束自动释放内存

​ 存储位置:栈

局部变量可以和全局变量同名,但在函数内调用时会优先访问局部变量,只有用extern才能访问到全局变量

块变量

定义在语句块中的变量

作用域:只作用于当前语句块

生命周期:

普通块变量:语句块执行期间

静态块变量:程序运行期间

存储位置:

普通块变量:栈

静态块变量:全局数据区

变量修饰符
auto

自动的 在c语言中 定义的变量默认就是atuo 一般不用 可以省略

static

静态的

static修饰局部变量 改变了存储位置和生命周期

static定义变量的语句,只会执行一次

程序启动时,为static变量分配内存,并执行初始化语句

static修饰的变量,在程序运行期间,不管函数调用多少次,内存地址不变

普通的变量,每次调用函数,其内存地址都有可能发生变量

普通局部变量如果没有初始化,垃圾值,值是不确定的

static修饰的局部变量,即使没有初始化,也会自动初始化为"零" 0

static修饰的变量,如果要初始化,必须初始化为常量

static修饰全局变量 改变了作用域

static修饰函数

限制了函数的作用域,表示函数只能在本文件中调用

在多文件编程时,头文件中,一般只放函数的声明,但是对于static修饰的函数,静态函数的定义可以放在头文件中

register

寄存器

请求编译器把register修饰的变量作为寄存器变量

register修饰的变量是没有内存地址的,是因为寄存器变量存储在寄存器上面(寄存器不属于虚拟内存的)

register变量不能进行取址运算(&)

如果一个变量被频繁读写,为了提高效率,直接可以把变量作为寄存器变量,以提高运行效率,CPU在处理register变量时,不需要从内存中加载,也不需要把其结果写回内存

register变量其实受到很多限制

个数限制 寄存器数量有限

寄存器字长度 不是任意类型数据都可以作为寄存器变量 机器字长

数据存储

硬盘 固态硬盘 内存 高速缓存(Cache) 寄存器

从左往右 容量是越来越小 造价是越来越贵 IO效率越来越快

程序存储在磁盘上 启动时 从磁盘上把数据加载到内存中 程序执行代码期间 把数据从内存中加载到Cache再到寄存器 操作系统操作的是寄存器

volatile
  • 易变的

  • volatile修饰变量,表示该变量可能随时发生意外的变化

    • 在使用volatile变量时,不能直接使用寄存器的值,需要重新从内存中加载进来
volatile int a = 10;

//如果要计算a的平方 
int s = a*a;//10*10  第一次加载进行的可能是10,但第二次加载进来的可能就不是10

int b = a; //加载
s = b*b;
const

const修饰局部变量,表示局部变量只读,不会改变局部变量的存储位置

const修饰全局变量,表示全局变量只读,改变了全局变量的存储位置

const修饰的全局变量,存储在字面值常量区

内存布局

程序(进程)内存布局

内存地址 实际上是虚拟内存

每个程序都有独立的虚拟内存

32位 0x0000 0000 -0xffffffff 有4G虚拟内存地址 3G为用户区 1G为内核区

64位 0x0000 0000 0000 0000-0xffff ffff ffff ffff

理论上有2^34G 实际上只有256T 2^48 而不是 2^64

内核和用户各自占一半

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ocoxXR0c-1683980959134)(C:\Users\asus\Desktop\新版笔记\C语言day11\images\64bit256T虚拟内存.png)]

虚拟内存需要映射到物理内存才能使用

因此出现段错误的原因有以下两点

1.访问没有权限的内存地址

2.访问没有经过映射的虚拟内存

编译

预处理 gcc -E xxx.c -o xxx.i 对xxx.c进行预处理 并生成 xxx.i文件

预处理会处理#include的指令将头文件的内容导入

预处理会删除宏 进行宏替换

预处理与进行条件编译

预处理会删除用户的注释

会检查#pragma GCC 指令

编译

gcc -S xxx.c/h /i cc以前的指令

自动生成xxx.s文件

编译阶段会进行语法语义的检查 通过后生成 汇编文件

汇编

gcc -c xxx.c/h/s/i/ as以前的指令

自动生成.o文件 目标文件

汇编会将汇编代码转化为机器指令 二进制

.h 会变成 .h.gch

链接

gcc x.o/s/c/h ld以前的指令 加上 -o可以生成指定名字的文件

链接目标文件和库文件 生成可执行程序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值