C语言 深入学习

时间:2023-01-29 12:28:22

浮点数:

x = Mx*2^Ex为一个规格化浮点数,Mx为x的尾数,Ex为x的阶码。
1e-6:表示1 * 10 ^ (-6)。
编译时执行:
sizeof是运算符(而非函数),在编译时执行,不会导致额外的运行时间开销。
三元运算符:是右结合运算。

数据类型:

char:占1个字节
short:占2个字节
int:占4个字节
float:占4个字节
double:占8个字节

一个变量本身定义为常量const时,必须初始化
常量指针:不能通过常量指针修改其指向的值(只是从p的角度来看)。
const int *p;
int const *p;
指针常量:指针其本身就是常量(故定义时须初始化),而且也不能通过这个指针修改其指向:
int * const p = &a;

数组指针(指向一维数组的指针
/ 行指针):char (*p)[10] —— 打括号就是说,先看括号里面的。故可以这样理解:
举例, 二维数组char a[
10][10], char (*p)[10];p = a。
首先确定,
char (*p)[10]只是将p定义为一个指针(其作用是指向某个数组整体,而不是这个数组中的某个元素);10就是指定了指向的数组的长度
p
= a就让p指向了a[0]这个数组;于是p++,就让p指向了下一个数组a[1];
所以若p
= a,那么*p就是获得a[0]这个数组。
既然char (
*p)[10]是指向数组的指针,那么通用的使用就是:把数组的地址给p,即char (*p)[10];char s[]="abcd";p=&s;
而二维数组a[][]的数组名a的含义是:a
== &a[0];

指针数组(数组元素是数组):
char *p[10] —— 没有括号,所以可以这样看:这是一个数组,其元素类型是char *p。这里是一个放了10个指针的数组。所以p[0]=arrary;是指向数组首元素,而不是整个数组

标准输入输出格式:

当数据的实际位宽大于printf中的指定位宽时,将按照数据的实际位宽输出数据

