文章上半部分是一些好看或高效代码的记录,下半部分是C prime plus的一些读书记录吧(好像烂尾了,后面的暂时都用不上,懒得学qaq,大佬轻喷)
给定n个2 * 1的矩形,若想将其拼成2 * n的矩形,共有几种拼法?
#include <stdio.h>
#define repp(i,x,y) for (int i=x;i<=y;++i)
long long f[2333]={0,1,2};
int x,n;
int main()
{
scanf("%d",&n);
repp(i,3,n) f[i]=f[i-1] +f[i-2];
printf("%lld",f[n]);
return 0;
}
输入的第一行表示一共有多少个测例。以下的每一行表示每一个测例,表示输入的数,每个数由空格隔开(这些数存在重复的),每一行以\n
结束输出为每个测例里面第二大的数,以\n
结束
#include <stdio.h>
int st[10000],tp,n;
int main()
{
scanf("%d",&n);
while (n--)
{
char ch=getchar();
while (!('0'<=ch&&ch<='9')) ch=getchar();
int tmp=0;
while (1)
{
while ('0'<=ch&&ch<='9') tmp=tmp*10+ch-'0',ch=getchar();
st[++tp]=tmp;tmp=0;
if (ch=='\n') break;
while (!('0'<=ch&&ch<='9')) ch=getchar();
}
int mx=0,pos;
for (int i=1;i<=tp;++i)// mx=max(mx,st[i]);
if (st[i]>mx) mx=st[i],pos=i;
st[pos]=0;
mx=0;
for (int i=1;i<=tp;++i)// mx=max(mx,st[i]);
if (st[i]>mx) mx=st[i],pos=i;
printf("%d\n",mx);
}
return 0 ;
}
第一行输入数字n(n<=50),表示有n组测试用例,第2到第n+1行每行输入数m(m为整数),统计并输出m用二进制表示时,1的个数。
例如:m=9时,二进制表示为1001,则输出2.
#include <stdio.h>
int n,x,cnt;
int main()
{
scanf("%d",&n);
while (n--)
{
scanf("%d",&x);
for (cnt=0;x;cnt+=x&1,x>>=1);
printf("%d\n",cnt);
}
return 0 ;
}
某天,小明来到一座大山前,这座大山有一道天梯从山底通往山顶。小明一时兴起,便想爬上这个天梯。他一次可以走1级台阶,也可以走2级,…,也可以直接走n级。那么,小明爬到第n级的台阶总共有多少种走法(先后次序不同算不同的结果)。
#include <stdio.h>
int climb(int m)
{
if (m == 1)
return 1;
int z=1;
for (int i = 1; i < m; i++)
z += climb(m - i);
return z;
}
int main()
{
int n;
scanf("%d", &n);
printf("%d", climb(n));
}
小明今天去买了一副新的扑克牌,发现里面有两个大王和两个小王(一副牌有54张)。他迫不及待想玩玩,于是从中抽取5张牌,看看能不能抽到顺子。“红桃A,黑桃3,大王,小王,方片5”,"哎~“,不是顺子…小明有些不高兴,他想了一下,决定将A看作1,J看作11,Q看作12,K看作13,且大\小王可以看成任何数字。这样处理后,上面的5张牌就成为了“1,2,3,4,5”(大小王分别看作2和4)。“得嘞”,小明非常高兴,便去找小刘,告诉小刘他新想的这个规则,并想和小刘比一比看谁运气比较好。小刘热爱编程,他觉得人工判断是否是顺子太麻烦了,于是他准备写一个程序,输入5张牌,然后自动判断这5张牌可不可以组成顺子,如果能组成顺子,就输出“True”,否则,就输出“False”。现在,请聪明的你为小刘实现这个程序,为了方便起见,你可以认为大小王是0。
输入5个数字,表示五张牌。有题目可知,数字的范围为[0,13]。
输出“True”或者"False",表示5个数字是否可以组成顺子。
#include <stdio.h>
#define rep(i,x) for (int i=1;i<=x;++i)
int cnt[2330];
int x;
int main()
{
rep(i,5) scanf("%d",&x),cnt[x]++;
for (int i=1;i<=13;++i)
{
int tmp=0;
for (int j=i;j<=i+4;++j)
{
if (cnt[j]==0) ++tmp;
}
if (tmp==cnt[0])
{
printf("True");
return 0;
}
}
printf("False");
return 0;
}
给定一个非空整数集合,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
输入第一行是一个整数n,表示集合的大小,第二行的n个正整数表示这个集合
输出只出现一次的数字
#include <stdio.h>
int main()
{
int n,k,a;
scanf("%d%d", &n,&k);
for (int i=1;i<n;++i) scanf("%d",&a),k^=a;
printf("%d\n",k);
}
输入一个数,把每个位置上的数字按要求变成0或1。如果当前位置上的数是偶数,就把它变为0,如果当前位置上的数是奇数,就把它变为1。请输出最后得到的数字。
#include <stdio.h>
int main()
{
char ch;
int w = 0;
while ((ch = getchar()) != -1)
{
if(ch=='\n') continue;
int k = ch - '0';
if (k % 2 == 0 && w == 1)
{
k = 0;
printf("%d", k);
}
else if (k % 2 == 1)
{
k = 1;
w = 1;
printf("%d", k);
}
}
if (w == 0)
printf("0");
}
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数,比如7,1221,12321都是回文数,而1234不是回文数。 现在给出一些数,请你判断它们是不是回文数。
输入 第一行是一个整数 n,代表数字的个数,0 < n <= 100 第二行是n个整数,是需要你判断的数,每个数的范围是[0, 100000000]
输出 n 行,每一行输出一个"Yes"或"No",代表对应的数字是不是回文数
#include <stdio.h>
#include <string.h>
#define LL long long
const int N = 2e5+233;
#define rep(i,n) for (int i=1;i<=n;++i)
int n,x;
int st[20000],tp;
int ans=0;
char s[10000];
signed main()
{
scanf("%d",&n);
rep(i,n)
{
scanf("%s",s+1);
int xx=strlen(s+1),flag=1;
rep(i,xx/2) if (s[i]!=s[xx+1-i]) flag=0;
printf("%s\n",flag?"Yes":"No");
}
return 0;
}
LOG是一个学生,可能是他名字的缘故,他从小就喜欢数学。今天他遇到一个数学问题:对于一个正整数A,LOG计算了A的A次方,但他不知道他算的对不对,所以他问你最高位是多少,通过最高一位来检验自己的结果
#include <cstdio>
#include <cmath>
#include <iostream>
#include <limits>
#define LL long long
#define db double
using namespace std;
const int INF =numeric_limits<int >::max();
void read(int &x)
{
x=0;
char ch=getchar();
int f=1;
while (!isdigit(ch)) (ch=='-'?f=-1:0),ch=getchar();
while ( isdigit(ch)) x=x*10+ch-'0',ch=getchar();
x*=f;
}
void write(int x)
{
if (x<0) putchar('-'),x=-x;
if (x>9) write(x/10);
putchar(x%10+'0');
}
int n;
signed main()
{
read(n) ;
db d=n*log10(n);
LL tt=d;
d=pow(10,d-tt);
printf("%d",(int)d);
return 0;
}
邮局最近推出了一套特殊的纪念邮票,这套邮票共有N张,邮票面值各不相同,按编号顺序为1分,2分,......,N分。
小杭是个集邮爱好者,他很喜欢这套邮票,可惜现在他身上只有M分,并不够把全套都买下。他希望尽量买,最好刚好花光所有钱。作为一个集邮爱好者,小杭也不想买的邮票编号断断续续。所以小杭打算买面值a分至b分的b-a+1张连续的邮票,且总价值刚好为M分。
你的任务是求出所有符合要求的方案,以[a,b]的形式输出。
#include <stdio.h>
#define LL long long
void write(int x)
{
if (x<0) putchar('-'),x=-x;
if (x>9) write(x/10);
putchar(x%10+'0');
}
int n,m;
signed main()
{
scanf("%d%d",&n,&m);
for (int len=100000;len>=1;--len)
{
if (2*m%len) continue;
int A=len-1;
int B=2*m/len;
if ((A+B)&1) continue;
int r=A+B>>1;
int l=B-A>>1;
if (l<=0) continue;
printf("[%d,%d]\n",l,r);
}
return 0;
}
What is the sum of all the integers (0≤integer≤100) that are on the main diagonal or antidiagonal of a given square matrix?
In the first line is a number n
(1≤n≤10) representing the number of the test cases.
Each case contains n + 1
lines, with one line representing the size (1≤size≤105) of the square matrix, and the following n lines representing the square matrix.
#include <stdio.h>
int main()
{
int total;
scanf("%d", &total);
while (total--)
{
int n;
scanf("%d",&n);
long long sum=0;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
{
int x;
scanf("%d",&x);
if (i==j||i+j==n+1)sum+=x;
//if (i+j==n+1) B+=x;
}
printf("%lld\n",sum);
}
return 0;
}
c prime plus 重点
//:行注释是C99新增
C99,C11 外部标识符能识别31个字符,内部标识符63个
操作系统和C库经常使用以一个或两个下划线开头,这些标识符都是保留的,这意味着,虽然没有语法错误,但会导致名字冲突
提高程序可读性的方法
无法一眼看出意义的变量名写注释
变量名要使用有意义的命名
用空行分割概念上的多个部分
常用变量在函数开头定义
函数的标准定义:void buf(void)
编译器报错的位置比真实错误位置滞后一行
调试程序
在程序的关键点插入printf语句
使用调试器
关键字与保留标识符不同,使用关键字不当会被编译器视为语法错误,而使用保留标识符作为变量名不会报错
一字节8位,计算机的字长越长,其数据转移越快,允许访问的内存更多
对于一些算数运算(如:大数相减),浮点数损失的精度更多
因为在任何区间内有无数实数,因此浮点数通常只是实际值的近似值,如7.0可能会被储存为浮点值的6.999999
要显示各个进制数的前缀,必须分别使用%#o,%#x,%#X
C99标准加入:long long ;unsigned long long int ;unsigned long long
C90标准加入:unsigned long int ;unsigned long ;unsigned short int ;unsigned short
现在计算机上最常见的是 long long 64位,long 32位,short 16位, int 32或16位
不使用到负数时可以把long类型合理替换为unsigned int 加快运行速度
溢出行为是未定义的行为
打印unsigned int类型用%u,long类型用%ld
c假定浮点型常量为double类型,若赋值给float,由double运算后再截断而得,这样会减慢程序运行的速度.
可在末尾加f或F,将常量默认为float 同理,加l或L默认为long double
程序发生上溢时,会输出inf,表示无穷
把浮点数转换为整数,会向下取整
内部的系统化命名:如int类型以 i_ 开头
printf("enter your name");
printf("______\b\b\b\b\b\b");
#include<string.h>//提供strlen()函数原型
char name[40];//代表数组中有40个元素
scanf("%s",name);
char类型数组用来储存字符串时,数组末尾以\0结尾,这意味着数组容量要至少比字符串多1
我们不用亲自将空字符放入字符串末尾,scanf在读入时已经完成了这项工作
一般而言,scanf只会读取字符串中的一个单词,遇到空格制表符或换行符就不再读取输入
C语言还有其他的输入函数如fget()
"x"与’x’的不同之处:前者为派生类型且由两个字符组成,后者为基本类型
strlen()不会将空字符算入其中,sizeof只会给出数组大小,若为常量,sizeof会算入空字符而strlen不会
#define的末尾不需要加分号
大写字母表示常量名称能增加程序可读性或在常量名称前加c_或k_
C90标准新增了const关键字,注意:用const 类型符声明的是变量而非常量
%d,%i 有符号十进制整数 %e 浮点数,e计数法 %g 根据值的不同,自动选择%f或%e %p 指针 %u 无符号十进制整数 %%打印一个百分号 %s 字符串
不能在双括号括起来的字符串中间断行,否则会编译报错
char a;
scanf(" %c", &a);
printf("%c", a);
//上方scanf读取非空格输入的第一个字符,下方scanf读取第一个字符(包括空格符)
char a1;
scanf("%c",&a1);
printf("%c",a1);
printf中的*修饰符:若不想事先设定字段宽度,可以用*修饰符指代字段宽度,但还是要用一个参数告诉函数字段宽度应该是多少
scanf中的*修饰符:在%与转换字符之间插入*,会使得scanf跳过该输入项.
scanf函数返回成功读取的项数,当读取不到输入项或与要求输入不匹配时,scanf()返回0,当scanf检测到文件结尾时,返回EOF.(常用#define将其定义为-1)
printf("%9d %9d %9d",a,b,c);//在两个转换说明中间插入空白字符,能确保即使数据前一溢出了字段,下一个数字也不会紧跟输出
假设有如下输入行:12.333@ 0:如果其对应的转换说明是%d,那么会读取12,小数点会被留在输入中作为下一次输入的首字符
字符串常量是用双引号括起来的字符序列,如"fuck you"
limit.h与float.h没看
许多其他语言都会回避形如:a=b=c=1;的多重复值,但是c完全没问题.赋值顺序是从右到左
C90标准新增了一元运算符"+"
事实上计算机不能用浮点数除以整数,会先将整数转化为浮点数再运算.(将整数转化为浮点数只需要在数后加点即可)
C99规定使用趋零截断
sizeof返回size_t类型的值,C99新增了%zd转换说明,用于printf()显示size_t类型的值,size_t事实上是一个无符号整数类型,C的typedef机制可以为现有类型创造别名,如:typedef double real;
求模运算只能用于整数
只要a和b都是整数值,就可以通过a-(a/b)*b来求模
while(++x<18)//有效缩短程序
++,–运算的另一个优点是它生成的机器语言代码效率更高
next=(y+n++)*6;n++可理解为:先使用n再递增:++n则是先递增n,再使用
在C语言中编译器可以自行选择先对函数中的哪个参数求值,这样提高了编译器的效率,但也会对递增运算带来麻烦,如:(ans=num/2+5*(1+num++);此时无法保证编译器的执行顺序;y=n++ +n++;结果也是未定义的)
表达式:k=6+(c=7-1);看似奇怪却完全合法(虽然不推荐使用)
序列点问题:
while(n++<10);//因为表达式是while的循环测试调件,所以该表达式结束就是一个序列点
y=(4+x++)+(6+x++);(4+x++)//不是一个完整的表达式,所以无法保证x的递增马上发生
将12.5赋值给int,会发生截断,只要变量的格式与转换说明一致,就不会输出垃圾值
强制类型转换:(type)
变量名是函数私有的,即在函数中定义的变量名不会和别处的相同名称发生冲突.
for(int i=0;a[i]!='\0';i++)//当a[i]不是\0,字符串的遍历方法
while(scanf("%d",&sum)==1)
;//跳过整数输入,处理这种情况更好的办法是使用 continue;
不能用关系运算符比较字符串
尽量只用<和>比较浮点数,因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等.可使用fabs()函数(math.h头文件中)方便的比较浮点数.
double response=1.99999;
while(fabs(response-1.99998)<0.0001)
printf("correct");
C语言中,所有非零的值都视为真
C99新增了_Bool类型,如果把其他非零值赋给_Bool,该变量会被设置为1,且C99提供了<stdbool.h>头文件,让bool成为了_Bool的别名,而且定义了true,false
关系运算符优先级比算术运算符低,比赋值运算符高
for循环的灵活性
for(;;);//第三个表达式每次迭代后都会更新该表达式的值;可以省略一个或多个表达式,但分号不能省略
+=,*=等运算符优先级与赋值运算符=相同,即比+,*等优先级高,这类赋值运算符生成的机器代码更加高效
逗号运算符
最常用的地方在for循环中
有两个作用:其一保证了被分割的表达式从左到右求值,换言之,这是一个序列点,其二:
s=2,5;//整个逗号表达式的值是右侧表达式的值,而左侧的赋值表示将2赋值给s;
一般而言,当涉及初始化和更新变量时,用for循环比较合适;而在其他情况下用while循环更好
考虑到影响执行的速度,C编译器不会检查数组的下标是否正确,故数组越界会带来奇怪的问题
字符输入输出函数
getchar()不带任何参数,它从输入队列中返回下一个字符,putchar函数打印它的参数,如putchar(ch);由于这些函数只处理字符,通常更快更简洁
while((ch = getchar())!='\n');//C语言特有的编程风格
<ctype.h>头文件包含的函数:isalpha(),isalnum(),isblank(),isdigit(),islower(),isxdigit(),tolower(),toupper(),注重可移植性,能在非ASCII上运行
如果没有括号,else与离他最近的if匹配
&&,||都是序列点,且有短路作用
<iso646.h>头文件可使用and,or,not分别代替&&,||,!
有时可以通过把if的测试条件反转来避免使用continue
注意以下代码count的增加:
count=0,a=0;
while(count<10)
{
a++;
if(a==1)
continue;
count++;//a=1时不会执行
}
continue只能用于循环中,不能用于switch中
while(getchar()!='\n')
continue;//丢弃其他读入的字符
switch中的多重标签:
switch(ch)
{
case 'a':
case 'A': x++;
break;
}
switch程序虽然运行快一些,但是使用面窄,用处较少
其实,getchar()和putchar()都不是真正的函数,他们被定义为供预处理使用的宏
如果用一个字符结束输入,就无法在输入中使用这个字符,是否有更好的方法结束输入
回显用户输入的字符后立刻打印属于无缓冲输入,而大部分按下Enter键之前不会重复打印刚输入的字符属于缓冲输入,用户字符被储存在一个被称为缓冲区的临时储存区域
缓冲区能节约输入时间,也能给操作者修改的机会
缓冲分为完全缓冲和行缓冲
while((ch=getchar())!=EOF)//可用此式判断是否到文件结尾
如果读取的是键盘而不是文件怎么办?绝大部分系统有办法通过键盘模拟文件的结尾条件
int ch;
while((ch=getchar())!=EOF)
putchar(ch);
不能只输入EOF或者-1,会被按单个字符传入,在大多数UNIX,Linux系统中,在一行开始处按下Ctrl+D会传输文件结尾信号,一些系统把任意位置的Ctrl+Z解释成文件结尾信号
getchar()不会跳过换行符
检查scanf的返回值可以判断用户是否输入了错误的类型值
函数
注意:函数头后面有分号;函数要求在每个变量名前都声明类型
void dibs(int a,b,c );//无效的函数头
void dibs(int a,int b,int c);//有效的函数头
void dibs(int,int,int);//有效的函数头
形式参数是被调函数中的变量,实际参数是主调函数赋给被调函数的具体值
函数返回值不一定是变量的值,也可以是任意表达式的值
return ( n < m ) ? n : m ;
只在函数末尾使用一次return语句比较好,因为这样更方便理解函数的控制流
对于较小的函数,把函数定义放在第一次调用函数之前十分方便普遍
函数中的return表示的是返回调用它的函数!!!在递归,DFS中此概念尤为重要
递归
递归方案更简洁,但效率较循环低,因为每次递归都会创建一组变量,这会导致内存消耗更多;而且每次函数调用都会花费时间,故递归执行较慢
最简单的递归形式是尾递归
指针
ptr = &pooh ;//ptr与&pooh的区别是:ptr是可修改的左值,&pooh是常量,即还可以把ptr指向其他地址
val = *ptr ;//*为解引用运算符,能找出ptr指向的值,即将pooh的值赋给了val
声明指针:要创建指针,先要声明指针变量的类型,假设想把ptr声明为储存int类型变量的指针,就要使用下面的运算符
int * pi;//*号表示声明的变量是一个指针
char * pc;
*和指针之间的空格可有可无,通常在声明时使用空格,使用时不加空格
虽然指针用无符号整数来表示,但不要认为指针是整数类型,例如:两个指针不能相乘.所以指针实际上是一种新类型
void interchange(int * u, int * v)//传地址时,形参的变量定义一定要形如"* u"
{
int temp;
temp = *u;//u的值是&x,所以u指向x,即用*u表示x的值
*u = *v;
*v = temp;
}
int main()
{
int x = 5, y = 10;
interchange(&x, &y);//传递的是地址,因此将u,v声明为指针
}
指针更接近机器语言,使用指针的程序更快
数组
const int days[4]={1,1,1,3};//创建只读数组
当初始化列表中的值少于数组元素时,编译器会将剩下的元素都初始化为0,也就是说,如果不初始化数组,数组和未初始化的变量一样,储存的是垃圾值,但如果部分初始化了数组,剩余元素就会初始化为0
C99增加了一个新特性:指定初始化器
int arr[6]={0,0,0,0,212};//对于传统的初始化语法,必须初始化了最后一个元素之前的所有元素才能初始化他
int arr[6]={[5]=212};//把arr[5]初始化为212
int arr[6]={[3]=212,3,4};//后面这些值将会被用于初始化指定元素后面的元素
int arr[]={[3]=212,3,4};//如果未指定数组大小,编译器会把数组设置为刚好足够装得下初始化的值,此时为编号0~5
C语言不允许把一个数组作为单元赋给另一个数组,使用数组时要注意数组越界问题
在C99标准之前,声明数组时只能在方括号用整型常量表达式,sizeof表达式被视为整型常量,而const值不是
C99引入变长数组,允许以变量声明数组长度
short a[100]={0};
a==&a[0];//成立,数组名是该元素的内存地址
a+2 == &a[2];
*(a+2) == a[2];s
在C中,指针加一指的是增加一个储存单元,对数组而言,这意味着加一后的地址是下一个元素的地址,而不是下一个字节的地址,这就是为什么声明指针需要所指对象的类型的原因之一
//假设函数待处理的是名为"arr"的int类型数组,则相应的函数定
int sum(int * ar)
{
//一般传入两个形参,一个是数组地址,另一个是数组的长度
int a = sizeof ar;//a=8,此时ar代表数组首位的地址的大小,并不代表本身数组的大小
//还有一种方法是传递两个指针,前者指向首后者指向尾
}
C保证在给数组 分配空间时,指向数组后面第一个位置的还是有效的指针,这使得while循环的测试条件是有效的,使用这种越界指针的调用更为简洁
while (start < end)
{
total += *start ;
start++;
//也可以简化为一行代码
//total += *start++;
}
一元运算符*和++的优先级相同,但是结合律从右到左,所以start++先求值,然后才是*start,也就是说,指针start先递增后指向.如果使用(*start)++,则先使用start指向的值,再递增该值,而不是递增指针.
C语言中,ar[i]和*(ar+i)这两个表达式是等价的.
指针操作
指针与整数相加:整数会先和指针所指向的类型的大小相乘,然后把结果与初始地址相加(相减运算相同)
指针求差:通过计算求出两个元素之间的距离,差值单位与数组类型单位相同
注意:即使编译器保证指针指向数组后面一个位置是有效的,也不保证可以解引用这样的越界指针.
int *p;
*p =5;//千万不要解引用未初始化的指针
对形式参数使用const
int sum(const int ar[],int n)
以上代码中const告诉编译器,该函数不能修改ar指向的数组中的内容,否则会被编译器捕获错误,如果编写的函数不用修改数组,则最好使用const
int rate[2]={1,3};
const int locked[4]={0};
const int * p = rate ;
*p = 0;//不允许
*p[1] =5;//不允许
rate[0] = 2;//允许
p++ ;//允许
p = locked;//允许
p = &rate[1];//允许
//然而只能把非const的数据的地址赋值给普通指针
//最后,在创建指针的时候还可以使用const两次,该指针既不可以更改指向的地址,也不能改变指向的值
const int * const pc = rate ;
指向多维数组的指针
字符串和字符串函数
puts()函数也属于stdio.h的输入/输出函数,但是它只显示字符串,而且自动在显示的字符串末尾加上换行符.
从ANSI C起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为串联起来的字符串常量
char A[50]="qaq""so cute";//与以下代码等价
char A[50]="qaqso cute";
字符串常量属于静态储存类别,这说明如果在函数中使用字符串常量,该字符串只会被储存一次.
在指定字符串数组大小时,确保数组元素分数至少比字符串长度多1,所有未被使用的元素都被自动初始化为0(这里的0指的是char类型的空字符,不是数字字符0)
通常让编译器确定数组的大小很方便,省略初始化声明中的数组大小即可,还可以使用指针表示法初始化字符串
const char m [] ="FUCK FUCK";
const char * ptr = "Something New";
字符串的数组和指针形式
数组形式的声明(arl[])在计算机内存中的分配为一个内含29个元素的数组,编译器把数组名arl识别为该数组首元素的地址的别名(类似&arl[0]),在数组形式中,可以进行类似arl+1(arl为数组名称)的操作,标识数组的下一个元素,但不允许进行++arl的操作,递增运算符只能用于变量名前,不能修改常量.
指针形式创建字符串,会将字符串的地址储存在指针中,该变量最初指向该字符串的首字符,但他的值可以改变,因此可以对指针使用递增运算符.
字符字面量被视为const数据,这意味着不能用指针改变它所指向的数据,但是如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非这个数组是const修饰的
#define MSG "fuck"
#include<stdio.h>
int main()
{
char ar[] = MSG;
const char *pt =MSG;
}
由以上程序可以发现 ,pt和MSG地址一致,而ar[ ]与MSG不同
我们来看一下未使用const限定符的指针的指针的初始化
char *word="frame";
//是否能使用该指针修改这个字符串,如: word[1]='l';
//编译器可以允许这样做,但是这在C中是未定义的行为,且很可能导致内存错误
word[0]='l';
printf("frame");//也就是说,编译器可以用相同地址替换每一个"frame"实例,所以上述语句打印的实际上是"lrame"
因此,建议把指针初始化为字符串字面量时使用const限定符
总之,如果打算修改字符串,就不要用指针指向字符串字面量
字符串数组
创建字符串数组有两种方法:指向字符串的指针数组和char类型数组的数组
#include<stdio.h>
#define SLEN 40
#define LIM 5
int main()
{
const char *my[LIM] = {
"good","morning","stupid"
}
char you[LIM][SLEN] = {
"good","morning","asshole"
}
printf("%zd %zd",sizeof(my),sizeof(you));//my是一个内含3个指针的数组,在系统中一共占用24个字节
}//而you是一个内含3个数组的数组,每个数组含有40个char类型的值,共占用120字节
my中的指针指向初始化时所使用的字符串字面量的位置,这些字符串字面量被储存在静态内存汇总,而you中的数组则储存着字符串字面量的副本,所以每个字符串都被储存了两次,此外,为字符串数组分配内存大使用率较低,you中的每一个元素大小必须相同,而且必须是能储存最长字符串的大小.
但是同样,指针数组也有缺点,指针指向的字符串字面量不能更改.
字符串输入
char *name;
scanf("%s",name);//虽然可能会通过编译,但在读入name的时候,可能会擦掉程序中的数据
最简单的方法是:在声明时显式指明数组大小
读取字符串的函数
1.gets()
在读取字符串时,scanf()和%s只能读取一个单词,可是在程序中经常要读取一整行输入,而非一个单词.
gets()读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串,它经常和puts()函数配对使用,该函数用于显示字符串,并在末尾添加换行符
int main()
{
char words[100];
puts("you stupid");
gets(words);
puts(words);
}//但有些编译器会在输出时发出警告
问题出在gets()的唯一参数是words,他无法检查数组是否装得下输入行,如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),如果这些多余字符擦掉了程序的其他数据,会出现程序异常终止或分段错误(说明改程序试图访问未分配的内存),C11直接从标准中废除了gets()函数
2.fgets()
fgets()的第二个参数指明了读入字符的最大数量,如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止,fgets()不会丢弃读到的换行符,会把他储存在字符串中,fgets()的第三个参数指明了要读入的文件,如果从键盘读入,则以stdin为参数
因为fgets()函数把换行符放在字符串的末尾,通常与fputs()函数连用(若用puts,输出行后会出现一行空行),fputs若要显示在计算机显示器上,应使用stdout作为第二个参数.
fgets()函数返回指向char的指针,改函数返回的地址与传入的第一个参数相同
char words[STLEN];
while(fgets(words,STLEN,stdin)!=NULL&&words[0]!='\n')
fputs(words,stdout);
虽STLEN被设置为10,但是程序处理过长的输出完全没有问题
如何处理掉换行符
while(words[i]!='\n')
i++;
words[i]='\0';
3.gets_s()
gets_s()只从标准输入中读取数据,所以不需要第三个参数,其次,它会丢弃读到的换行符,假如输入超过了最长字符限制,gets_s()的处理十分复杂,这就是gets_s()被使用次数少的原因
4.s_gets()
与fgets()大同小异,但在字符串中出现空字符时,会丢弃该输入行的其他字符,如果字符串中出现换行符,就用空字符替换.
丢弃过长输入行中的余下字符的原因是:输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入,可能导致程序崩溃
自定义输出函数
通过while,putchar(),getchar(),可以自定义输入输出函数
void put1(const char * string)
{
while(*string !='\0')
putchar(*string++);
}
许多程序员会在while循环中加入以下测试信息
while(* string)//当string指向空字符时,*string的值是0,循环结束
字符串函数
1.strlen()
用于判断字符串长度
void fit(char *string, unsigned int size)
{
if(strlen(string)>size)
string[size]='\0';
}
//此时,用puts(string+size);可以打印出剩余的字符
2.strcat()
接受两个字符串作为参数,把第二个字符串的备份附加在第一个字符串的末尾,并把拼接后的字符串作为第一个字符串,第二个字符串不变.strcat()的类型是char*,(即指向char的指针),返回第一个参数,即拼接第二个字符串后的第一个字符串的地址
3.strncat()
strcat()无法检查第一个数组是否能容纳得下,会导致多出来的字符溢出到相邻储存单元的问题发生,(可以用strlen()事先查看),strncat的第三个参数指定了最大添加字符数.
4.strcmp()
该函数比较的是两个字符串的内容,而非地址.如果两个字符串一样,则返回0,否则返回非零值,可以用strcmp()比较储存在不同大小数组中的字符串
5.strncmp()
该函数在比较字符串时,可以比较到字符不同的地方,也可以只比较第三个参数指定的字符数
if(strncmp(list[i],"astro",5)==0)
6.strcpy()
把整个字符串拷贝至指定数组,请注意:strcpy()参数中第二个参数指向的字符串被拷贝至第一个参数中,需保证空间足够
几个小特性:函数返回值类型是char*;第一个参数不必指向数组的开始
ps = strcpy(copy+7,orig);
7.strncpy()
与strcpy()类似,但其第三个参数指明可拷贝的最大字符数,如果目标空间装不下数组,就把副本最后一个元素设置为空字符
结构和其他数据形式
struct book
{
char title[30];
char author[30];
float value;
};
int main()
{
struct book library;
s_gets(library.title,30);
}
结构变量也可以这样初始化
struct book library ={
"fuck",
"you",
1.95
};
使用结构运算符(.)可以访问结构中的成员 (.比&的优先级高)
嵌套结构
struct names{
char first[10];
char last[10];
};
struct guy {
struct names handle;
char favfood[10];
char job[10];
float income;
};
指向结构的指针
至少有4个理由解释为何要使用指向结构的指针
第一:指向结构的指针比结构更好操控,第二:在早期C的实现中,结构不能作为参数传递给函数,但可以传递结构的指针,第三:使用指针更有效率,第四:一些用于表示数据的结构中包含指向其他结构的指针