基础C语言(3)

1.写个函数,交换下面两个变量的值
char *str1 = “Hello”;
char *str2 = “world”;

#include<stdio.h>

void change(char **str1,char **str2){//形参指针,指针的指针二级指针来改变
	char *t=*str1;
	*str1=*str2;
	*str2=t;
}

int main(){
	char *str1 = "Hello";
 	char *str2 = "world";
 	printf("%s %s\n",str1,str2);
	change(&str1,&str2);
	printf("%s %s\n",str1,str2);
	return 0;
	
}

2.写个函数,交换下面两个变量的值
char str1[10] = “Hello”;
char str2[10] = “world”;

#include<stdio.h>
#include<assert.h>

char *mystrcpy(char *dest,const char *src){
	assert(dest!=NULL && src!=NULL);
	char *pdest=dest;
	while((*dest++ = *src++)!='\0');
	return pdest;
}

int main(){
	char temp[10]={0};
	char str1[10] = "Hello";
 	char str2[10] = "world";
 	printf("%s %s\n",str1,str2);
	char *p1=mystrcpy(temp,str1);
	char *p2=mystrcpy(str1,str2);
	char *p3=mystrcpy(str2,temp);
	printf("%s %s\n",p2,p3);
	return 0;
}

//或者----------------------------

#include<stdio.h>
#include<string.h>

void swap(char *str1,char *str2){//因为char字符所以我们能求到长度,如果是int的数组,就要传长度
	int len=strlen(str1);
	char stmp[len+1];
	stmp[len]='\0';
	strcpy(stmp,str1);
	strcpy(str1,str2);
	strcpy(str2,stmp);
}

int main(){
	char str1[100]="Hello";
	char str2[100]="world";
	printf("%s %s\n",str1,str2);
	swap(str1,str2);
	printf("%s %s\n",str1,str2);
	return 0;
}

一个指针数组,一个二维数组的元素输出

#include<stdio.h>

void showarr(char **arr,int len){
	int i;
	for(i=0;i<len;i++){
		printf("%s ",arr[i]);
	}
	printf("\n");
}

void showbrr(char (*brr)[20],int len){//brr是个数组 brr[5][20]。二维数组名是一个指针,指向一维数组的地址。
	int i;
	for(i=0;i<len;i++){
		printf("%s",brr[i]);
	}	
	printf("\n");
}

int main(){
	char *arr[5]={
		"万飞",
		"回答",
		"大祭司",
		"大庆",
		"确定"
	};//这个数组里面只是5个地址,因为这是个指针数组,里面的元素是指针,而不是这些字符串
	showarr(arr,5);//arr是首元素的地址,所以形参是**。因为是指针数组,本质是数组,所以要传数组长度。
	char brr[5][20]={
		"万飞",
		"回答",
		"大祭司",
		"大庆",
		"确定"
	};
	showbrr(brr,5);
	return 0;
}

int crr[5];    数组名 crr   的类型是   int *   即一个int型的指针

交换数组前两个元素,两个数组的类型和上面的形式相同,一个指针数组,一个二维数组

#include<stdio.h>
#include<string.h>

void showarr(char **arr,int len){
	int i;
	for(i=0;i<len;i++){
		printf("%s ",arr[i]);
	}
	printf("\n");
}

void showbrr(char (*brr)[20],int len){
	int i;
	for(i=0;i<len;i++){
		printf("%s ",brr[i]);
	}	
	printf("\n");
}

void sortarr(char **arr,int len){
	char *temp=arr[0];
	arr[0]=arr[1];
	arr[1]=temp;
}

void sortbrr(char (*brr)[20],int len){
	char tmp[20]={};
	strcpy(tmp,brr[0]);
	strcpy(brr[0],brr[1]);
	strcpy(brr[1],tmp);
}

int main(){
	char *arr[5]={
		"wangfei",
		"huida",
		"dajisi",
		"daqin",
		"queding"
	};
	showarr(arr,5);
	char brr[5][20]={
		"wangfei",
		"huida",
		"dajisi",
		"daqin",
		"queding"
	};
	showbrr(brr,5);
	sortarr(arr,5);
	sortbrr(brr,5);
	showarr(arr,5);
	showbrr(brr,5);
	
	return 0;
}
要修改肯定不能 char *s=“ ”;//因为这是字面值字符串,储存在常量区,不能修改
只有储存在栈区的字符数组  char str[100]=” ”  这个可以修改字符串的内容

