0. 参考
https://www.runoob.com/cprogramming/c-tutorial.html
1. 关键字
https://blog.csdn.net/L_fengzifei/article/details/127921678!!!
1.1 auto
动态类型
所有局部变量 默认为auto类型
只能在函数内使用,只能修饰局部变量
块作用域、空链接
int func()
{
// 下面两种写法都表示的是局部变量
int mount;
auto int month;
}
typedef
https://blog.csdn.net/weixin_45743799/article/details/104532763
https://blog.csdn.net/weixin_44948467/article/details/114452324
typdef 与 #define
#define
是由预处理器来处理的,在预处理阶段,只是简单的字符序列替换
typedef
是由编译器完成的,在编译阶段,给原来的数据类型起别名,可以作为一种新的数据类型进行使用
typedef int INGEGER;
INTEGER a,b; //等价于 int a,b;
typedef int* PTR_INT
PTR_INT p1,p2
typedef与数组
arr数组的类型是
char [20]
typedef char ARRAY[20]; // ARRAY 是char [20]的别名
ARRAY arr; // 等价于char arr[20];
typedef与结构体
typedef strcut Stu{
char name[10];
int age;
}STU; // STU 是struct stu的别名
STU stu1,stu2; // 等价于struct stu stu1,stu2;
typdef struct Stu{
char name[10];
int age;
}STU,*PSTU;
// version2
typdef struct Stu{
char name[10];
int age;
};
typedef struct Stu STU;
typedef struct Stu* PSTU;
STU stu1; // 等价于 struct Stu su1;
PSTU pstu2; // 等价于 struct Stu *pstu2
结构体数组
#define MAXSIZE 10
typedef struct
{
int data;
int next; // 下一个元素的数组下表
}SLinkList[MAXSIZE];
int main()
{
SLinkList arr; // struct arr[MAXSIZE];
return 0;
}
typedef与指针
typedef int (*PTR_ARR)[4]; //二维数组 PTR_ARR 是int *[4]的别名
PTR_ARR p1,p2; //等价于int (*p1)[4],(*p2)[4];
typedef 函数指针
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
// 例子
typedef int (*PTR_TO_FUNC)(int, int);
int max(int a, int b){
return a>b ? a : b;
}
PTR_TO_FUNC pfunc = max;
printf("max: %d\n", (*pfunc)(10, 20));
1.3 const
整个作用域值不被改变,常量,(建议使用大写名进行)
使用const关键字 必须进行初始化
const 修饰局部变量,在栈区创建
const 修饰全局变量,在常量区创建
const 不能用于switch-case语句,实际上const 修饰之后变成的是只读变量,不是实际的常量
不要将const修饰的数据 赋值给非const修饰的数据,不然会报错
!!! 不完全对 !!!
– 见下面的const和非const
可以将非const修饰的数据,赋值给const修饰的数据
const int Var=10;
// const int var;
// var=20; // 不能进行这种初始化
// var=20; // 不能改变
进阶
int getNum()
{
return 100;
}
int main()
{
int n=10;
const int M1=getNum(); // 运行时初始化
const int M2=n; // 运行时初始化
const int M3=30; // 编译时初始化
}
1.3.0 const 与 非const
https://blog.csdn.net/souching/article/details/6796705
https://blog.csdn.net/zxc024000/article/details/78624188
const某些情况下可以赋值给非const
本质上是值拷贝(),与地址拷贝的区别
int a=10;
const int &b=a;
int c=b; // 正确
printf("%#p\n",&a); // 0x61fe14
printf("%#p\n",&b); // 0x61fe14
printf("%#p\n",&c); // 0x61fe110
a=20;
cout<<b<<endl; // 20
cout<<c<<endl; // 10
const Student & topval(const Student &s) const;
Student s=s1.topval(s2); // 正确
1.3.1 const 与数组
数组不允许被修改,数组中的元素都被当成常量
const int a[10];
1.3.2 const 与 指针
可以修饰指针变量本身,或指针指向的数据
不要将const修饰的数据 赋值给非const修饰的数据,不然会报错 (
!!! 不完全对 !!!
)(要看是值传递还是地址传递)
const 离(指针)变量名远,修饰的是指针指向的数据
const 离(指针)变量名近,修饰的是指针变量本身
const int *p; // 指针指向的值不能修改,指针可以指向其他数据
int const *p; // 指针指向的值不能修改,指针可以指向其他数据
int* const p; // 指针变量本身不能修改(不能指向其他数据)
// 下面的方法 表示指针和指向的数据都是只读的
const int* const p4
int const * const p5
// 不同通过指针修改数组,但是可以通过数组名本身进行修改!!!
int arr[2]={0};
const int *p=arr;
// p[0]=1; // ERROR 错误
arr[0]=10; // 正确
1.3.3 const 与函数参数
const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,可以用const来限制
// 限制内部函数对字符串str进行修改
size_t strnchr(const char* str,char ch){
int i,n=0,len=strlen(str);
for (i=0;i<len;i++){
if (str[i]==ch){
n++;
}
}
return n;
}
int main(){
char* str="http"; //hello
char ch='t';
int n=strnchr(str,ch);
printf("%d\n",n);
return 0;
}
注意:上面针对的是指针传递,如果是值传递,本来实参传形参的时候就是深拷贝操作,所以不需要也不要加const
// 下面两种都要禁止
// void func(const int a);
// void func(const <T> a); // 用户定义的类型
1.4 static
https://www.runoob.com/w3cnote/cpp-static-usage.html – 还没看
静态变量
无论是全局的的还是局部的,都存储在静态数据区
静态数据区数据在程序启动时就会初始化,直到程序结束
对于代码块中的静态局部变量,即使代码块执行结束,也不会销毁
静态存储区的变量只能,且只需要初始化一次,由于不销毁,所以多次调用都是有效的
int sum(int n)
{
static int reuslt=0;
result+=n;
return result;
}
int main()
{
for (int i=0;i<100;i++)
{
result=sum(i);
}
}
static
也可以修饰全局变量,当修饰全局变量的时候,会使变量的作用域限制在声明它的文件中。全局声明的一个static变量或方法可以被任何函数或方法调用,只是必须与被修改的变量或方法在同一个文件中
static
修饰静态函数,表示该函数只能在当前源文件中被调用
1.4.1 static/const/多文件
// s.h
/*
如果下面的变量不加static 在file1.c和file2.c的引用过程中会出现重复定义
如果加上static 则两个变量分别变成了file1.c 和 file2.c中的(私有)变量,不会造成重复定义的问题
*/
static const int a=1;
static const char* names[2]={"li","wang"};
// file1.c
#include "s.h"
// file2.c
#include "s.h"
1.5 extern
extern dataType var 相当于引用声明,表明声明不是一个定义,告诉编译器该变量的真正定义在别的地方,可以当前文件,也可以是别的文件
extern 关键字修饰的表达式,的变量,不会分配内存空间,他只是用来引用一个已经存在的外部定义
不能利用extern进行变量定义,extern int a=1
; 这种是错误的
用于提供一个全局变量的引用
跨文件调用的时候,要加上extern,虽然有些情况下不写extern仍然可以执行(函数不用extern可以,变量不可以)(因为对于函数声明的extern 加不加都是等价的)
定义与声明
// main.cpp
int a=1; // 既是声明也是定义
int b; // 既是声明也是定义
extern int c; // 只是声明,c在其他文件中被定义
简单用法
提供一个全局变量/函数的引用,全局变量对所有程序文件都是可见的
使用extern
无法初始化(定义)变量,会把变量名指向一个之前定义过的存储位置
其实就是 A文件中定义了一个可以在B文件中使用的全局变量或函数,那么在B文件中就可以使用extern来得到A文件中定义的变量或函数的引用(用在在另一个文件中声明一个全局变量或函数)
进阶:A/B文件可以存在相互的使用extern
// A.c
#include <stdio.h>
int count;
extern void write_extern();
int main()
{
count=5;
write_extern();
}
// B.c
#include <stdid.h>
extern int count;
void write_extern(void)
{
printf("count is %d\n",count)
}
简单用法2
时刻注意先声明后定义
可多次声明,但是不可重复定义
// 在一个文件内
// 例子1
int a; // 之所以下面可以使用extern 是因为此时a相当于一个外部链接变量(外部链接的静态)
// 当在函数内不写extern的时候,那么根据作用域进行判断使用局部变量a或全局变量a
int main() // or some function
{
extern int a; // 使用的是全局变量的a
// int a; // 重新定义了一个局部变量a ; 如果extern int a 和int a 同时存在于一个作用域块内,则ERROR重复定义
}
// 例子2
int a=10;
int main() // or some function
{
extern int a; // 使用的是全局变量的a
// int a; // 重新定义了一个局部变量a ; 如果extern int a 和int a 同时存在于一个作用域块内,则ERROR重复定义
}
// 例子3
// 值得注意的是:下面的例子是正确的!!!
int main()
{
extern int a;
}
int a=10;
进阶用法
https://blog.csdn.net/m0_46606290/article/details/119973574
https://blog.csdn.net/rammuschow/article/details/107993479
/*test.h*/
extern int a; // 引用声明,引用了一个已经存在真正定义的变量,该变量先通过.c文件#include 然后进行了定义!!!
/*test.c*/
#include "test.h"
int a=10;
/*main.c*/
#include <stdio.h>
#include "test.h" // 会链接到test.c文件
int main()
{
printf("%d\n",a); // 10 使用的是test.c中的a 具有外部链接的变量
int a=2000; // 这个是具有空链接的变量:块作用域、局部变量,这个不会造成重复定义
printf("%d\n",a); // 2000 就近原则,使用的是局部变量的a
}
进阶用法2
/*test.h*/
void func(); // 函数声明
/*test.c*/
#include <stdio.h>
#include "test.h"
extern int times; // 引用声明,可以引用主文件中的全局变量
// 函数定义
void func()
{
int i=0;
int i=0;
for ( i;i<times;i++)
{
printf("hello world\n");
}
}
/*main.c*/
#include <stdio.h>
#include "test.h"
// 定义全局变量
int times=10; // test.c中extern 引用声明
int main()
{
loop(); // 调用的是test.h 中连接test.c的函数
return 0;
}
1.5.1 extern / #include
https://www.runoob.com/w3cnote/extern-head-h-different.html
1.6 register – 还要看
定义存储在寄存器中而不是RAM(内存)中的局部变量,
意味着变量的最大只存等于寄存器的大小(一个字节),且不能也对它应用一元运算符???
通常变量存储在内存中
寄存器变量被存储在CPU的寄存器中,寄存器变量比普通变量具有更对的访问和读写速度
无法获得寄存器变量的地址
寄存器只用于需要快速访问的变量,如计数器等(定义了register,并不意味着变量将被存储在寄存器中,只是意味着变量可能存储在寄存器中,这取决于硬件和实现的限制)
一般不能定义double类型
int func()
{
register int miles;
}
void func(register int n);
1.7 volatile – 没看懂???
通常用于 硬件地址和与其他并行运行的程序共享的数据(例如:一个地址中可能保存着当前的时钟事件,不管程序做些什么,该地址的值都会随着时间而改变。另一种情况是一个地址被用来接收来自其他计算机的信息)
语法同const
防止编译器、寄存器过分优化
一般用于嵌入式
volatile int a;
volatile int b;
// 防止a和b,共享x???
a=x;
b=x;
控制方法
if-else
注意,
else
总是与其前面位置最近的if
配对
判断语句中,可以是数值、表达式、赋值表达式、变量等,只要是非0都可以
if (condition)
{
...
}
else if (condition)
{
...
}
else ()
{
...
}
//例子
if (a=b) // 把b的值赋值a,如果b为非0,则执行
{
//TODO
}
switch-case
switch-expression 必须是一个常量表达式,一个整型(整型表达式)或一个枚举类型,(其实字符常量也可以,因为字符和整型可以相互转换),(也就是说不能包含任何变量,浮点型也不行)
expression1/2 必须是与expression类型相同的数据,且必须是常量或字面量(字符也行,可以与整型相互转换)
expression 总结switch后面接的expression 可以是变量表达式(可以包括运算公式),但是必须是整型变量表达式 -->
(整型常量、整型变量、整型常量表达式、整型变量表达式、枚举类型)
case 后面接的expression 必须是常量表达式(表达式的结果必须是整型就可以,但不能是变量)(可以包括运算公式)(不能是变量),必须与switch后的expression具有相同的类型(整型常量、整型常量表达式)
两种表达式不能是浮点型,但是可以是字符型
const 后面接const 常量也不行,因为const int val 是只读变量 不是常量
如果case不加break,控制流会继续后面的case的语句(无论满不满足下一个case的条件),直到遇到break
当所有的case都不匹配的时候,有一个可选语句default(default不是必须的),default语句中的break不是必须的
swtich(expression)
{
case expression1:
...
break;
case expression2:
...
break;
default:
...
}
例子
{
char c='b'; // c存储在常量区
scanf("%c",&c);
switch (c)
{
case 'a':
cout<<'a'<<endl;
break;
default:
cout<<"not a"<<endl;
break;
}
return 0;
}
#include <stdio.h>
int main(){
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day; // day是整型常量
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
case Fri: puts("Friday"); break;
case Sat: puts("Saturday"); break;
case Sun: puts("Sunday"); break;
default: puts("Error!");
}
return 0;
}
while
while(condition)
{
...
}
注意!!!
#include <iostream>
using namespace std;
int main()
{
int a=10;
while (a--)
{
cout<<a<<endl; // 第一次a已经是9了!!!
}
// for (;a>=0;a--)
// {
// cout<<a<<endl;
// }
return 0;
}
for
int
会首先被执行一次,且只会被执行一次;这一步可以声明并初始化任何循环控制变量,也可以不写任何语句,只有一个分号即可
然后会判断condition,如果为真则执行循环主体,如果为假,则不执行循环主体,且控制流会跳转到紧接着for循环的下一条语句
执行完for循环主体后,控制流会跳回increment,该语句可以留空,只要在条件后有一个分号出现即可
条件再次被判断,如果为真,则执行循环,条件变为假的时候,for循环终止
for(int;condition;increment)
{
...;
}
do-while
函数体至少会被执行一次
do
{
...;
}while(codition);
文件操作
C语言中硬件设备可以看做文件
打开文件–保存到一个FILE类型的结构体变量中;关闭文件就是释放结构体变量
文本和二进制
都是二进制存储的,不过是采用特定的编码方式能够进行读取
换行符
linux :
\n
程序中的数据会原封不动的写入文本文件
windows:\r\n
作为文本文件的换行符如果以文本方式打开文件,当读取文件时,程序会将文件中所有
\r\n
转换成一个字符\n
(如果文本文件中有连续的两个字符\r\n
,则程序会丢弃前面的\r
,只读\n
)
当写入文件时,程序会将\n
转换成\r\n
写入
位置指针/EOF
文件位置指针,指向当前读写到的位置,也就是读写到第几个字节
rewind(fp)
将文件中的位置指针重新定位到文件开头
部分函数再读取出错时也会返回EOF,有些情况下无法判断文件读取结束和是读取出错,feof() ferror()
fseek
移动文件指针到任意位置
fseek
一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错
一般搭配fread\fwrite使用
int fseek(FILE* fp,long offset,int origin)
// offset 移动的字节数,offser 为正,向后移动;offset为负时,向前移动
// origin 为起始位置,表示从何处开始计算偏移量
// // 文件开头 0;当前位置 1;文件末尾 2
feof
判断文件内部指针是否指向了文件末尾
feof(FILE *fp)
ferror
判断文件操作是否出错
ferror(FILE *fp)
if (ferror(fp))
{
puts("出错")
}
else
{
puts("读取完毕")
}
fopen
/fclose
#include <stdio.h>
FILE *fp=fopen("filename","openmode");
// 打开错误就返回一个空指针NULL
FILE *fp;
if (fp=fopen("filename","openmode")==NULL) "ERROR..."
// 正常关闭的时候返回值为0,非0表示有错误发生
fclose(fp);
fgetc/fputc
int fgetc(FILE *fp)
// 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF(EOF在stdio.h中定义的宏,通常是一个负值)
int fputc(FILE *fp)
char ch;
FILE *fp=fopen("filename","filemode");
ch=fgetc(fp)
FILE *fp=NULL;
char ch;
if ((fp=fopen("F:\\c-learning\\file.txt","rt"))==NULL)
{
puts("failed to open file");
exit(0);
}
while ((ch=fgetc(fp))!=EOF)
{
putchar(ch);
}
putchar('\n');
if (ferror(fp))
{
puts("出错")
}
else
{
puts("读取完毕")
}
fclose(fp);
char ch='a';
fputc(ch,fp);
// 知道遇到回车结束
while ((ch=getchar())!='\n')
{
fputc(ch,fp);
}
fgets/fputs
读写一个字符串或一个数据块
char* fgets*(char *str,int n,FILE *fp)
str为字符数组,读取成功时返回字符数组首地址
int fputs(char *str,FILE *fp)
向指定的文件写入字符串,写入成功返回非负数,失败返回EOF (注意:fputs不自动添加\0)
读取到的字符串会在末尾自动添加\0,n个字符也包括\0。实际值读取到了n-1个字符
在读取n-1个字符之前如果出现了 换行 或者读到了文件末尾,则读取结束。即不管n的值多大,fgets()最多只能读取一行数据,不能跨行
C语言中没有按行读取文件的函数,只能将n设置的足够大,每次读取到一行数据
fgets() 遇到换行符时 会将换行符一起存入到当前字符串 !!!
// 读取100个字符
#define N 101
char str[N];
FILE *fp=fopen("filename","filemode");
fgets(str,N,fp);
//例子
int main()
{
FILE *fpl
char str[N+1];
if ((fp=fopen("filename","openmode"))==NULL)
{
puts("failed to open file");
exit(0);
}
while (fgets(str,N,fp)!=NULL)
{
printf("%s\n",str);
}
fclose(fp);
return 0;
}
if ((fp=fopen("F:\\c-learning\\file.txt","at+"))==NULL)
{
puts("failed to open file");
exit(0);
}
char *str2="hello\nqt\n";
fputs(str2,fp);
fclose(fp);
fread/fwrite
以数据块的形式读写文件,
注意,使用fread/fwrite时应该以 二进制 的形式打开
其实就是多个字节数据:一个字节、一个字符串、多行数据等
// 读写成功,返回读写成功的块数count
size_t fread(void* ptr,size_t size,size_t count,FILE *fp) // 读取文件
size_t fwrite(void* ptr,size_t,size,size_t couont FILE *fp) // 写入文件
// ptr 内存区块的指针,可以是数组、变量、结构体等,用于存放读取到的数据
// fwrite 的ptr 用于存放写入的数据
// size 表示 *每个数据块* 的字节数
// count 表示要读写的 *数据块的块数*
// fp 表示文件指针
// 每次读写size*count个字节的数据
// 读写成功,返回读写成功的块数count,如果返回值小于count
// fwrite 发生了写入错误,可以用ferror()函数检测
// fread 可能读到了文件末尾或发生错误,用ferror() 或feof()检测
int main()
{
int N=5;
int a[5]={1,2,3};
int b[5];
int i,size=sizeof(int);
FILE *fp;
if ((fp=fopen("F:\\c-learning\\file2.txt","rb+"))==NULL)
{
puts("file to open");
exit(0);
}
// size 每个块的字节数; N 为块数
fwrite(a,size,N,fp); // 把a写入到fp
rewind(fp);
fread(b,size,N,fp); // 把fp读到b
for (int i=0;i<N;i++)
{
printf("%d\t",b[i]);
}
printf("\n");
fclose(fp);
return 0;
}
进阶-读写结构体
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 2
struct stu
{
char name[10];
int num;
int age;
float score;
}boya[N],boyb[N],*pa,*pb;
int main()
{
FILE *fp;
int i;
pa=boya;
pb=boyb;
strcpy(pa[0].name,"zhangsan");
strcpy(pa[1].name,"lisi");
for (int i=0;i<N;i++)
{
pa[i].num=10;
pa[i].age=20;
pa[i].score=60.0;
}
if ((fp=fopen("./file.txt","wb+"))==NULL)
{
puts("fail to open file\n");
exit(0);
}
fwrite(boya,sizeof(struct stu),N,fp); // 写入fp
rewind(fp);
fread(boyb,sizeof(struct stu),N,fp); // 从fp读入结构
// fread(&someCertainStructInstance,sizeof()...)
for (int i=0;i<N;i++)
{
printf("%d\n",pa[i].num);
printf("%d\n",pa[i].age);
printf("%f\n",pa[i].score);
}
fclose(fp);
return 0;
}
fprintf/fscanf – 有问题
scanf\printf
把字符串输出到标准输入输出设备上(显示器)
fscanf
把格式化字符串输出到执行文件
fprintf
把文件的内容输出到指定的数据
int fscanf(FILE *fp,char *format); // 返回参数列表中被成功赋值的参数个数
int fprintf(FILE *fp,char *format); // 返回成功写入的字符的个数,失败则返回负数
FILE *fp;
int i,j;
char *str,ch;
fscanf(fp,"%d,%s",&i,str); // 读取文件
fprintf(fp,"%d %c",j,ch); // 写入文件
文件复制
注意:除非对于文本格式,否则
fopen
一定要以二进制的形式打开
开辟一个缓冲区,不断从源文件中读取内容到缓冲区,每读取完一次就将缓冲区中的内容写入到新建的文件,直到把原文件的内容读取完
通常来讲,缓冲区的数据时没有结束标志的,如果缓冲区填充不满,–> 一般借助的就是fread 记录每次读取到的字节数
size_t fread(void* ptr,size_t size,size_t count,FILE *fp)
// 如果 size =1 ,则返回的就是读取的字节数,
int copyFile(char *fileRead,char *fileWrite);
int main()
{
char fileRead[100]; // 要复制的文件名
char fileWrite[100]; // 复制后的文件名
strcpy(fileRead,"file1.wmv");
strcpy(fileWrite,"file2.mp4");
if (copyFile(fileRead,fileWrite))
{
printf("copy done ...");
}
else
{
printf("copy failed...");
}
return 0;
}
int copyFile(char *fileRead,char *fileWrite)
{
FILE *fpRead;
FILE *fpWrite;
// 设置缓冲区大小
int bufferLen=1024*4;
// 开辟缓冲区
char *buff=(char*)malloc(bufferLen);
// 实际读取的字节数
int readCount;
if ((fpRead=fopen(fileRead,"rb"))==NULL || (fpWrite=fopen(fileWrite,"wb"))==NULL)
{
printf("connot open file");
exit(1);
}
// 不断从fileRead读取内容,放在缓冲区,再将缓冲区的内容写入filewrite
// readcount 表示实际是多少个字节
while ((readCount=fread(buff,1,bufferLen,fpRead))>0)
{
// 下面两种写法都可以 !!!
fwrite(buff,readCount,1,fpWrite);
// fwrite(buff,1,readCount,fpWrite);
}
free(buff); //手动释放动态内存
fclose(fpRead);
fclose(fpWrite);
return 1;
}
ftell
long int ftell(FILE* fp); //获得文件内部指针(位置指针)距离文件开头的字节数
long fsize(FILE *fp);
int main()
{
long size=0;
FILE *fp=NULL;
char filename[30]="file1.wmv";
if ((fp=fopen(filename,"rb"))==NULL)
{
printf("failed open...");
exit(1);
}
printf("%ld\n",fsize(fp));
return 0;
}
long fsize(FILE *fp)
{
long n;
// 当前位置
// fpos_t 保存文件的内部指针
fpos_t fpos; // typedef long long fpos_t
// 获取当前位置
// fgetpos 获得文件内部指针,fsetpos 设置文件内部指针
fgetpos(fp,&fpos);
// 设置到文件末尾
fseek(fp,0,SEEK_END);
// 获得文件内部指针(位置指针)距离文件开头的字节数
n=ftell(fp);
// 恢复之前的位置
fsetpos(fp,&fpos);
return n;
}
exit
#include<stdlib.h>
exit(1); // 异常退出,返回给操作系统
exit(0); // 正常退出