C语言复习

文章上半部分是一些好看或高效代码的记录,下半部分是C prime plus的一些读书记录吧(好像烂尾了,后面的暂时都用不上,懒得学qaq,大佬轻喷)


输出格式

如何输出长度不同的空格

解方程组

指针的解释

gets函数

读一行

stringsteam

给定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;
}

电脑的fn键的使用

stringstream用法

size_t的讲解

ss.fail()的用法

ss.str()的用法

memset用法

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

转义字符的运用

\b的运用

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的实现中,结构不能作为参数传递给函数,但可以传递结构的指针,第三:使用指针更有效率,第四:一些用于表示数据的结构中包含指向其他结构的指针

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值