大家好,我的这篇稿子纯属C语言的一些知识点,有兴趣的可以楷模一下。
1927年,C语言由丹尼斯 · 里奇 和 肯 · 汤普逊在开发UNIX设计了C语言。
优点:设计特性,高效性,可移植性,强大而灵活,面向程序员
缺点:C语言使用指针,而所涉及指针的错误旺旺难以察觉,优点比缺点多很多。
使用C语言七大步骤:1,定义程序目标
2,设计程序
3,编写代码
4,编译
5,运行程序
6,测试和调试程序
7,维护和修改程序
Mi推荐编写或练习C或C++时使用Dev-C++软件,应用宝官网均可下载。用法网上均可查见,简易而方便
编译器的任务就是把源代码(自己写的代码)翻译成等价的机器语言代码。
链接器就是把编译器翻译好的源代码以及库代码和启动代码组合起来,生成一个可执行程序。
C语言概述:
每个C程序必须包含一个main()函数,花括号作为有头有尾的结束,C语言的基本模块都叫做函数。
例子: 小技巧:程序输出如有一闪而过现象发生只需在调用getchar();函数可等待用户输入
#include <stdio.h>
void cc(void); //一个程序包含多个函数调用时在主方法里调用,输出位置对应调用位置。
int main(void) //程序一执行走main方法
{
float salary;
printf("\aEnter your desired monthly salary:");// \a发出警告
printf(" $_________\b\b\b\b\b\b\b\b");
scanf("%f", &salary);//获取用户输出(键盘的输入) &指针
printf("\n\t$%2.f a month is $%.2f a year.", salary,salary * 12.0);
cc();
return 0;
}
void cc(void) //函数体内容
{
printf("\rGee!\n");
}
在代码中/**/为注释多行,//为注释单行,注释作用易于代码的可读性。
数据类型:
整数没有小数部分,浮点数有小数部分。浮点数通常范围比整数大,浮点通常只是实际值的近似值
超过当前能表达的范围时,称为上溢。损失了原末尾有效位上的数字称为下溢。
转义序列
转义序列 | 含义 |
\a | 警报(ANSIC) |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | 反斜杠(\) |
\' | 单引号 |
\'' | 双引号 |
\? | 问号 |
\0oo | 八进制值 |
\0xhh | 十六进制值 |
常用数据类型和printf()中对应的转换:
常量 | 类型 | 转换说明(%转换字符) | 取值范围 | 类型 | 转换说明(%转换字符) |
指针 | %p | -32767-32767|0-32767 | short | unsigned short | %h|%hu | |
使用十六进制数0f|0F | 无符号十六进制整数 | %x|%X | -32767-32767 | int(占16位或32位) | %d |
浮点数,十六进制和p计数法 | %a|%A | 0-65535 | unsigned int | %ud | |
2.34E07 | e计数法浮点数(douuble) | %e|%E | -2147483647-2147483647 | long(32位) | %l |
sizeof | %zd | 0-4294967295 | unsigend long | %lu | |
6.0f | float|double | %f(表示6位有效数字) | / | long long(64位) | %ll |
字符串 | String | %s | unsigend 这个只表示正数/ | unsigend long long | %iiu |
44 | 044 | 八进制 | %o | %#o | 'c' | char(占16位或32位) | %c |
64 | 0x64 | 十六进制 | %x | %#x | renyichao | [%10.5s] | [ renyichao]这样输出代表有十个字符空间,小数点后面是5位,因为打印的是字符类型,所以没有小数点,正数靠右排齐,负数靠左排齐。如把10换位5,输出[renyichao],会自动扩充其所要输出空间。 |
类型的级别从高到底依次是: long double, double, float, unsigned long long, long long, unsigned long, long, unsigend int, int
#define 指令可以定义字符及字符串常量。sizeof 运算符,以字节为单位给出对象的大小。
#include <string.h> ---->包含strlen()函数 用于获取字符串的长度(末尾的空字符不计算在内) scanf()函数转换说明是%s可读取一个单词
scanf和printf的区别:
printf()函数把整数,浮点数,字符和字符串转换成字符串
scanf()函数把输入的字符串转成整数,浮点数,字符和字符串 添加转换中插入*会跳过这个转换符,用户输入会跳过前面的空白
scanf(); 读取基本变量的值,在变量前面加& scanf(); 把字符串读取字符数组中,不要使用&
如果用户输入三行三列字符或数字长度不一可以使用固定的字段宽度让输出整齐美观。示例:
#include <stdio.h>
#define cc4 3.141591
#define cc5 3.141592
#define cc6 3.141593
int main(void)
{
char cc1[20],cc2[20],cc3[20];
int cc7,cc8,cc9;
printf("请每行输入名字一行三个及年龄一行三个\n");
scanf("%s%s%s", cc1, cc2, cc3);//字符数组不使用&
scanf("%d %d %d ", &cc7, &cc8, &cc9);//scanf用户输入会跳过空白
printf("cc= %12s,%12s,%12s\n", cc1, cc2, cc3);//%12s可以固定为12的字段宽度
printf("cc= %12f,%12f,%12f\n", cc4, cc5, cc6);
printf("cc= %12d,%12d,%12d\n", cc7, cc8, cc9);
return 0;
}
输入结果:
请每行输入名字一行三个及年龄一行三个
ren renyi renyichao
1 22 222
ee
cc= ren, renyi, renyichao
cc= 3.141591, 3.141592, 3.141593
cc= 1, 22, 222
--------------------------------
Process exited after 25.77 seconds with return value 0
请按任意键继续. . .
编写一个程序,先提示用户输入名,然后提示用户输入性。在一行打印用户输入的名和姓,下一行分别打印和姓的字母数。字母数要与相应的名和姓结尾对齐。例子一枚。
#include <stdio.h>
#include <string.h>
int main(void)
{
char xing[10];//姓
char name[10];//名
printf("\n\tPlease limi xing plause:");
scanf("%s", xing);
printf("\n\tPlease limi name plause:");
scanf("%s", name);
printf("\n\t%s %s\n",xing,name);
printf("\t%*d %*d\n",strlen(xing),strlen(xing),strlen(name),strlen(name));
printf("\t%s %s\n",xing,name);
printf("\t%*d %*d\n",-strlen(xing),strlen(xing),-strlen(name),strlen(name));
return 0;
//碰见这种未知字符宽度的,可以先用 * 号先顶着,然后后面函数代入 *
//上面这两个printf就是例子,第一个是strlen计算完字符宽度然后传给 * 号
//第二个strlen计算完字符宽度然后传给 d 以十进制显示出来
}
输出结果:
Please limi xing plause:zhangxiaoyu
Please limi name plause:zhangxiaoyu
zhangxiaoyu zhangxiaoyu
11 11
zhangxiaoyu zhangxiaoyu
11 11
--------------------------------
Process exited after 17.67 seconds with return value 0
请按任意键继续. . .
前缀++和后缀++的区别:
int a = 2;
前缀形式:
q = 2*++a;
首先,a递增1; //2+1
然后,2乘以a,并将结果付给q //q = 2*2
后缀形式:
q = 2*a++;
首先,2乘以a,并将结果付给q//q = 2*1
然后,a递增1 //2+1
--同理;
简单理解为:
x = 2;
y = 3;
num = (y + x++)*6;
把y和n的值代入得:
num = (3 + 2)*6 = 30;
如果x++是表达式的一部分,可其视为“先使用n,在递增”;而++n则表示“先递增n,再使用”
哪些值为真://一般而言,所有非零值都视为真,只有0被视为假。
//哪些值为真
#include <stdio.h>
int main(void)
{
int n = 3;
while(n)
printf("%2d is true\n", n--);
printf("%2d is flase\n", n);
n = -3;
while(n)
printf("%2d is true\n", n++);
printf("%2d is flase\n", n);
return 0;
}
-------------------------------------------------------------
输出结果:
3 is true
2 is true
1 is true
0 is flase
-3 is true
-2 is true
-1 is true
0 is flase
--------------------------------
Process exited after 0.4692 seconds with return value 0
请按任意键继续. . .
= 与 == 的区别:
cc = 5 //把5附给cc
cc == 5 //检查cc是否为5
do -- while与while区别差异:
//do while用户输入之前不断提示输入数字:出口条件循环
#include <stdio.h>
int main(void)
{
const int RESULT = 13;
int num;
do{
printf("To enter the triskaidelaphobia thewapy club,\n");
printf("Please enter the secret code number:");
scanf("%d", &num);
}while(num != RESULT);
printf("Congratulations! You are aured!\n");
}
//while循环也可以写出 麻烦
#include <stdio.h>
int main(void)
{
const int RESULT = 13;
int num;
printf("To enter the triskaidelaphobia thewapy club,\n");
printf("Please enter the secret code number:");
scanf("%d", &num);
while(num != RESULT){
printf("To enter the triskaidelaphobia thewapy club,\n");
printf("Please enter the secret code number:");
scanf("%d", &num);
}
printf("Congratulations! You are aured!\n");
return 0;
}
for嵌套循环:
#include <stdio.h>//for嵌套循环
#define ROWS 6
#define CHARS 10
int main(void)
{
int row;
char ch;
for(row = 0; row<ROWS; row++){ //对应的6行
for(ch = 'A'; ch<('A'+CHARS); ch++) //对应每行的内容
printf("%c", ch);
printf("\n"); //内循环执行完换行
}
return 0;
}
//输出结果:
/*ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
--------------------------------
Process exited after 0.6074 seconds with return value 0
请按任意键继续. . .*/
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
#include <stdio.h>//for嵌套变式
int main(void)
{
const int ROWS = 6;
const int CHARS = 6;
int row;
char ch;
for(row = 0; row<ROWS; row++){ //对应的6行
for(ch = ('A'+row); ch<('A'+CHARS); ch++) //对应每行的内容
printf("%c", ch);
printf("\n"); //内循环执行完换行
}
return 0;
}
//输出结果:
/*
ABCDEF
BCDEF
CDEF
DEF
EF
F
--------------------------------
Process exited after 0.465 seconds with return value 0
请按任意键继续. . .
*/
while和for小结:
典型的while伪代码如下:
获取初值
while(值满足测试条件)
{
处理该值
获取下一个值
}
for循环也可以完成相同的任务
for(获取初值; 值满足测试条件; 获取下一个值)
处理该值
数组
1,省略数组初始化大小,编译器会自动计算数组的大小。
2,字符数组char car[10] = "Tata";
car == &car[0] *car = 'T' *(car + 1) == car[1] == 'a'.
3,数组形式的字符串末尾都要多加上一个 '\n' ,当把程序载入内存时,也载入了程序中的字符串。
字符串储存在静态存储区。
指针
1,指针的值是它所指对象的地址。地址依赖于计算机内部硬件,许多计算机都是 按字节编制,意思是内存中每个地址都按顺序编号,一个较大对象地址(double类型的变量)通常是该对象第一字节的地址。
2,在指针面前使用 * 运算符可以得到该指针所指对象的值。
3,指针加1,指针的值递增它所指类型的大小。
4,只有程序需要在函数中修改数值时,才会传递指针。
5,const放在*左侧的任意位置,限定指针指向的数据不能改变:const放在*右侧,限定指针本身不能改变。
数组和指针的区别
它们指向字符串有何区别: char array[] = "I love zhangxiaoyu!";
const char *head = "l love My!";
数组名array是常量,而指针head是变量,区别如下:
首先:两者都可以用数组表示法:
for(i = 0; i < 6; i++)
putchar(array[i]);
putchar('\n');
for(i = 0; i < 6; i++)
putchar(head[i]);
putchar('\n');
上面两段代码输出是:
l love
l lone
其次,两者都能进行指针加法操作:
for(i = 0; i < 6; i++)
putchar(*(array+ i));
putchar('\n');
for(i = 0; i < 6; i++)
putchar(*(head + i));
putchar('\n');
输出是:
l love
l lone
但是只有指针表示法可以进行递增操作:
while (*(head) != '\0') //在字符串末尾处停止
putchar(*(head++)); //打印字符,指针指向下一位置
这段代码输出如下:
l love My!
head = array; //head现在指向数组arra
赋值运算符左侧必须是变量。
指针初始化字符串字面量时建议使用const,这样不能修改数据,如若想修改数据,不加即可。
int sum(int * ar, int n)//第一个形参告诉函数该数组的地址和数据类型,第二个形参告诉元素的个数
{
int i;
int total;
for(i = 0; i < n; i++) //使用n个元素
total += ar[i]; //+=运算符把右侧运算对象加到左侧运算对象上。因此total是当前数组元素之和
//ar[i] 和 *(ar + i)相同
return total;
}
//上面函数代码块和下面的函数定义等价
int sum(int ar[], int n)
{
//其他代码已省略
}
指针运算中的优先级:
#include <stdio.h>
int data[2] = {100, 200};
int moredata[2] = {300, 400};
int main(void)
{
int * p1, * p2, * p3;
p1 = p2 = data;
p3 = moredata;
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1, *p2, *p3);
printf(" *p1++ = %d, *++p2 = %d, (*p3)++ = %d\n",*p1++, *++p2, (*p3)++);
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1, *p2, *p3);
//只有 (*p3)++ 改变了数组元素的值 其他两个操作分别把p1和p2指向数组的下一元素
}
/*
先指向 *p1 = 100, 先自增 *p2 = 100, 改变了 *p3 = 300
后自增 *p1++ = 100, 后指向 *++p2 = 200, 数组元 (*p3)++ = 300
*p1 = 200, *p2 = 200, 素的值 *p3 = 301
--------------------------------
Process exited after 0.6417 seconds with return value 33
请按任意键继续. . .
*/
一般而言:如果编写的函数需要修改数组,在声明数组形参时不要使用const;
如果编写的函数不用修改数组,那么在声明数组形参时最好使用const。
并且一旦声明数组或指针以及字符串的值不可以修改
一般而言:声明一个指向N维数组的指针时,智能省略最左边方括号中的值;
如若不省略编译器也会忽略。
int sum4d(int ar[][12][20][30], int rows);
//因为第一对方括号只用于表明这是一个指针,而其他方括号则用于描述指针所指向数据对象类型,与下面声明等价:ar[] = (*ar)
int sum4d(int (*ar)[12][20][30], int rows);
这里,ar指向一个12 * 20 * 30的int数组。
//使用变长数组的函数 传形参先传大小(行,列),在传数组
#include <stdio.h>
#define ROWS 3
#define COLS 4
int sum2d(int rows, int cols, int ar[rows][cols]);
int main()
{
int i, j;
int rs = 3;
int cs = 10;
int junk[ROWS][COLS] = {{2,4,6,8},{3,5,7,9},{12,10,8,6}};
int morejunk[ROWS - 1][COLS + 2] = {{20, 30, 40, 50, 60, 70},{5, 6, 7, 8, 9, 10}};
int varr[rs][cs]; //变长数组
for(i = 0; i < rs; i++)
for(j = 0; j < cs; j++)
varr[i][j] = i * j + j;
printf("3 * 5 array\n");
printf("Sum of all elements = %d\n", sum2d(ROWS, COLS, junk));
printf("2 * 6 array\n");
printf("Sum of all elements = %d\n", sum2d(ROWS - 1, COLS + 2, morejunk));
printf("3 * 10 VLA\n");
printf("Sum of all elements = %d\n", sum2d(rs, cs, varr));
return 0;
}
int sum2d(int rows, int cols, int ar[rows][cols])
{
int r, c;
int tot = 0;
for(r = 0; r < rows; r++)
for(c = 0; c < cols; c++)
tot += ar[r][c];
return tot;
}
/*
3 * 5 array
Sum of all elements = 80
2 * 6 array
Sum of all elements = 315
3 * 10 VLA
Sum of all elements = 270
--------------------------------
Process exited after 0.5445 seconds with return value 0
请按任意键继续. . .
*/
//复合字面量 --有趣的常量
#include <stdio.h>
#define COLS 4
int sum2d(const int ar[][COLS], int rows);
int sum(const int ar[], int n);
int main(void)
{
int total1, total2, total3;
int *pt1;
int (*pt2)[COLS];
pt1 = (int[2]) {10, 20};
pt2 = (int[2][COLS]) {{1, 2, 3, -9},{4, 5, 6, -8}};
total1 = sum(pt1, 2);
total2 = sum2d(pt2, 2);
total3 = sum((int []){4, 4, 4, 5, 5, 5}, 6); //复合字面量
printf("total1 = %d\n", total1);
printf("total2 = %d\n", total2);
printf("total3 = %d\n", total3);
return 0;
}
int sum(const int ar[], int n)
{
int i;
int total = 0;
for(i = 0; i < n; i++)
total += ar[i];
return total;
}
int sum2d(const int ar[][COLS], int rows)
{
int r, c;
int tot = 0;
for(r = 0; r < rows; r++)
for(c = 0; c < COLS; c++)
tot += ar[r][c];
return tot;
}
/*
total1 = 30
total2 = 4
total3 = 27
--------------------------------
Process exited after 0.4854 seconds with return value 0
请按任意键继续. . .
*/
字符串
1,puts();函数也属于stdio.h输入输出函数,但是pust();只显示字符串。gets();读取输入,遇到换行丢弃,并在末尾添加换行符。
2,用双引号括起来的内容称为字符串字面量,也叫字符串常量,自动在末尾加 \0 字符,作为字符串存储在内存。
3,如果在字符串内部使用双引号,必须在双引号前面加上一个反斜杠 (\) ;
4,fgets()和(fputs()):fgets()第二个参数用来限制读入的字符数来解决溢出的问题,该函数用于专门设计处理文件输出
5,fgets()和 gets()的区别:0,fgets()每次读入一行
1,fgets()第二个参数知名读入字符的最大数量。
2,如果遇到换行符 会把它存储在字符串中, 与gets不同,gets会丢弃换行符。
3,第三个参数表明要读入的文件,如果读入从键盘输入的数据,则以stdin作为参数,该标识符一定在stdio.h
6,fputs第二个参数知名他要写入的文件,如果要显示在计算机显示器上,应使用stdout
7,空字符是整数类型,空指针式指针类型,他们都可以用0来表示,空字符是一个字符,占1字节:而空指针是一个地址,通常占4字节。
8,gets_s() 与 fgets()类似,用于一个参数限定读入的字符数,gets_s() 与 fgets()的区别如下:
1,gets_s()只从标准输入中读取数据,所以不需要第3个参数。
2,如果gets_s()读到换行符,会丢弃他而不是存储它。
9,比较一下gets() , fgets()和gets_s()的适用性。如果目标存储区装得下输入行,三个函数都没问题,但fgets_s()会保留输入末尾的换行符作为字符串的一部分,要编写额外的代码将其替换成空字符。
10,s_gets()函数
//s_gets()函数
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if(ret_val)//即,retval != NULL
{
while(st[i] != '\n' && st[i] != '\0')
i++;
if(st[i] == '\n')
st[i] == '\0';
else
while(getchar() != '\n')
continue;
}
return ret_val;
}
//如果fgets()返回NULL,说明读到文件结尾或出现读取错误,s_gets()跳过这个过程。
//如果字符串中出现换行符,就用空字符替换它,如果字符串中出现空字符,就丢弃该输入的其余字符,节省内存,效率自然也就高
//缺点,遇到不适合的输入时毫无反应,它丢弃多余的字符时,即不告知程序,也不告知用户。
11,pus()函数一些用法:
//puts()的一些用法
#include <stdio.h>
#define DEF "I am renyichao."
int main(void)
{
//用双引号括起来的是常量,且被视为该字符串的地址,存储字符串的数组名也被看做是地址,
char str1[80] = "An array was initialized to me.";
const char *str2 = "A pointer was initialized to me.";
puts("I'am an argument to puts().");
puts(DEF);
puts(str1);
puts(str2);
puts(&str1[5]);//str1数组第六个元素r
puts(str2 + 4);//第四个元素后面i
return 0;
}
/*
I'am an argument to puts().
I am renyichao.
An array was initialized to me.
A pointer was initialized to me. 每个字符单独占一行,因为puts()会在显示字符串时自动在其末尾添加一个换行符。
ray was initialized to me.
inter was initialized to me.
*/
12,fputs()函数: 与puts区别如下:1,,fputs()函数的第二个参数要指明写入的数据文件,+6;
2,与puts()不同,fputs不会在末尾添加换行符。
相同点:他们只显示一行输出
注意:gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets保留输入中的换行符,fputs()不在输入中添加换行符
还要注意:puts()与gets()配对使用,fputs()与fgets()配对使用。
//puts()的一些用法
#include <stdio.h>
#define DEF "I am renyichao."
int main(void)
{
//用双引号括起来的是常量,且被视为该字符串的地址,存储字符串的数组名也被看做是地址,
char str1[80] = "An array was initialized to me.";
const char *str2 = "A pointer was initialized to me.";
puts("I'am an argument to puts().");
puts(DEF);
puts(str1);
puts(str2);
puts(&str1[5]);//str1数组第六个元素r
puts(str2 + 4);//第四个元素后面i
return 0;
}
/*
I'am an argument to puts().
I am renyichao.
An array was initialized to me.
A pointer was initialized to me. 每个字符单独占一行,因为puts()会在显示字符串时自动在其末尾添加一个换行符。
ray was initialized to me.
inter was initialized to me.
*/
13,printf();函数与puts()函数相同的是:把字符串的地址作为参数。
不相同的是:printf不会自动在末尾加上一个换行符,必须指定在哪里使用换行符,还可以格式化不同的数据类型
14,strcat()函数(用于拼接字符串)函数接受两个字符串作为参数。strncat()函数第3参数指定最大添加字符数。
//strcat()和strncat()拼接两个字符串
#include <stdio.h>
#include <string.h> //strcat()函数原型在该文件中
#define SIZE 30
#define CC 13
char * s_gets(char *st, int n);
int main(void)
{
char flower[SIZE];
char addon[] = "s smell like old shoes";
char bug[CC];
int acailable;
puts("What is your favorite flower?");
s_gets(flower, SIZE);
if((strlen(addon) + strlen(flower) + 1) <= SIZE)
strcat(flower, addon);
puts(flower);
puts("What is your favorite bug?");
s_gets(bug, CC);
acailable = CC - strlen(bug) - 1;
strncat(bug, addon, acailable);
puts(bug);
puts("bye");
return 0;
}
char * s_gets(char *st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin); //1,读入的数据 2,读入字符的最大数量(本例80) 3,读取从键盘输入的数据
if(ret_val)
{
while(st[i] != '\n' && st[i] != '\0')
i++;
if(st[i] == '\n')
st[i] == '\0';
else
while(getchar() != '\n')
continue;
}
return ret_val;
}
15,strcmp()用于比较字符串,而不是字符,如比较的第1个字符串在第2个字符串的前面,返回负数,反之返回整数,相等返回0.
strncmp()用于比较字符中的字符:
//使用strncmp()比较字符串中的前5个字符
#include <stdio.h>
#include <string.h>
#define SIZE 6
int main(void)
{
const char *list[SIZE] = {"renyiddefes", "dadawfefe", "renyiceses", "chaofdiedfe", "renyichaofesfe", "siugesuioefgs"};
int count = 0;
int i;
for(i = 0; i < SIZE; i++)
if(strncmp(list[i], "renyi", 5) == 0)
{
printf("Found: %s\n", list[i]);
count++;
}
printf("count = %d\n", count);
return 0;
}
/*
Found: renyiddefes
Found: renyiceses
Found: renyichaofesfe
count = 3
*/
16,strcpy()相当于字符串赋值运算符:第二个参数(指针,数组名,字符串常量)的字符串拷贝到第一个参数(数据对象)的数组中。
属性:第一,返回值类型是char *,返回第一个参数的值,即一个字符的地址。第二,第一个参数不必指向数组的开始。
char cc[20]; //一定要初始化数组
int x;
x = 50; //数组赋值
strcpy(cc, "Hi ho"); //字符串赋值
cc = "So long"; //语法错误
strncpy()函数第三个参数可拷贝最大字符串。
17,sprintf()函数把多个元素组合成一个字符串,并存储在数组而不显示到屏幕上。
//使用sprintf()
#include <stdio.h>
#include <string.h> //sprintf()函数原型在该文件中
#define MAX 20
char * s_gets(char *st, int n);
int main(void)
{
char first[MAX];
char last[MAX];
char cc[2 * MAX + 10];
double num;
printf("Enter your first name:");
s_gets(first, MAX);
printf("Enter your last name:");
s_gets(last, MAX);
printf("Enter your num monry:");
scanf("%lf", &num);
sprintf(cc, "%s, %19s: $%6.2f\n", last, first, num);
puts(cc);
return 0;
}
char * s_gets(char *st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin); //1,读入的数据 2,读入字符的最大数量(本例80) 3,读取从键盘输入的数据
if(ret_val)
{
while(st[i] != '\n' && st[i] != '\0')
i++;
if(st[i] == '\n')
st[i] == '\0';
else
while(getchar() != '\n')
continue;
}
return ret_val;
}
18,strchr()函数:
char *strchr(const char * s, int c);
//简单用法
char line[80];
char * find;
fgets(line, 80, stdin);
find = strchar(line, '\n'); //查找换行符
if(find) //如果没找到换行符,返回NULL
*find = '\0'; //把该处的字符替换为空字符
//修改字符串
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define SIZE 81
void ToUpper(char *);
int PunctCount(const char *);
int main(void)
{
char line[SIZE];
char * find;
puts("Please enter a line:");
fgets(line, SIZE, stdin);
find = strchr(line, '\n'); //查找换行符
if(find) //如果没找到换行符,返回NULL
*find = '\0'; //把该处的字符替换为空字符
ToUpper(line);
puts(line);
printf("PunctCount = %d.\n", PunctCount(line));
return 0;
}
void ToUpper(char *str)
{
while(*str)
{
*str = toupper(*str);//把整个字符串转换成大写
str++;
}
}
int PunctCount(const char *str)
{
int ct = 0;
while(*str)
{
if(ispunct(*str))//用于统计字符串标点符号个数
ct++;
str++;
}
return ct;
}
/*
Please enter a line:
dawdaw , dwada w ? dawda! daefde# fesf&
DAWDAW , DWADA W ? DAWDA! DAEFDE# FESF&
PunctCount = 5.
*/
19,atoi() atol()和atof()函数把字符串形式分别转换为int,long,double类型的数字。
strtol() strtoul()和strtod()函数把字符串形式的数字分别转换为long,unsigned long,double。
存储类别 链接和内存管理
1,C对象4种存储期:静态存储期:如果有对象具有静态存储期,那么它在程序的执行期间一直存在,文件作用域具有静态存储期。
(外部变量)
线程存储期:从声明到线程结束一直存在,以关键字_Thread_local声明
自动存储期:块作用域具有自动存储期,所有自动存储的变量
动态分配内存期:malloc()和free()
2, 5种存储类别
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
自动 | 自动 | 块 | 无 | 块内 |
寄存器(CPU) | 自动 | 块 | 无 | 快内,使用关键字register |
静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 所有函数外,使用关键字static |
静态无链接 | 静态 | 块 | 无 | 块内,使用关键字static |
3,C六个关键字作为是存储类别说明符:
suto()说明符,表示变量自动存储期,只能作用于块作用域的变量声明中。
register()说明符也只作用于块作用域的变量,以寄存器(CPU)存储类别最快速度访问该变量,还保护该变量地址不被获取。
static()说明符创建对象具有静态存储区,载入程序创建对象随着程序的结束而结束,作用文件作用域声明,作用域受限于该文件。
extern()说明符表示声明的变量定义的别处,
_Thread_local()
typedef()
4,小结:存储类别
自动变量具有块作用,无链接,自动存储期,它们是局部变量。具有静态存储期的变量可以具有外部链接,内部链接,无链接。
5,分配内存:
//动态分配内存malloc()分配足够的内存来存储数据 free()释放内存空间
...
int main()
{
double cc[2000];
int i;
...
for(i = 0; i < 1000; i++)
gobble(cc, 2000);
}
void gobble(double ar[], int n)
{
double * temp = (double *) mallor(n * sizeof(double));//假设double为8字节,每次循环分配1600字节
.../* free(temp); //假设忘记使用free() */ //那么每次循环浪费1600字节,循环执行1000次,循环结束有1600万字节被占用。
}
calloc()函数也可以分配内存
long * newmen;
newmen = (long *)calloc(100, sizeof(long)); //long4字节 分配400字节的内存。
volatile类型限定符:限定的数据除了被当前的程序修改外还可以被其他进程修改,目的警告编译器不要进行假定的优化。
可以同时使用const和volatile限定一个值。例如通常const把硬件时钟设置为不能更改的变量,但是可以通过代理改变,这时使用volatile。只能在声明使用这两个限定符,顺序不重要,如下所示:代理(而不是变量所在的程序)可以改变该变量的值
一般用于硬件地址以及在其他程序或同时运行的线程中共享数据。
volatile const int loc;
const volatile int * pac;
resterict类型限定符:是为了编译器设置优化方案,resterict限定的指针是访问它所指向数据的唯一方式。
允许编译器优化某部分代码已更好的支持计算,只用于指针,表明访问数据对象的唯一且初始方式。
总而言之,编译器不会检查用户是否遵循这一限制,但是无视它后果自负。
复习题:1,自动存储类别:寄存器存储类别:静态,无链接存储类别可以成为它所在函数的成员变量。
2,静态,无链接存储类别;静态,内部链接存储类别;静态,外部链接存储类别在它所在程序的运行期一直存在。
3,静态,外部链接存储类别可以被多个文件使用。静态,内部链接存储类别只能在一个文件中使用。
4,块作用域变量具有无链接属性。
5,关键字extern用于声明中,表明该变量或函数已在定义在别处。
文件输入输出
标准I/O: 使用标准I/O的第一步是调用fopen()打开文件,并且创建缓冲区一集一个包含文件和缓冲区的一个数据结构。另外fopen()返回的是一个指向该结构的指针,如果以文本模式打开该文件,就获得一个文本流;如果以二进制打开该文件,获得一个二进制流。
//使用标准I/O
#include <stdio.h>
#include <stdlib.h> //提供exit()的原型
int mian(int argc, char *argv)
{
int ch; //读取文件时,存储每个字符的位置
FILE *fp; //文件指针
unsigned long count = 0;
if(argc != 2)
{
printf("argv[0] = %s\n", argv[0]);
exit(EXIT_FALLURE);
}
//fopen()函数打开文件,第一个参数是待打开文件的名称,确切说是一个包含改文件名的字符串地址。
if(FP == fopen(argv[1], "r") == NULL)
//第二个参数是待打开文件的名称。"r" = 以读模式打开"w" = 以写打开,把现有的文件长度截为0,如若不存在创建一个新的
//"a"以写打开,在现有文件末尾添加内容,如若不存在创建一个新的
{
printf("argv[1] = %s\n", argv[1]);
exit(EXIT_FALLURE);
}
while ((ch = getc(fp)) != EOF)//与 getchar(ch); 类似 从fp指定文件中获取一个字符
{
//第一个参数是待写入字符,第二个参数是文件指针 stdout作为标准输出相关联的指针
putc(ch, stdout);//与 putchar(ch); 类似 把字符ch放入FILE指针fpout指定文件中
count++;
}
fclose(fp); //关闭fp指定文件,必要刷新缓冲区
printf("argv[1] = %s, count = %lu\n", argv[1], count);
return 0;
}
fseek()第一个参数是FILE 的指针,指向查找文件,fopen()应该已经打开该文件。
第二个参数偏移量,该参数表明从起始点要便宜的位置,参数必须是long类型的值。
第三个参数是模式,SEEK_SET : 文件开始处。SEEK_CUR : 当前位置。SEEK_END :文件末尾
ftell()函数返回long类型的值,返回参数指向当前文件的当前位置距文件开始处的字节数。
//倒序显示文件的内容
#include <stdio.h>
#include <stdlib.h>
#define CNTL_Z '\032'
#define SIZE 61
int main(void)
{
FILE *fp;
char file[SIZE];
char ch;
long count, last;
puts("Please Enter:");
scanf("%80s", file);
if((fp = fopen(file, "rb")) == NULL)
{ //只读模式
printf("reverse can't open %s\n", file);
exit(0);
}
fseek(fp, 0L, SEEK_END); //定义到文件末尾
last = ftell(fp); //文件开始处到结尾的字节数附给last
for(count = 1L; count<= last; count++)
{
fseek(fp, -count, SEEK_END); //回退
ch = getc(fp);
if(ch != CNTL_Z && ch != '\r')
putchar(ch);
}
putchar('\n');
fclose(fp);
return 0;
}
因为fseek()和ftell()潜在的问题是:它们都把文件大小限制在long类型能表示的范围内。
避免文件更大,ANSIC新增两个处理较大文件的新定位函数:fgetpos()和fsetpos()
函数原型
int ungetc(int c, FILE *fp)函数把c指定的字符放入输入流。
int fflush()函数刷新缓冲区,引起输出缓冲区中所有的未写入的数据发送到fp指定输出文件。
int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);
fp识别待处理的流,buf指向待使用的存储区,不是空就创建缓冲区,size告诉数组的大小(size_t是一种派生的整数类型)
mode模式:_INFBF 完全缓冲, _IOLBF 行缓冲, _IONBF 无缓冲
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
size_t是标准的C类型定义的类型,它是sizeof运算符返回的类型,通常是unsigned int类型。
ptr是待写入数据块的地址,size代写入数据块的大小,nmemb待写入数据块的数量,fp要写入文件的指针
size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
ptr是待读取文件数据在内存中的地址,fp待读取的文件,该函数用于读取被fwrite()写入的文件数据。
结构和其他数据形式
1,关键字struct建立结构声明:
struct book { //结构模板开始
char title[MAXTITL];
char author[MAXAUTL];
float value;
//结束
};,
2,定义结构变量: struct book library; //把library声明一个book类型的变量
//传递一个结构
#include <stdio.h>
#define CC 50
struct funds{
char bank[CC];
double bankfund;
char save[CC];
double savefund;
};
double sum(struct funds moolah); //参数是一个结构
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"3",
8452.94
};
printf("Stan has a totle of $%.2f.\n", sum(stan));
return 0;
}
//结构通过.运算符获取stan.bankfund, stan.savefund的值
double sum(struct funds moolah)
{
return (moolah.bankfund + moolah.savefund);
}
//Stan has a totle of $12485.21.
//传递指向结构的指针
#include <stdio.h>
#define CC 50
struct funds{
char bank[CC];
double bankfund;
char save[CC];
double savefund;
};
double sum(const struct funds *); //参数是一个指针
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"3",
8452.94
};
//地址传给函数,使得money指向结构变量stan
printf("Stan has a totle of $%.2f.\n", sum(&stan));
return 0;
}
//指针通过->运算符获取stan.bankfund, stan.savefund的值
double sum(const struct funds * money)
{
return (money->bankfund + money->savefund);
}
//Stan has a totle of $12485.21.
//把结构成员作为参数传递
#include <stdio.h>
#define CC 50
struct funds{
char bank[CC];
double backfund;
char save[CC];
double savefund;
};
double sum(double, double);
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"3",
8452.94
};
printf("Stan has a totle of $%.2f.\n", sum(stan.backfund, stan.savefund));
return 0;
}
double sum(double x, double y)
{
return (x + y);
}
//Stan has a totle of $12485.21.
结构特性:可以赋值也可以初始化另一个结构。
//使用指向结构的指针
#include <stdio.h>
#include <string.h>
#define CC 30
struct namect{
char fname[CC];
char lname[CC];
int num;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
char * s_gets(char *st, int n);
int main(void)
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
return 0;
}
//获取每行的输入
void getinfo(struct namect * pst)
{
printf("Please enter your first name.\n");
s_gets(pst->fname, CC);
printf("Please enter your last name.\n");
s_gets(pst->lname, CC);
}
//算出输入的字母长度
void makeinfo(struct namect * pst)
{
pst->num = strlen(pst->fname) + strlen(pst->lname);
}
//输出打印
void showinfo(const struct namect * pst)
{
printf("%s, %s, your name contains %d num.\n", pst->fname, pst->lname, pst->num);
}
char * s_gets(char *st, int n)
{
char * cc;
char * find;
cc = fgets(st, n, stdin);
if(cc)
{
find = strchr(st, '\n'); //查找换行符
if(find) //如果地址不是NULL
*find = '\0'; //在此放置一个空字符
else
while(getchar() != '\n')
continue; //处理剩余字符
}
return cc;
}
/*
Please enter your first name.
Viola
Please enter your last name.
Plunderfest
Viola, Plunderfest, your name contains 16 num.
*/
//传递并返回结构
#include <stdio.h>
#include <string.h>
#define CC 30
struct namect{
char fname[CC];
char lname[CC];
int num;
};
struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);
char * s_gets(char *st, int n);
int main(void)
{
struct namect person;
person = getinfo();
person = makeinfo(person);
showinfo(person);
return 0;
}
struct namect getinfo(void)
{
struct namect temp;
printf("Please enter your first name.\n");
s_gets(temp.fname, CC);
printf("Please enter your last name.\n");
s_gets(temp.lname, CC);
return temp;
}
//算出输入的字母长度
struct namect makeinfo(struct namect info)
{
info.num = strlen(info.fname) + strlen(info.lname);
return info;
}
//输出打印
void showinfo(struct namect info)
{
printf("%s, %s, your name contains %d num.\n", info.fname, info.lname, info.num);
}
char * s_gets(char *st, int n)
{
char * cc;
char * find;
cc = fgets(st, n, stdin);
if(cc)
{
find = strchr(st, '\n'); //查找换行符
if(find) //如果地址不是NULL
*find = '\0'; //在此放置一个空字符
else
while(getchar() != '\n')
continue; //处理剩余字符
}
return cc;
}
/*
Please enter your first name.
renyichao
Please enter your last name.
zhangxiaoyu
renyichao, zhangxiaoyu, your name contains 20 num.
*/