基础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;
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>1){//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
内存泄漏:动态内存没有或者忘记释放,会一直被占有
动态内存在使用时,要注意只能使用申请大小的空间,不能越界访问或者修改
动态内存越界访问很危险
其他申请释放函数
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个字节的控制块,所以堆内存一般用于大片内存