//输出
printf("%ld\n", test); /* 以长整型格式输出 */
printf(
"%f\n", test); //输出单双精度浮点数
printf("%4.2f\n", test); //输出数据共占4位(包括小数点),有2位小数;其他数据类型也是如此(自然有些是没有小数的)
printf("%u", n); //n是unsigned int
printf("%s\n", str); //输出一个字符串
printf("%p\n, p); //输出地址

//输入
/*
scanf:返回值为成功接收到的、输入的变量值个数。
如scanf("%d%d%d",i,j,k,a,b)则只能返回3(因为只能接收三个)
*/
char *str = (char *)malloc(N * sizeof(char)); scanf("%s", str); //输入一个字符串
scanf("%lf", &test); //输入双精度浮点数
scanf("%d,%d", &a, &b); //输入必须与指定的格式相同。比如这里有",",那么在Console中输入也需要","

随机数:

//#include <stdlib.h>:rand、srand
//#include <time.h>:time
srand(0);
printf(
"%d\n", rand()); //rand对应于srand,只要srand的种子不变,rand的随机值也不会变;rand的范围是0 - int
srand(time(NULL));    //srand(unsigned)
printf(
"%d\n", rand()); //time函数返回当前时间(秒);参数param如果不为BULL,则time会将时间赋给param。故这样一来随机数就会一直改变(真正实现随机)
printf("%d\n", rand() % 100 + 1); //生成1-100的随机数:rand() % 100是0-99

指数函数:

pow(x, y)        //表示x的y次方 —— math.h

数组:

初始化:

int count[10] = {0};    //长度为10的数组,全都初始化为0;定义数组时,数组长度只能是常量,不能是变量或含变量的表达式

二维数组:

多维数组在内存中实际上都是一维存储的。例如二维数组,我们只是在逻辑上可以理解为二维的矩阵,但实际上二维数组是一维数组,每个元素还是一维数组,所以整个二维数组都是"一条线"
1、因此int a[3][3]={1...9};int (*p)[3] = a;此时不仅可以*(*p+2),还可以*(*p+8)=*(*(p+2)+2),只要不越二维数组的界(这里是9);
简单点就是:int a[3][3] = {1...9};int *p = &a[0][0];此时可以从头指到尾:头是*p,尾是*(p+8);
2、所以二维数组a有m行n列,则在a[i][j]之前的元素个数为i * n + j —— 因为a[i][j]是第i + 1行,第j + 1列,而前i行都是在a[i][j]之前的

求数组大小:

char s[][20] = { "first", "second", "Third" };
char (*p)[20] = s;
printf(
"%d %d\n", sizeof(s) / 20, sizeof(p)); //3、4:因为p要找"\0",即'\0' —— {"...", "...", "...", "\0"}
//用指针数组
char *p[] = {"How", "are", "you"};
printf(
"%d\n", sizeof(p) / sizeof(char *));

函数

函数返回值:

printf("%d\n", func(-1));        //main函数

int func(int i)
{
if (i == 0)
{
return i;
}
else
{
printf(
"h\n"); //这种情况,会返回printf输出的字符个数(包括转义符):此时return 2
}
}

数组作为函数参数,形参数组与实参数组实际上是同一个数组,即传址调用;数组作为实参,只需要写数组名(无论是几维数组)。

变量的生命周期与作用域:

#include <stdio.h>

void Incre();

int main()
{
Incre();
Incre();
return 0;
}

void Incre()
{
static int x = 1; //静态:第二次就输出上次,保存的x的值;此外这还是一个局部var,因此作用域还是Incre
printf("%d\n", ++x);
}

字符串:

字符常量与字符串常量的区别:

  • 字符串常量:1、字符串里面包含的字符是连续存储的。2、每个字符串都有结束标记("\0"),这是一个特殊的字符,即ASCII码为0的字符。3、C语言用字符数组(没有字符串变量)来动态存储字符串,例如char s[10] = {'a', 'b', '\0'}是一个字符串,而char s[10] = {'a', 'b'}不是。或者写为char s[] = "abc"(在初始化的情况下可以省略数组大小)。存储多个字符串(用二维数组):char s[][10] = {"星期一", "星期二"}(第一个大小可省略,由字符串的个数,即二维数组中包含数组的个数决定)。
  • 字符常量:char s = 'a'。

用指针来访问字符串:

#include <stdio.h>

int main()
{
char *p = NULL;
char s[] = "abc";
p
= s;    //反过来就不行了,因为数组名相当于是一个指针常量
printf(
"%c\n", *p); //现在只是指向字符串的第一个字符
//printf("%s\n", *p); //这样是不行的,应该是printf("%s\n", p);输出的是这个指针指向字符串的某个字符以及后面剩下的字符,所以p在此时可以用s替代
return 0;
}

输入输出字符串:

char s[] = "adjshd";
char s1[] = "casn";char *p = s;
char *p1 = s1;
gets(p);
//可以输入空格
puts(p);
scanf(
"%s", p1);
printf(
"%s\n", p1);

但是gets是存在安全隐患的(它没有限制输入的大小,即输入可能超出字符数组的大小,此时就可能把输入存到了指定的字符数组之外的地方):

char s2[] = "sdfjhasjd";
char *p2 = s2;
char n = 3;
//fgets限制输入的长度
fgets(p2, n, stdin); //最多只能接收n - 1个字符
puts(p2);

getchar函数:

用处1:

#include <stdio.h>
#include
<math.h>

int main()
{
int n = 0;
char c = 'c';
int result = scanf("%d", &n);
if(result != 1) //输入与scanf指定的类型不符,则不能接收到。未能成功接收的字符会暂时放在缓冲区
{
c
= getchar(); //此时getchar就是将缓冲区的字符一次取走一个,并返回
}
printf(
"%d %c\n", n, c);
return 0;
}

用处2:

#include <stdio.h>
#include
<math.h>

int main()
{
char c = 'c';
//getchar直接从stdin读取一个字符,putchar从stdout输出
c = getchar();
putchar(c);
return 0;
}

字符串处理函数:

//需要#include <string.h> -- 字符串处理函数strcmp、strlen、strcpy、strcat
strcmp(s1, s2); //两个字符串自左向右,逐个字符相比(比较ASCII码),直到出现不同字符或'\0'为止,此时若:s1==s2;return 0;s1 > s2;return 1;否则return -1.
strlen(s1); //返回字符串s1的长度,例如:char s1[10] = "abcde";printf("返回字符串的长度:%d,返回数组的长度%d\n", strlen(s), sizeof(s));
char source[] = "hello kit";char destination[] = "world";
puts(strcpy(destination, source));puts(destination);
//source->destinations
char source[] = "hello kit";char destination[] = "world";
puts(strcat(destination, source));puts(destination);
//即destination + source

函数库:

字符处理函数#include <ctype.h>
函数的参数皆为int,返回也为int
int isdigit(int c); //判断是否为数字字符,例如:如果是'3',return 1;是'a',return 0;
int toupper(int c);

实用函数#include
<stdlib.h>
double atoi(const char *s);
double atof(const char *s); //例如://printf("%6.2f\n", atof("1e2"));输出:100.00——还要算上小数点
//printf("%6.2f\n", atof("y3")); 输出: 0.00;就算是e3也不会当成是科学计数,而是和y3一样

枚举类型:

#include <stdio.h>

int main()
{
//定义枚举类型,集合{}中的元素,是枚举元素 —— 供枚举变量进行选择
//枚举元素的值:如果没有指定具体值,编译器会把第一个元素处理成0,后面元素以此+1:因此枚举元素是常量
//当然也可以自定义枚举元素的值:例如指定Tuesday = 100,那么Wednesday也遵循+1的原则,因此Wednesday = 101
enum Date { Monday, Tuesday, Wednesday };
//定义枚举变量
//1、直接在定义类型时,顺便把变量定义了
//enum Date { Monday, Tuesday, Wednesday } date;
//2、或者只定义变量
//enum { Monday, Tuesday, Wednesday } date;
//枚举变量选择具体的枚举值
enum Date date = Tuesday;
date
= Wednesday;
printf(
"%d\n", date);
return 0;
}

联合union:

编译器处理union时,一个union所有成员共用同一段内存空间(同一时刻,只有一个成员使用内存空间,存放成员值),因此内存空间的大小=最长的成员大小。具体使用如下:

#include <stdio.h>
int main()
{
union
{
int i[2];
int k;
int c;
} t,
*s = &t;
//给哪个成员赋值,内存空间就被这个成员使用
s->i[0] = 10;
s
->i[1] = 20;
printf(
"%d\n", s->c);

s
->k=30;
printf(
"%d\n", s->c);

s
->c = 1000;
printf(
"%d\n", s->c);
printf(
"%d\n", s->i[1]);//始终为20
return 0;
}

使用联合的一些注意事项:

1、联合不能作为函数参数传递。2、不能整体初始化或者整体赋值,只能对其成员一个一个赋值。3、不能作为函数返回值。

结构体:

#include <stdio.h>

typedef
struct student
{
int number;
char name[20];
char sex;
} S;
//此时S是别名
struct
{
int year;
int month;
int day;
} birth;
//此时birth是结构体变量

int main()
{
S s;
s.number
= 1000;
birth.year
= 250;
printf(
"%d %d\n", birth.year, s.number);
return 0;
}

文件操作

<stdio.h>中的一些变量类型、宏定义:

  • 文件结束符:#define EOF     (-1)
  • 存储文件流信息的类型:typedef struct _iobuf FILE;
  • 类型的大小,即sizeof运算符的结果:typedef unsigned int size_t;

打开已存在文件、或新建一个文件:

函数原型:FILE *fopen(const char *filename, const char *mode);//用FILE的指针变量操纵文件
根据打开模式(mode)的不同决定:新建或打开文件
1、文本文件:
mode的值:r(打开已有文件,只读)、w(打开文件,只写、覆盖写入,即打开后删除原有内容,重新写入。若文件不存在,则新建)、a(打开文件,只写、追加写入。若文件不存在,则新建)
可读写:r
+(打开已有文件,覆盖写入)、w+(新建文件)、a+(打开文件。若文件不存在,则新建。写:追加写入)
2、二进制文件mode:形如ab、ab+
=>每次读写都会从文件游标开始读(第一次读,游标在文件开头),游标属于fp指向的 FILE 变量:因此,对于同一个FILE变量,fgetc的调用也会影响到fgets的读取位置

清空缓冲区数据,并关闭文件:

函数原型:int fclose(FILE *fp);
成功关闭,释放掉文件占用的内存,
return 0;发生错误,return EOF(常量,在stdio.h中定义);

写入文件:

写入一个字符:int fputc(int c, FILE *fp);
如果成功,
return 刚写入的c;如果失败,return EOF;
写入一个字符串:
int fputs(const char *s, FILE *fp);
如果成功,
return 一个非负值;失败,return EOF;

格式化写入文件:

//与fputs同理,函数原型:int fprintf(FILE *fp, const char *formatSting, ...);
//像printf一样,接受可变长参数,如:
fprintf(fp, "%d != %d\n", 1, 2);//即写入1 != 2

读取文件:

//与写入同理:int fgetc(FILE *fp)
printf("%c\n", fgetc(fp));
fgetc(fp);
//每调用一次,读取文件的游标,就会向后移动一个char
printf("%c\n", fgetc(fp));
//读取字符串:char *fgets(char *buf, int n, FILE *fp);
//读取n - 1个字符。并将读取到的字符串,复制到字符串变量buf中,最后以\0结束 —— buf的长度应不少于n
//返回的字符串与buf的值相等
char str[7];
char *buf = str;
printf(
"%s\n", fgets(buf, 7, fp));//如果还未读取n个char,就遇到了\n或者EOF,则直接结束本次读取(包含\n)

直接读取一个单词(从游标开始,到遇到的第一个空格为止,认为是一个单词;游标是读到了这个空格的,因此调用下面的函数后,游标指向这个空格之后):

char buff[255];
//与fprintf不同:%s作为sourceString(不加修饰),buff作为destString
fscanf(fp, "%s", buff);
fscanf(fp,
"%s", buff);
printf(
"%s\n", buff);//读到第二个单词

检测当前游标是否指向文件结尾(EOF):

int feof(FILE *fp);//文件结束,return 1;否则return 0;