3.编写一个程序,反序显示一个字符串的单词
how are you —> you are how

//----------------------第一种方法,先整体翻转,再单个单词一个个翻转
#include<stdio.h>
#include<string.h>

void allreverse(char *str,int len){
	int i;
	for(i=0;i<len/2;i++){
		char c=str[i];
		str[i]=str[len-1-i];
		str[len-1-i]=c;
	}
}//本身其实全部翻转不需要传递len,但是为了后面每个单词方便,才传递长度,因为翻转单个单词判断长度不方便。

void reverse(char *str){
	allreverse(str,strlen(str));
	int i=0;
	char *ws=str;
	int wi=0;//wi用来记录上次单词结束的位置
	while(1){
		while((str[i]>='a' && str[i]<='z') || (str[i]>='A' && str[i]<='Z')){
			i++;
		}
		if(i-wi>0){//i是已经计算完单个单词的长度的i
			allreverse(ws,i-wi);//ws这时候其实已经充当一个数组,每次一个单词结束就是一个数组,在这里进行单个单词的翻转
			ws=str+i+1;
			wi=i+1;//+1跳过空格到下一个单词的位置
		}
		if(str[i]=='\0')
			break;
		i++;//跳过空格;
	}
}

int main(){
	char str[100]="how are you";
	reverse(str);
	printf("%s",str);
	printf("\n");
	return 0;
	
}
//---------------------------------------第二种

#include<stdio.h>
int main(){
	int n=0,i;
	char m[100][100];
	while(1){
		scanf("%s",m[n]);
		n++;
		if(getc(stdin)=='\n'){
			break;
		}
	}
	for(i=n-1;i>=0;i--){
		printf("%s",m[i]);
		if(i>0){
			printf(" ");
		}
	}
	printf("\n");
	return 0;
}

4.编写一个程序,把一个字符串里的空格用两个%替换,要求不同定义新的字符串数组

//----企业笔试的时候一般要求最优解,这种方法就是以时间换空间,所调用的内存比较少
#include<stdio.h>

void replace(char *str){
	int len =0;
	int space=0;//空格的个数
	while(str[len]!='\0'){
		if(str[len]==' ')
			space++;
		++len;
	}
	str[len+space]='\0';
	//从后往前
	int i=len-1;//18  0~17
	int j=len-1+space;//18+5=23  0~22,这样的话就相当于有了两组空格
	while(i>=0 && j>i){//当i==j时,前面没有空格
		if(str[i]!=' ')
			str[j]=str[i];
		else{
			str[j]='%';
			--j;
			str[j]='%';
		}
		--j;
		--i;
	}
}

int main(){
	char str[100]="hw q qjwie oqw  wq";
	replace(str);
	puts(str);
	return 0;
}

//--------不提倡的方法,因为要调用的内存比较大
#include<stdio.h>
#include<string.h>

void replace(char *str){
	int len=strlen(str);	
	char tmp[len*2+1];//+1是给停止符留位置
	tmp[len*2]='\0';//创建的新数组最后一位给停止符
	int i,j;
	for(i=0,j=0;str[i]!='\0';i++,j++){
		if(str[i]!=' '){
			tmp[j]=str[i];
		}else{
			tmp[j]='%';
			++j;
			tmp[j]='%';
		}
	}
	tmp[j]='\0';
	strcpy(str,tmp);
}

int main(){
	char str[100]="as asd  wqe d";
	replace(str);
	printf("%s",str);
	printf("\n");
	return 0;
}

变量的作用域(即使用范围)

	变量必须先声明才能使用
1.	全局变量
	(1)在全局域定义的变量,所有的函数外面定义的
	(2)所有的函数都可以访问(除了静态全局变量:只能在本文件中访问)
	(3)全局变量没初始化它也会自动初始化为0,但是如果是局部变量的话没初始化就是垃圾值
	(4)一个函数如果要使用定义在其他文件中的全局变量
		链接:#include"  .h"  //用户自定义头文件
		extern int a;//声明一个其他文件中的全局变量a,对系统编译有欺骗性,明明没有在其他文件中,系统也不会报错,但是你其实并没有。函数也一样可以这样声明
		编译的过程一个个.c进行编译,extern就是暂缓报错,这个.c编译完在去那个.c找(即头文件链接的.h)
	(5)生命周期 是整个程序运行期间
