文章目录
指针
指针的应用场景
- 交换两个变量
- 函数返回多个值,某些值就只能通过指针返回
- 传入的参数实际上是需要保存带回的结果的变量
int a[]={1,2,3,4,5,6,7,12,36,45,98};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0],&min,&max);
printf("min=%d,max=%d\n",min,max);
void minmax(int a[],int len,int *min,int *max)
{
int i;
*min=*max=a[0];
for(i=1;i<len;i++){
if(a[i]<*min){
*min=a[i];
}
if(a[i]>*max){
*max=a[i];
}
}
}
- 函数返回运算的状态,结果通过指针返回,让函数返回特殊的不属于有效范围内的值来表示出错。但是当任何数值都是有效的可能结果时,就得分开返回了。java中利用异常机制处理
- 需要传入较大的数据时用作参数
- 传入数组后对数组做操作
- 需要用函数来修改不止一个变量
- 动态申请内存时
指针常见错误
- 定义了指针变量,还没有指向任何变量,就开始使用指针
指针与const
int *const q=&i;//指针是const,后面不能在指向其他
int *const q=&i;//表示一旦得到了某个变量的地址,不能再指向其他变量,q是const
*q=26;//没问题
q++;//错误
const int*p=&i;//不能通过这个指针去修改那个变量
const int*p=&i;
//表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
*p=26;//错误,(*p)是const
i=26;//可以这样写
p=&j;//可以这样写
判断方式:看const在*前面还是后面,
- const在*前:它所指的东西不能通过指针被修改;
- const在*后,表指针不能被修改指向其他变量
- 总是可以把一个非const的值转换成const的。
- void f(const int*x);//在函数内部,不会更改指针所指的变量值
- 当要传递的参数的类型比地址大的时候,这是常用的手段:即能用比较少的字节数传递给参数,又能避免函数对外面的变量修改
- const int a[]={1,2,3,4,5,6,};数组变量已经是const的指针了,而这里的const表明数组的每个单元都是const int
- 因为把数组传入函数时传的是地址,所以那个函数内部可以修改数组的值
- 为了保护数组不被函数破坏,可以设置参数为const
- int sum(const int a[],int length)
指针的运算
- 当给指针+1;就是给这个指针指的那个类型的地址加相对的sizeof个字节数(sizeof (int)==4)
- *(p+1)指的是先运算再取加1后的地址
- 对于数组来说
char *p=ac;
*(p+n)<——>ac[n]
- 两个指针相减:这两个地址之间能放几个这样类型的东西,即这两个地址的差除sizeof(对应类型)所得的数
- *p++:取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
- *的优先级比++低
- 常用于数组类的连续空间操作
如利用指针循环遍历数组
int main(int argc,char const *argv[])//这样写参数不用return 0;
{
char ac[]={0,1,2,3,4,5,6,-1};
//-1不是有效数字
char *p=&ac[0];
while(*p!=-1){
//也可以用for(p=ac;*p!=-1; )
printf("%d\n",*p++);
}
return 0;
}
- 指针可以做>、<、=、!=等,也就是比较它们在内存中的地址,数组单元的地址线性递增
- 有虚拟的0地址,但0地址通常是不能碰的,所以指针不应该具有0值,因此可以用0地址来表示特殊的事情
- 返回的指针是无效的
- 指针没有被真正的初始化(先初始化为0)
- NULL是c语言预定义的符号,表示0地址
- 有的编译器不允许你用0来表示0地址
指向不同类型的指针不能直接相互赋值
- 但指针可以进行强制类型转换 ,(尽量不要这么干)
- int *p=&i;void*q=(void*)p; 这并没有改变p所指的变量类型,而是让后人用不同的眼光通过q看,看做他所指的变量是个void
- void*表示不知道指向什么东西的指针
- 计算时与char相同,但不相通
动态分配
- C99可以用变量做数组定义的大小
- 动态分配
int *a=(int*)malloc(n*sizeof(int));
,(n个int的空间)后面可以将a当做数组使用 - 动态申请使用了之后再
free(a);
,只能还申请来的空间的首地址,比如指针p在p++后再free(p)是不行的。free(NULL)是可以的,这是为了有个好习惯 - free过再free,地址变过再free,free的不是申请来的,都是不被允许的
- 向malloc申请的空间的大小是以字节为单位的,返回的结果是void*,需要类型转换为自己需要的类型,申请失败则返回0或NULL
字符串操作
putchar
- int putchar(int c);
- 向标准输出写一个字符
- 返回写了几个字符,EOF(-1)表示写失败
getchar
- int getchar(void);
- 从标准输入读入一个字符
- 返回类型是int是为了返回EOF(-1)
字符串数组
- char **a
- a是一个指针,指向另一个指针,那个指针指向一个字符(串)
- char a[ ][10 ]指每一个 都有10个字符的空间
- char *a[ ],a[0]相当于char*,a[0]指向外面某一处空间,a[1]也是等等
字符串函数的一些套路
- 复制一个字符串常用的套路:
char*dst=(char*)malloc(strlen(src)+1);
//+1是因为结尾的0
strcpy(dst,src);
char s[]="Hello";//找到l前面的,并输出
char *p=strchr(s,'l');//找到第一个l
char=strchr(p+1,'l');//从第一个l后再找l并输出这后面所有字符
printf("%s\n",p);
char s[]="Hello";//找到l前面的,并输出
char *p=strchr(s,'l');//找到l,此时p指向的是第一个l
char c=*p;//c暂存此时p所指的值
*p='\0';//让p所指的地方变为\0
char *t=(char*)malloc(strlen(s)+1);
strcpy(t,s);
printf("%s\n",t)
free(t);
*p=c;//将变为\0的地方变回去
结构类型
枚举
- 用枚举而不是定义独立的const int 变量
- enum 枚举类型名字{名字0…名字n};
- 枚举类型的名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int,值则依次从0到n.如enum colors{red,yellow,grenn}red就是0,yellow是1,等就这样创建了三个常量
- 当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字
- 枚举量可以作为值
- 枚举类型可以跟上enum作为类型,但实际上是以整形来做内部计算和外部输入输出的
- 但在用的时候要带上enum,c++可以不带
- 一般在定义枚举时,会在最后一个写上类似numcolor的,可以用来记录定义了多少个量。这样在需要遍历所有枚举量或需要建立一个用枚举量做下标的数组就很方便。
- 声明枚举量时值不一定要按顺序来,也可以指定值enum colors{red=1,yellow,grenn=5}
- 枚举并不太好用,但比宏好些,因为它有int 类型
结构
- 一个结构就是一个复合的数据类型,在里面可以有很多各种类型的成员,然后用一个变量来表达那么多的数据。
//如以下
struct data{
int month;
int day;
int year;
};
struct data today;//使用,定义了一个struct data类型的叫today的结构变量。
struct data today={07,31,2014};//定义并赋初值
struct data thismonth={.month=7,.year=2014};//定义并赋初值的第二种方式,没赋的变量为0
today.year//具体使用结构体某个元素的调用方法
---------------------------------
struct {
int x;
int y;
}p1,p2;//第二种方式:声明的同时定义变量
//p1和p2都是一种无名结构,里面都有x和y
---------------------------------
struct point{
int x;
int y;
}p1,p2;//p1和p2都是point,里面有x和y的值
- 和本地变量一样,在函数内部声明的结构类型只能在函数内部使用,所以通常在函数外部声明结构类型。
- 结构用结构变量点
.
运算符和成员名字访问其成员;要访问整个结构,可以用结构变量的名字 - 对于整个结构,可以做赋值、取地址、也可以传递给函数参数
pl=(struct point){5,10};//相当于pl.x=5;pl.y=10
pl=p2;//相当于pl.x=p2.x;pl.y=p2.y
- 结构变量的名字不是结构变量的地址,必须用&运算符
struct data *pData=&today;
结构与函数
- 结构作为函数参数
int numberOfDays(struct data d)
- 整个结构可以作为参数的值传入函数
- 这时候是在函数内新建一个结构变量,并复制调用者的结构的值
- 也可以返回一个结构
- 没有直接的方式可以一次scanf一个结构,也不可以写一个函数来读入结构,传入函数的结构,只是新建一个函数,只传入了值。
- 可以在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者,在函数外再将返回的值赋值给原先的结构变量,如下
//做函数一次性scanf结构值
#include <stdio.h>
#include <stdlib.h>
struct point{
int x;
int y;
};
struct point getStruct(void);//声明函数,该函数没有参数,返回一个结构变量
void output(struct point);
int main(int argc,char const *argv[])
{
struct point y={0,0};//声明一个结构变量
y=getStruct();//将函数返回的那个临时结构变量的值赋给原先的结构变量
output(y);
}
struct point getStruct(void)
{
struct point p;//函数内暂时声明一个结构体,函数消失,此结构体消失,但可以有返回值
scanf("%d",&p.x);
scanf("%d",&p.y);
scanf("%d,%d",p.x,p.y);
return p;
};
void output(struct point p)
{
print("%d,%d",p.x,p.y);
}
- 但比以上更好的方式是传指针
- p->month=12,
用->指针所指结构变量的成员
- p->month=12,
struct data{
int month;
int day;
int year;
} myday;
struct data8p=&myday;
(*p).month=12;//指针所指结构变量的成员
p->month=12;//指针所指结构变量的成员的更简洁的方式
以下是指针的方式实现一次性可以scanf结构值的方法
//指针的方式实现一次性可以scanf结构值的方法
struct point{
int x;
int y;
};
struct point getStruct(struct point*);//声明函数,该函数没有参数,返回一个结构变量
void output(struct point);
int main(int argc,char const *argv[])
{
struct point y={0,0};//声明一个结构变量
getStruct(&y);//将函数返回的那个临时结构变量的值赋给原先的结构变量
output(y);
output(*getStruct(&y));//用*取出这个函数返回的那个东西(一个指针)作为一个变量使用
print(getStruct(&y));
getStruct(&y)->=0;
*getStruct(&y)=(struct point){1,2};
}
struct point* getStruct(Struct point *p)
{
scanf("%d",&p->x);
scanf("%d",&p->y);
scanf("%d,%d",p->x,p->y);
return p;//返回,这是一个很好的套路,使得getstruct可以直接作为print的参数 print(getStruct(&y));
};
void output(struct point p)
{
print("%d,%d",p.x,p.y);
}
void print(const struct point *p)
{
print("%d,%d",p->x,p->y);
}
结构数组
- struct date dates[100];//100个结构数组,100个类似的结构,定义结构数组
- struct data datas[]={{4,5,2005},{2,4,2005}};//定义并初始化赋值
- 结构中的变量可以是另一个结构。如一个r结构中有pt1,pt2两种结构。x是pt1的一个成员。另一个rp指针指向r结构。
- 则:r.pt1.x等价于rp->pt1.x等价于(r.pt1).x等价于(rp->pt1).x
- (但rp->pt1->x不等价于以上,因为pt1不是指针)
- 可以结构中是数组,可以数组中是结构
类型的定义
- typedef 声明一个已有的数据类型的新名字。
- 如typedef int Length;//使得Length成为int类型的别名。
- 这样,Length这个名字就可以代替int出现在变量定义和参数声明的地方
- typedef char*Strings[10];//Strings是10个字符串的数组,字符指针的数组
typedef struct Adate{
int month;
int day;
int year;
}Date;//在有typedef情况下,Date是新定义的别称,而这之前的都是原来的名字。从而简化了复杂的名字
Data d={9,1,2005}//使用新名字
联合
union AnElt{
int i;
char ch[sizeof(int)];//即应该是4个字节的数组
}CHI;
//CHI这四个字节中可以被看作是i,也可以看作是ch的数组
//如果说CHI.i=1234的话,那就会往那四个字节中写下1234的16进制所代表的数
//1234的16进制是04D2。按应是00、00、04、D2。
//但现在x86是小端机器,低位在前,则实际顺序是D2、04、00、00
- union和struct非常相似但又有所不同
- 存储
- 所有成员共享一个空间
- 同一时间只有一个成员是有效的
- union的大小是其最大的成员
- 初始化
- 对第一个成员做初始化
链表
可变数组
自己做一个可变数组的库
#include <stdio.h>
#include <stdlib.h>
#include "array.h"
const BLOCK_SIZE=20;//当目前数组不够用时,每次新增长的数量
//typedef struct {
// int *array;
// int size;
//}}Array;
Array array_creat(int init_size)//表示用来创建一个数组的函数
{
Array a;//结构变量
a.size=init_size;
a.array=(int*)malloc(sizeof(int)*a.size);
return a;//返回的是array变量本身,不是指针
//返回指针的话,这个本地变量就无效了
}
void array_free(Array *a)//用来回收数组空间
{
free(a->array);
a->array=NULL;//为了更保险,防止别人调用两次
a->size=0;//为了更保险
}
//封装
int array_size(const Array *a) //告诉数组现在有多少单元可以用
{
return a->size;
}
int*array_at(Array *a,int index)//访问数组中的某个单元
{
if(index>=a->size){//若发现越界
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);//每次让他增加20个
//index/BLOCK_SIZE先算出位于哪个BLOCK里,
//+1是index/BLOCK_SIZE的从1开始数的序号
//增加后的末尾-原来的
}
return &(a->array[index]);//因为返回的类型应该是个指针
}
/**若主函数不写这一句array_at(&a,0)=10;
则要写以下功能完成,并在主函数中调用这些函数
int array_get(const Array*a,int index)
{
return a->array[index];
}
void array_set(Array *a,int index,int value)
{
a->array[index]=value;
}
*/
void array_inflate(Array *a,int more_size)//让这个数组长大
{
//重新申请新的空间
int *p=(int*)malloc(sizeof(int)(a->size+more_size));
//将原来空间的东西循环放进新的空间中
int i;
for (i=0;i<a->size;i++){//这一段可以换成标准库里的一个函数叫memcpy
p[i]=a->array[i];
}
free(a->array);
a->array=p;
a->size+=more_size;
}
int main(int argc,char const *argv[])
{
Array a=array_create(100);
printf("%d\n",array_size(&a));
*array_at(&a,0)=10;//这句话可以将10写到第0位上
//上句话函数调用返回的是指针,在函数前加*号,指指针所指的那个东西给他赋值
int number=0;
int cnt=0;//计数器
while(number!=-1){
scanf("%d",number);
if(number!=-1)
*array_at(&a,cnt++)=number;
//scanf("%d",array_at(&a,cnt++));//不断将东西放入,和上面两句意思相同
}
array_free(&a);
return 0;
}
#ifndef ARRAY_H
#define ARRAY_H
typedef struct {
int *array;
int size;
}Array;
//若上面此处Array前有个*号。
//再如果有个变量a,Array a;
//那么这个a其实就是指针
//若再想在函数中写个本地变量Array a就做不到
//这样得到的Array a一定从某个地方制造出来的东西,如动态申请一块内存
//且后面再用时,让人容易不把它当指针看
Array array_creat(int init_size);//表示用来创建一个数组的函数
void array_free(Array *a);//用来回收数组空间
int array_size(const Array *a);//告诉数组现在有多少单元可以用
int*array_at(Array *a,int index);//访问数组中的某个单元
void array_inflate(Array *a,int more_size);//让这个数组长大
#endif // ARRAY_H
- 但仍有很多的缺点,每申请一次空间就要拷贝一次,浪费时间。有时还回出现,有空间却申请不了的情况
- 若是链表就会好很多,不用再不停的申请复制过去
链表
//定义节点,节点中有值和指针
typedef struct_node{
int value;
struct_node *node;
}Node;
链表的增删改查
#ifndef NODE_H
#define NODE_H
typedef struct_node{
int value;
struct_node *next;
}Node;
#endif // NODE_H
//有error未消,看个思路
#include "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct_node{
// int value;
// struct_node *node;
//}Node;
//第四种方案重新再定义一个结构
typedef struct_list{
Node*head;
//Node*tail;//创建一个指针永远使他指向最后一个
//这样就不用再遍历寻找最后一个
}List;
void add(Node*head,int number);//函数原型声明
void print(List *pList);
int main(int argc,char const *argv[])
{
List list;
list.head=NULL;
//list.tail=NULL;
//读入一串数字,直到输入-1为止
int number;
do{
scanf("%d",&number);
if(number!=-1){
//加到链表中
head=add(&list,number);//第三种方案,传指针进去
//第二种方案head=add(head,number);
}
}while(number!=-1);
//遍历输出一下
print(&list);
//输入数字,查询到并删除它
scanf("%d",&number);
//需要循环遍历
int isFound=0;
for (p=list.head;p;p->next){
if(p->value==number){
printf("找到了\n");
isFound=1;
break;
}
if(!isFound){
printf("没找到\n");
}
Node *q;//删除节点用到,指向前面的节点
//使得前一个节点链的是删除节点的下一个节点
//删除节点
//像q->next,q在变量左边,代表要用这个指针
//则指针一定不能为空,要有代码判断
//下面这个循环中p在循环条件中有判断,而q没有
//如第一个就是要删的,则q=NULL
//此时就不该是q->next=p->next;
//而应该是head->next=p->next
for (q=NULL,p=list.head;p;q=p,p=p->next){
if(p->value==number){
if(q){
q->next=p->next;
}else{
list.head=p->next;
}
free(p);
break;
}
}
//链表的清除
for(p=head;p;p=q){
q=p->next;
free(p);
}
return 0;
}
}
//添加到链表的函数
void add(List*pList,int number)
{
//不断申请得到指针
Node *p=(Node*)malloc(sizeof(Node));
p->value=number;//做初始化
p->next=NULL;//新的哪一个
//找到最后一个,遍历去找
Node*last=pList->head;
if(last){//如果last不是NULL
while(last->next){//last还有next的话
last=last->next;
}
//那个新的接到最后一个后面上去
last->next=p;//last的后面接p
}else{
pList->head=p;
}
//使用时会修改Head的值,直接传入head不行
//考虑返回指针,当然也可以将指针作为全局变量,但这是有害的
}
//遍历输出函数
void print(List *pList)
{
Node *p;
for(p=plist->head;p;p=p->next){
printf("%d\t",p->value);
}
printf("\n");//回车结束
}
程序结构
全局变量
- _func_这是一个字符串,表达当前这个函数的名字。
- 本地变量指定义在函数内的
- 全局变量会被初始化
- 函数内部存在与全局变量同名的变量,则全局变量被隐藏,在更小的范围内可以定义与大范围内变量相同的名字,大范围内的会被隐藏。
静态本地变量
- 本地变量定义时前面加static
- 函数离开时,静态本地变量会继续存在并保持
- 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
- 静态本地变量实际上是特殊的全局变量,具有全局的生存期,函数内的局部作用域
- static在这里是局部作用域(本地可访问)
- 它们位于相同的内存区域
*返回指针的函数
- 返回本地变量的地址是危险的,因为一旦离开函数本地变量就不存在了(不受控)
- 返回全局变量或静态本地变量的地址是安全的
- 返回在函数内malloc的内存是安全的,但是容易造成问题
- 最好的方法是返回传入的指针
关于全局变量的一些贴士
- 不要用全局变量来在函数间传递参数和结果
- 尽量避免用全局变量
- 使用全局变量和静态本地变量的函数是线程不安全的
宏定义与编译预处理指令
- #开头的是编译预处理指令
- 这并不是c 语言因此不用加分号,但c中可以用且常见
- #define用来定义一个宏:
#define PI 3.14159
- 没有分号,完全的文本替换
- 若一个宏的值中有其他宏的名字,会再一次被替换
- 若一个宏的值超过一行,最后一行的行末要加\
- 宏的值后面可以有注释
- 也可以定义没有值得宏,这类宏用于条件编译,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
- 预定义的宏:如_LInE_表示这个源代码文件的行号,_FILE_、_DATE_、_TIME_、_STDC_等
- 宏也可以定义像函数的宏,可以带参数
如:
#define cube(x) ((x)*(x)*(x))//空格隔开的前面是名字,后面是值后面会被替换
int main(int argc,char const *argv[])
{
printf("%d\n",cube(5));
return 0;
}
//错误的定义方式
#define RADTODEG1(x) (x*57.29578)
#define RADTODEG2(x) (x)*57.29578
int main(int argc,char const *argv[])
{
printf("%f\n",RADTODEG1(5+2));
//运行过程中根据宏定义会被替换为
//printf("%f\n",(5+2*57.29578));
printf("%f\n",180/RADTODEG2(1));
//会被替换为printf("%f\n",180/(1)*57.29578);
}
- 因此定义带参数的宏时,一切都要有括号,整个值要有括号,参数出现的每个地方都要有括号
- #define RADTODEG(x)((x)*57.29578)
- 可以带多个参数
#define MIN(a,b) ((a)>b)?(b):(a))
- 也可以组合嵌套使用其他宏
- 部分宏会被inline函数替代
大程序结构
- 新建项目,将多个.c的源代码文件加入进去
- 有的编译器会有编译、构建两个按钮,构建是对整个项目做链接
头文件
- 把函数的原型(原型的声明)放到一个头文件(.h结尾的),在需要这个函数的源代码文件(.c文件)中,#include “这个头文件名”,就能让编译器在编译的时候知道函数的原型
- #include也是编译预处理指令即在编译前就处理了
- #include做的是文本插入
- #include有两种形式
- “ ”要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,若没有,到编译器指定的目录去寻找。
- < >让编译器只在指定的目录去找,一般系统给的用这个
- 环境变量和编译器命令行参数也可以指定寻找头文件和目录
- #include 不是用来引入库的,现在的c语言编译器会默认引入所有标准库
- #include <stdio.h>中仅有原型而没有具体函数定义的代码
- 在使用和定义这个函数的地方都应该有#include这个头文件
- 一般的做法是任何的.c都有对应的同名的.h,把所有对外公开的函数原型和全局变量的声明都放进去
- 不对外公开的函数,在函数前面加上static就使他成为只能在所在编译器单元中被使用的函数
- 在全局变量前加static就使他成为只能在所在的编译单元中被使用的全局变量
声明
- 在一个文件中定义了全局变量,在另一个文件中要使用,就需要声明这个全局变量。
- 在头文件中声明全局变量gAll:
extern
int gAll,extern 变量名 - 只有声明可以被放在头文件中,否则会造成一个项目中多个编译单元有重名的实体,某些编译器允许多个编译单元里有同名的函数,或用weak修饰符强调这种存在
- 同一个编译单元,同名结构不能被重复声明。为避免这种情况,引入标准头文件结构,开头有#ifndef _MAX_H_和#define _MAX_H_即结尾处的#endif,#pragma once也有相同作用,但不是所有编译器都支持
函数指针及其应用
void f(int i)
{
printf("in f(),%d\n",i);
}
int main(void)
{
void (*pf)(int)=f;//定义了一个新的变量,叫pf,它的类型是一个指向函数的指针,这个函数返回void,参数表是int。用pf变量指向它
(*pf)(10);//通过指针来调用f函数
return 0;
}
//根据用户输入,来做不同的动作,执行调用不同的函数
void f(int i)
{
printf("in f(),%d\n",i);
}
void g(int i)
{
printf("in g(),%d\n",i);
}
void h(int i)
{
printf("in h(),%d\n",i);
}
int main(void)
{
int i=0;
void(*fa[])(int)={f,g,h};//定义了一个叫fa的数组,做了一个函数指针的数组,这个数组中每一项都是一个函数指针,给他初始化为f,g,h
scanf("%d",&i);
if(i>=0&&i<sizeof(fa)/sizeof(fa[0]))
{
(*fa[i])(0);//根据输入0,1,2来调用对应不同的函数
}
return 0;
}
int plus(int a)(int b)
{
return a+b;
}
int minus(int a)(int b)
{
return a-b
}
void cal(int (*f)(int,int))
{
printf("%d",(*f)(2)(3))
}
int main(void)
{
cal(plus);//可以将一个函数作为参数传到另一个函数中
cal(minus);//按照传入的函数来做
return 0;
}
文件
输入,输出格式
输出
%[flags][width][.prec][hlL]type
Flag | 含义 |
---|---|
- | 左对齐 |
+ | 在前面放+或- |
(space) | 正数留空 |
0 | 0填充 |
int main(int argc,char const *argv[]){
printf("%9d\n",123);//9表总共会占据9个字符的空间靠右对齐
printf("%+9d\n",123)//靠右对齐的,输出+123
printf("%-9d\n",123)//靠左对齐的
printf("%+-9d\n",123)//输出+123且靠左对齐的
printf("%09d\n",123)//输出000000123
}
width | 含义 |
---|---|
number | 最小字符数 |
* | 下一个参数是字符数 |
.number | 小数点后的位数 |
.* | 下一个参数是小数点后的位数 |
%.nf | 显示精度,对浮点数表示输出n位小数 |
%m.nf | 共m位,小数点后有n位 |
int main(int argc,char const *argv[]){
printf("%9.2f\n",123.0);//总共的整个输出要占据9个字符的位子,小数点后有2位
printf("%*d\n",6,123);//6是满足*的,6会被填到*里头,输出123前面有3个空格,且共占6个字符的位置,
//将原来放在字符串里的6变成后面的一个参数,也就可以是个变量
int a=10;
printf("%d%%",a;)//输出10%
return 0;
}
类型修饰 | 含义 |
---|---|
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
int main(int argc,char const *argv[]){
printf("%hhd\n",12345);//仅拿他的最低位作为整数的char输出得到57
return 0;
}
type | 用于 |
---|---|
i或d | int |
u | unsigned int |
o | 八进制 |
x | 十六进制 |
X | 字母大写的十六进制 |
f或F | float,6 |
e或E | 指数格式输出 |
g | float |
G | float |
a或A | 十六进制浮点 |
c | char |
s | 字符串 |
p | 指针 |
n | 读入/写出的个数 |
int main(int argc,char const *argv[]){
int num;
printf("%dty%n\n",12345,&num);//输出ty12345
printf("%d\n",num);//输出7
}
输入
scanf:%[flag]type
flag | 含义 |
---|---|
* | 跳过 |
数字 | 最大字符数 |
hh | char |
h | short |
l | long,double |
ll | long,long |
L | long double |
int main(int argc,char const *argv[]){
int num;
scanf("*%d%d",&num);//先跳过一个%d再读一个%d交给num
printf("%d\n",num);//输入123 345情况下,输出345
}
type | 用于 |
---|---|
d | int |
i | 整数,可能为16进制或8进制 |
u | unsigned int |
o | 八进制 |
x | 十六进制 |
a,e,f,g | float |
c | char |
s | 字符串(单词) |
[…] | 所允许的字符 |
p | 指针 |
scanf("%[^],%[^],[^]",a,b,c)//到逗号前所有东一读
printf与scanf函数其实是会有返回值的
文件输入输出
- 用>和<做重定向,小于号指定用一个文件来做它的输入,用大于号指定把他的输出写到另外一个文件去
FILE
- FILEfopen(const charrestrict path,const char *restrict mode);
- int fclose(FILE*stream);
- fscanf(FILE*,…)
- fprint(FILE*,…)
//打开文件的标准代码
FILE*fp=fopen("filetest","r");
if(fp){//文件是否存在
int num;
fscanf(fp,"%d",&num);//读那个文件
printf("%d\n",num);//读到后输出
fclose(fp);
}else{
printf("无法打开文件\n");
}
}
fopen
r | 打开只读 |
w | 打开只写,若不存在则新建,若存在则清空 |
w+ | 打开读写,若不存在则新建,若存在则清空 |
a | 打开追加,若不存在则新建,存在则从文件尾开始 |
…x | 只新建,若文件已存在,则不能打开 |
二进制文件
二进制读写
- size_t fread(void restrict ptr,size_t size,size_t nitems,FILErestrict stream);
- size_t fwrite(const void restrict ptr,size_t size,size_t nitems,FILErestrict stream);
- 参数:第一个参数:指针,要读或写的那块内存;第二个参数:这块内存有多大;第三个参数:有几个这样的内存
- 注意FILE文件指针是最后一个参数
- 返回的是成功读写的字节数
- 二进制文件的读写一般都是通过对一个结构变量的操作来进行的
- 故nitems是用来说明这次读写几个结构变量,而size是一个结构的大小
- sprintf向一个字符串输出,sprintf(format,"%%ds",STR)//%19s
位运算
按位运算
- c有这些按位运算的运算符:
- & 按位的与:按位全1为1,有两种常用应用
- 让某位或某些位为0:x&0xFE
- 取一个数中的一段 :x&0xFF
- 按位或|:全一为1,l。两种应用:
- 使得一位或几个位为1:x|0x01
- 把两个数拼起来:0x00FF|0xFF00
- ~ 按位取反:1变0,0变1,应用:
- 想得到全部位为1的数:~0
- 按位异或^:两位相等为0,不等为1
- 同一个变量用同一个值异或两次等于什么也没做
- 可以用来加密(比较弱的方式)
- 可以用来判断相等
- << 左移:i<<j,i当中所有位向左移动j个位置,右边填入0
- x<<=n(一个数左移n位)等价于 x*=2n
- >> 右移:i>>j,i当中所有位向右移动j个位置,
- 小于int的类型,移位以int方式做,结果是int。
- 对于unsigned的类型, 左边填入0.
- 对于signed的类型,左边填入原来的最高位(保持符号不变)
- x>>=n等价于x/=2n
- 移位的位数不要用负数,这是没有定义的行为。如x<<-2是错的
- 1u代表数值1类型为unsigned的数
位段
把一个int的若干位组合成一个结构
struct{
unsigned int leading:3;//这个成员占三个比特
unsigned int FLAG1:1;
unsigned int FLAG2:1;
int trailing:11;
};
- 定义位段之后,可以直接用位段的成员名称来访问
- 比移位、与、或、还方便
- 编译器会安排其中的位的排列,不具有可移植性
- 当所需的位超过一个int时会采用多个int
- 位段有16位和32位