预处理  编译 汇编 链接
2.	局部变量
	在函数内部定义的变量
	形参也是局部变量 
	函数调用时会开辟内存空间,结束函数释放空间(回收),局部变量生命结束(static 静态局部变量除外),回收的地址可能会被其他值占用,也可能被取消权限(即不能访问)
3.	块变量
	在语句块内部定义的变量,有些书上也把块变量归为局部变量
	块变量只能在语句块中使用
	{
		语句块
	}//只要有{}就是语句块,块变量不会自动初始化

gcc -std=c99 t.c //命令行改变运行标准

变量能重复定义

int n;//全局,全局不初始化就是0
main(){
	int n=10;//局部
	printf("%d",n);
}//打印的是局部的n=10

在同一个作用域下,变量不能重复定义
在不同的作用域下,能够定义同名的变量;

int n;//全局
main(){
	int n=10;//局部
	{
		int n=100;
		prinf(n)
	}
}//这个打印的是块变量中的n=100

局部优先原则:
局部变量会隐藏全局同名变量
块变量也会隐藏同名的局部变量

如果想访问同名的全局变量
1.//第一种方法用指针
int *pn=&n
int n;

2.//第二种用函数
int getn(){
	return n;
}
printf(getn());

3.//最好的方法.
{
	extern int n;  //声明全局的变量n,因为extern访问的只有全局变量
	printf(“%d\n”,n);
}


一个项目中,全局变量越少越好,多了不利于开发,

变量的存储域(内存位置)

Linux下c语言程序的内存分布
	程序(.out  .exe)存储在磁盘上的静态代码
	运行程序的时候,会把静态代码加载到内存区域,形成内存映像
	一个程序4G内存映像(虚拟内存) ,因为不同进程都是0~4G,虚拟可以避免冲突,不同进程返回地址答案可能不同
,系统会帮我们把虚拟内存映射到物理内存(操作系统范畴)

在这里插入图片描述

程序的内存分布:

内核空间:4G里有1G用户访问不到  0xFFFFFFFF~0xC0000000  ( 3G 0xC0000000 )   
—————
Main函数参数 和 环境列表
————
栈区:存储局部变量,块变量,函数调用开销。自动分配和回收内存,从大到小使用,即向↓(用虚线是因为栈区和堆区的大小都是不固定的)地址0xbf 打头   因为在3G附近
------
堆栈缓冲区(栈区从大到小,堆区从小到大,这样内存大小就可以随心分配,代码更灵活)
	加载的共享库
-----
堆区:动态内存,需要程序员手动申请,手动释放,有小到大使用,即向↑
————
全局区(全局数据区):区内分为: 
	BSS段:存储未初始化的全局变量和未初始化的静态(static)变量 (程序在加载时会自动把BSS段清零、又称擦除)
	数据段:存储已经初始化的全局变量和已初始化的静态变量
常量区(存储常量,字面值字符串) 权限:(只读):
代码区(存储代码)权限:(只读)(可执行):   
保留区域: 0x00000000~

int main(int argc,char *argv[]){}
Main 函数的参数 命令行: ( ./a.out 1 2 3 45,(命令行参数))会直接传递参数给main函数,就能直接用

Linux 32位(寻址范围)
32根数据总线
64位的话2^64

auto 自动的

 auto int x;//默认自带,所以省略auto,但是在C++里面就是自动类型推导

static 静态的

	存在静态全局区,会自动初始化
	静态局部变量和普通局部变量:静态存在全局区,普通存在栈区,生命周期也不一样,静态是整个程序,普通是函数调用期间。
	静态只会执行一次(类似定义全局变量), 后面改变值会保留
	但修改的值作用域一样,即局部还是局部,静态还是只在那个函数中可用。
	
可以在函数中返回静态局部变量的地址://正常局部局部变量的地址不能返回,因为地址会被回收
	static n;
	return &n;

在写字符串时,return pdest 虽然也是返回局部变量,但是pdest=dest,本身dest的地址在函数调用完后还是存在(因为它是在main函数中),所以可以返回。要判断地址会不会被回收,来判断能不能return

静态全局变量和普通全局变量的区别

静态全局变量只允许在本文件中使用,相当于改变了作用域
	存储位置没发生改变,生命周期没有改变
	
静态函数:
	只限制在本文件中调用

register 寄存器

程序是加载到内存中形成4G虚拟映像,真正运行在CPU,所以也要加载进来,加载到CPU中的寄存器,CPU的读取速度:读取寄存器>内存>固态硬盘>普通
	寄存器数量有限
	register int x;//申请把x作为寄存器变量
寄存器变量存储在寄存器中,不在内存区域里,不能对寄存器取&
寄存器变量一般要满足机器的字长单位(word)一般等于1或2字节
	char short int 可行    
	double不行
寄存器变量的读取效率最高,所以频繁进行读写的变量作为寄存器变量比较好,
register 只是一种请求,寄存器变量有限,不是所有有reigster都实质上变为寄存器,这是系统决定的,不是程序员

volatile 易变的

volatile int a;
用在特定的场合,多线程
声明的变量表示该变量的值有可能被其他线程修改
	volatile int x=10;
	int *p=&x;
	int n=*p;
	int res=n*n;//10*10
	int res2=x*x;//x就不确定
下面是在x被声明为volatile的类型后,如何正确算x的平方的方法:
	int n=x;
	int res=n*n
随时可能发生变化的值
表示取该值运算时不能直接从寄存器上面取,只能从内存中取
	
多进程:多软件一起使用
多线程:例如QQ中打开多个窗口,同时和多个人聊天

科学计数

3e1.2错误,指数不能是小数
3e错误
枚举常量:
	enum Week{MON,TUE,WED,THR,FRI,STA,SUN};

常量

定义常量时,定义时并初始化,否则错误
const int x;
x=1;//错误

其实const不是一个完完全全不能修改
cosnt int x = 1;
int const y = 2;

const char * p1;// const修饰*p1  *p1只读  p1可以修改
char const * p2;// const修饰*p2  *p2只读  p2可以修改
char * const p3;// const修饰p3   p3只读   *p3可以修改
const char * const p4;//
char const * const p5;// const修饰p5 *p5,  p5和*p5都只读
指针常量
本质是一个常量,指针是一个常量, p只读   char *const p
常量指针
本质是一个指针,指针指向常量   *p只读  const char *p

typedef 给类型取别名

typedef int* IP
IP p1,p2  都是指针
int *p1,p2  p1是指针,p2整型
typedef int I
unsigned I a;//不可以,这样是错误的,

typedef void (*sighandler_t)(int)

void (*f)(void); // 函数指针变量
typedef void (*F)(void)  // 定义函数指针类型
F g;//
g=get;
g();

定义数组类型
typedef int brr[5];//可以这么写,所以是一个合法的类型,类型是 int [5]
brr x;  x是 int 型的有5个元素的数组

int *(*f[5])(int ,int *(*f)(int *))={};  //数组就能{}  ,指针就能NULL
//先看标识符  f
从标识符往右找,[]说明是数组,()说明是函数
右边如果是 )  就往左找,有*说明是指针
因为是数组,所以考虑其中存什么
f[5]看做一个整体 X  重复上面的步骤,所以里面存的是指针
再考虑指针的类型  
把 *X看做一个整体 g  , 所以是函数

所以是一个函数指针数组


推箱子:
	向左向右向上向下
	闯关
	回退
	复位
	退出
	闯关
有数组 int  二维数组
      0代表空白区域  1代表墙 2代表推推人…
#include<stdlib.h>
While(1){
	System(“clear”)
	Show(地图)
	用户控制输入 include “get_keyboard.h”
	Int key=get_keyboard();
	Switch(key){
		Case KEY_UP
			Puts(up);break;
		Down
		KEY_BACKSPACE
		Left
		…
}	
}

指针与数组名的区别?

1.指针即内存地址,数组名也代表一个内存地址
2.sizeof求大小的时候不同
3.数组名是一个指针常量,即数组名不能放在=左边
		int arr[3]={1,2,3};
		int *p=arr;
		p=p+1  printf(%d,*p) 输出数组中的下一个元素
		但是arr=arr+1就不行
		int *const p=arr;  
	4.指针取地址是二级指针,数组名取地址即数组指针
		int (*parr)[5] = &arr;  
	5.	数组名+整数   ===  指针+整数
		数组名[整数]  ===  指针[整数]
	6.一维数组作为参数传递时,退化成了指针
		func(int arr[10]){//在形参形表中的数组 完全和指针一样
			sizeof(arr)
			arr = arr+1;
		}

const的作用?

	1.指针形参用const修饰,防止函数内部对实参内容进行修改,增加代码的健壮性
	2.定义只读变量  常量   指针常量  常量指针
	3.加上const修饰,防止意外修改
	4.权限可以变小 但不能提升(C语言中还有可能只是警告,但是C++中全部报错)
	
		int n = 0;
		const int x = 0;
		const int *p = &n;//权限受到了限制

		提升权限 
		int *p1 = &x;//提升权限 编译器要么是报错 要么是报警告

Static的作用?

c++中还有很多作用

1.修饰局部变量 改变存储位置 改变了生命周期
2.修饰全局变量 改变作用域范围
3.修饰函数 限定作用范围

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

void *malloc(size_t size);
#include<stdlib.h>   //包含在头文件stdlib.h
手动申请:
申请size个字节堆内存
返回申请好的堆内存的首地址
这片内存用于存储什么数据完全取决于程序员自己
程序员只需要转换成自己想要的类型即可

int *p=malloc(4);//申请一个int 型,刚好4字节
*p=100;
printf(%d,*p)

int *p1=malloc(4*10);//申请一个数组
for(int i=0;i<10;i++){
	printf”%d”,*(p1+i))
}

int arr[4][3]
和下面的式子一个效果
int (*parr)[3]=malloc(4*4*3) 并不关心这片内存存储什么数据  
parr[0][1]=100;

如果申请失败,会返回NULL
手动释放:释放堆内存
void free(void *ptr);
参数ptr的值一定要是申请动态内存时返回的值
不能释放一个不是堆内存的地址
同一块堆内存不能多次释放,多次释放 会核心已转储
堆内存用完之后,立刻free
内存泄漏:	动态内存忘记或者延迟释放
			动态内存没有或者忘记释放,会一直被占有
			valgrind   检测程序是否存在内存泄漏	

动态内存在使用时,要注意只能使用申请大小的空间,不能越界访问或者修改
动态内存越界访问很危险

其他申请释放函数

1.void *calloc(nmenb,suze)
也是动态内存的申请
   
malloc只有一个参数
calloc 申请nmemb个size个字节的内存

int *p=calloc(1,4);
double *p=calloc(10,8)
malloc申请的值可能有垃圾值,但是calloc会全部清0,不会有垃圾值情况
在使用堆内存之前,一定要判断该内存是否申请成功

int *p=calloc(1,4);
*p=111;
free(p);
p=null;//释放以后把它置为null  这是良好的编程习惯

申请内存是以页为单位,一次性申请33页
一页4kb,4096个字节,即使只申请4个字节,但是给了33页,所以后面还有很多字节,但是只能使用4个
即使把所有的堆内存都释放了,依然会保留一开始的33页

free后
*p=100;还是可以用,但是我们不能这么用

申请多少,使用多少,释放的内存不能再使用了




2.realloc(ptr,size)
调整堆内存大小
int *p=malloc(10*4);
发现不够用
p=realloc(p,100*4);//扩大了内存,之前保留的值依然会保留

参数ptr是之前申请动态内存返回的地址
size是把动态内存调用为size个字节
如果需要使用这个内存,必须重新接受返回值,但是返回值可能不一样
要么的后面内存不够配给,所以建一个新的内存,把原来的数据拷贝到新的,然后返回新的ptr,
要么就是后面有足够的空余内存,返回原来的ptr

申请4个字节,不单单只分配4个字节,事实上分了16个
剩下的12个字节的内存控制块用于记录该块内存的大小,是否空闲,以及下一块内存的位置

内存控制块的信息不能被破坏。

内存碎片:
	动态内存在重复的申请和释放的过程中,有一些大的动态内存片会分配给小的动态内存片使用,其中会有一部分无法访问的,就是内存碎片

如果修改控制块的信息
后续必出段错误

free一个后
char *p4=malloc(1)
free的那个和p4指向同一个字节
申请动态内存时,首先会检查之前分配的动态内存快是否有空闲且大小>=申请的内存大小的空间,如果有,这把这块内存返回,并标识为已使用

因为每个动态内存都要12个字节的控制块,所以堆内存一般用于大片内存

在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值