虽然学过C和C 但很长一段时间没有写语法不熟悉,会像上一篇文章一样避免很多简单的方法,为了写代码稍微有效,必须整理实用语法(以算法笔记为学习材料,为我pat整理考试,使用代码C C的形式)。
基本类型">基本类型
int整型
绝对值在10^9(包括10^9)范围内的整数可以定义为int类型。
long long长整型
不容易记住的范围(大于2147483647)超过10^9的都定义成long long吧。
double浮点型
单精度float精度不够,遇到浮点数据都用double来存储吧。
需要特别注意的是浮点数的比较,因为浮点数的存储并不总是准确的,所以比较可能有问题,需要引入一个极小的数字eps纠正误差。
const double eps = 1e-8; // e用于表达10次次幂
为了便于比较,可以将比较操作定义为宏的形式。注意宏里记得打括号,以防出错。
#define Equ(a,b) ((fabs((a)-(b)))<(eps)) // 判断是否等于 #define More(a,b) (((a)-(b))>(eps)) // a是否大于b #define MoreEqu(a,b) (((a)-(b))>(-eps)) // a是否大于等于b
char字符型
- 例如:char c = 'e'
- 转移字符:\n 换行,\0 代表空字符
- 字符串常量:
-
c字符串用字符数组存储。
#include <stdio.h> int main(){ char str[15] = "ele elejiuchi"; printf("%s",str); return 0; }
- C 中有string类型,使用需要##include <string>”,注意是<string>,不是<string.h>。但是这种类型不方便使用scanf和printf读入和读出(如下面的代码),只使用cin和cout但是这个地方很方便。
#include <stdio.h> #include <string> using namespace std; int main(){ string a; a.resize(100); //如果用scanf读取时不声明长度,运行会报错 scanf("%s", &a[0]); printf(a.c_str()c_str()函数用于string转char数组。 return 0; }
-
布尔型
布尔型在C 可直接使用,true和false存储时为1和0,可使用%d输出。
类型转换
变量名(新型名)
定义常量
#define pi 3.14 const double pi = 3.14
运算符
&&与,||或,!非
输入输出
数据类型 | 格式符 | 例 |
---|---|---|
int | %d | scanf("%d",&h); |
long long | %lld | scanf("%lld",&n); |
float | %f | scanf("%f",&n); |
double | %lf | scanf("%lf",&n); |
char | %c | scanf("%c",&c); |
字符串(char数组) | %s | scanf("%s",); |
注意字符串数组名前不添加地址操作符。
代码格式 | 输入 |
---|---|
scanf("%d:%d:%d",&h,&min,&s); | 12:58:3 |
scanf("%d%d",&n1,&n2); | 233 666 |
scanf用空白符(空格和tab)读入结束时,字符%s读入以空格和换行结束。
注意double输出时%f而非%lf。
getchar和putchar
getchar和putchar用于输入和输出单个字符,包括‘\t空格和空格和空格和空格\n’。
char a[5]; for(int i=0;i<=4;i ){ a[i]=getchar(); } for(int i=0;i<=4;i ){ putchar(a[i]); }
cin和cout
这种输入输出不推荐,因为数据量大的时候性能很差,只用string考虑类型。
必须添加以下代码:
#inclue<iostream>
using namespace std;
cin可以读入不同类型:
int n;
double db;
char str[10];
cin >> n >> db >> str;
cout用法类似:
cout << n << db << " " << "hhh" << str << endl;
如果输出要限制double精度的话还是别用cout了,太麻烦。
cin.getline()
因为《算法笔记》上的get()方法已经不被PAT编译器支持,为避免需要读入一行字符串时(含空格)一脸懵逼,暂时用cin.getline()代替get()的功能!使用前记得要增加如下两句:
#include<iostream>
using namespace std;
cin.getline()实际上有三个参数:cin.getline(字符串名称,接受长度,结束字符)。第三个参数无特殊时可以省略。注意每次实际读取时接受的字符数会比第二个参数少1,自动在最后加一个‘\0’终止符。例如:有如下代码,输入“12345”,输出的会是“1234”。
cin.getline(b,5);
printf(b);
cin.getline()用来输入一行字符串,即以“\n”结束输入,因此注意如果之前有剩余的“\n”,需要先将其处理掉再gets,例如有以下输入:
i'm
a bad boy!
用scanf()和cin.getline(),希望能原样输出,能正常跑通的代码如下:
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
char a[5],b[20];
scanf("%s",a);
getchar(); \\必须把i'm后的\n先读了
cin.getline(b,20);
printf("用printf输出a:\n%s\n",a);
printf("用printf输出b:\n%s\n",b);
}
常用math函数
#include <math.h>
- fabs(double x) :用于对double值求绝对值,返回double型。
- floor(double x):对double型变量向下取整,ceil(double x)对double型变量向上取整,两者都返回double型。
- pow(double a,double b):返回a的b次方。
- sqrl(double b):返回double的算术平方根。
- log(double x):返回以自然对数为底数的对数。如果要以a为底数,只能lnx/lna。
- sin(double a),cos(double b),tan(double c):参数需为弧度值,有对应反函数asin(double x),acos(double y),atan(double z)。
- round(double x):对x四舍五入,返回double(小数点后为0),要想不输出末尾的0则(int )result。
选择结构
if语句
(注意不是elif是else if就可以了)
if (){
...
}
else if() {
...
}
else{
...
}
switch
switch (a+b){
case 1:
...
break;
case 2:
...
break;
default:
...
}
循环结构
- while
- do{}while();
- for(;;)
注意break用于退出循环,continue用于结束循环的当前轮回。
数组
基础数组
数组是由地址上连续的若干个相同类型的数据组合而成(记住这个“连续”,后面有用)。
//一维数组:数组类型 数组名[数组大小]
int a[3] = {0,1,2}
//二维数组:数组类型 数组名[第一维大小][第二维大小]
int a[2][3] = {
{0,1,2},{20,21,22}}
如果数组的部分没有被赋初值的话将会被编译器赋以初值,虽然说一般情况下默认为0,但编译器不同初值也可能不同,在考试的时候如果需要,请检查一下未赋值的数组项是否都为0。
另外注意二维数组初始化时,需要按第一维的顺序依次用大括号给出第二维初始化情况,然后用逗号分隔,并用大括号全部括住。每个大括号里的初始化方式和一维数组一样,如果不初始化某一行(默认为0)也要放一个空括号。
int a[4][3] = {
{0,1,2},{},{1,3},{3}}
特别注意:如果数组很大(大概10^6级别),需要将其定义在主函数外面。
memset函数
用于给数组中每个元素赋予相同的0或者-1,需要在开头添加string.h头文件。特别注意memset的第3个参数必须是sizeof(数组名),不是数组长度,例如在下面的代码中第三个参数不能是5。
#include<string.h>
int a[5];
//memset(数组名,值,sizeof(数组名))
memset(a,0,sizeof(a));
string.h头文件
包含了用于字符数组的函数:
- strlen(str):得到字符串中第一个\0前的字符的个数。
- strcpy(str1,str2):把str2整个复制给str1。
- strcat(str1,str2):把str2接到str1的后面。
字符数组
字符数组可以像普通数组一样初始化,也可以直接赋值字符串来初始化。
#include <stdio.h>
int main()
{
char a[5]="hey!";
printf(a);
}
注意直接赋值字符串仅限于初始化,不能在程序别的位置这样用。
注意每个一维字符数组的末尾都有一个空字符‘\0’,以表示存放字符串的结尾,在scanf()和cin.getline()输入字符串时会自动加在输入的字符串后面,并会占用一个字符位,这样printf()才知道在哪里结束输出,因此用来存char型的数组必须要多留一个位置(只有字符串数组需要)。
并且,如果是用getchar()逐个读入却要用printf()一起输出,则必须自己在输入的末尾加上这个‘\0’,否则printf会按照字符数组定义的长度全部输出,即使后面的位置没有被赋值(输出乱码)。
#include <stdio.h>
int main()
{
char str[10];
for(int i=0;i<6;i++){
str[i]=getchar();
}
str[6]='\0';
printf("%s",str);
}
#include <stdio.h>
int main()
{
char str1[10]="123",str2[10];
int n;
//把str1的内容读到n里,把第一个参数当做键盘
sscanf(str1,"%d",&n);
//把n的内容打印到str2里,把第一个参数当做显示器
sprintf(str2,"%d",n);
printf("%s",str2);
}
可做一些更复杂的操作,比如:
#include <stdio.h>
int main()
{
char str2[100];
int n = 100;
sprintf(str2,"在%dmin内得%d分",n,n);
}
函数
注意:main函数必须返回int类型,返回0意思是正常终止。
数组参数
参数中数组的第一个维度不需要写长度,调用时只需要写数组名。最重要的是,在函数中对数组元素的修改等同于对原数组的修改。并且数组不能作为返回值(不过都已经修改在原数组上了也没有必要做返回值吧)。
void change(int a[], int b[][5]){
a[0]=1;
b[0][0]=2;
}
指针
指针变量
注意下面的代码中只有p1被定义成了指针。
int* p1,p2;
建议还是把‘*’放变量前面吧。
初始化指针
int *p = &a; //a是变量,相当于把a的地址存到p里。
//上面一句等同于:
int *p;
p = &a;
如果被‘*’搞晕了,就想想‘*’是类型的一部分(表示这是一个指针变量),p才是变量名,赋值时自然是赋给变量。
'*'的另一个作用相当于启动p中的传送法阵,传送到p里面存储了地址的地点,*p就相当于a。
printf("%d",*p);//接上例,打印出a的值
可以对指针进行加减、自增、自减,这里加减的数值代表地址偏移的距离。
指针数组
数组名称也作为数组的首地址使用,可以将其考虑成一个指针(存了数组的首地址),例如有数组a[len],就可以用*(a+i)来表示a[i](别漏了‘*’)。
指针变量做参数
每次看这块都觉得懂,但经常写代码的时候都觉得懵,这里想了个技巧,看能不能好理解一点(这里写的很详细了,只要自己写代码的时候不晕就行,倒也不用每次把故事讲那么清楚哈哈)。
首先理解一下变量,如下代码。这个1是房间里的存的东西,这个房间有自己的房间号(地址),而变量名相当于房间名(有的房间名叫“三味书屋”,有的房间名叫“金色大厅”,这里的房间名就叫“a”),我们对a的操作其实都是在a房间里对里面的东西进行操作。赋值过程其实是把a房间里的内容复制一个一样的放到x房间(x本来有东西的话扔掉)。
int a=1;
int x=a;
然后理解一下指针,前面有提到,指针相当于在名字叫p的房间里放了a房间的地址(理解成传送阵吧),如果想要的话可以用“*”启动该传送阵直接去到a房间(p就是a的地址,*p和a是一个房间)。注意在定义*p时就相当于规定了这是一个新的专门放法阵的房间。
int *p;
p=&a;
再来理解一下函数,(如下代码)函数func是main函数的小迷弟,一般的传参(除了引用)相当于main把自己的某些房间给小迷弟参观一下。函数func参观了a房间,感叹:“学到了!”。于是自己立刻整了一个跟a长得一模一样的房间取名叫b(叫啥都可以,叫a也行),里面放的东西也一模一样。不过b终究是小迷弟仿制的,不管造好之后对b里的东西做什么,都跟大哥a房间里的东西没啥关系。
void func(int b){
printf("%d",b);
}
int main(){
int a=1;
int *p=&a;
func(a);
}
但如果这是一个有野心的小弟,就会对参数提出要求,如下代码中func要求得到一个存了地址的参数,即指针参数(func跟大哥说:“我要参观你放了传送阵的房间!”)。
void func(int* b){
...
}
这时,main就得给他传一个存了地址的变量,也就是指针变量:
int main(){
int a=1;
int *p=&a;
func(p);
}
鉴于小迷弟已经膨胀,他会依旧仿造一个和p一模一样的房间,然后画一个一模一样的传送阵放在里面。这样小迷弟就得到了a房间的传送阵,只要他想,可以随时通过这个传送阵去到a房间,然后就能对大哥的a房间里的东西酱酱酿酿……
void func(int* b){
(*b)++; // a房间的内容被更改了
}
现在来理解《算法笔记》里强调的可能出现的两钟错误:
//错误1
void func2(int *b){
int *temp;
*temp = *b;
}
这样的代码很可能出问题的原因是:没有初始化temp时,temp存放的地址是随机的,可能指向很重要的地址。相当于小迷弟仿造了房间和传送阵后过于兴奋,马上去老大的a房间复制了一份玻璃珠(假设a房间里放的是博主小时候玩过的玻璃珠),又自己新做了一个房间,跑进去随便开启了一个传送阵,也不管被传送到哪里,一到地方直接把原房间清干净,把复制的玻璃珠放进去。但他不知道的是,原来这个房间里放着梅西告别巴萨的发布会上用来擦眼泪的纸($1,000,000)......如果先初始化temp,比如在里面画一个空房间的传送阵(如下代码),就不会出现上述悲剧。
void func2(int *b){
int x;
int *temp = &x;
*temp = *b;
}
//错误2,以下操作本意是要交换a,c的值
void swap(int *a, int *c){
int *temp = a;
a = c;
c = temp;
}
int main(){
int a=1,c=2;
int *p1=&a,*p2=&c;
swap(p1,p2);
return 0;
}
这里main大哥把画着a、c传送阵的两个房间都给小弟swap看,派swap去把这两个房间的法阵交换一下。小弟自己仿照p1建了个房间叫小a,里面画着大哥的a房间的传送阵,又仿造p2房间建了个房间叫小c,里面画着大哥的c房间的传送阵,然后小弟把小a房间里的传送阵又画在了temp房间里,再把小c房间里的传送阵画在小a房间里,最后把temp房间里的传送阵画到小c房间。现在一看就能知道,小弟swap自以为的交换全程甚至没有真正去过大哥的p1和p2房间,净搁自个儿家里画过去画过来了,自然无法完成任务。
引用
又一个老大难问题,结合上面的故事继续编,希望能永远不要再被引用搞晕了。
如果函数在要求的参数前面加&,可以理解为租借,直接把大哥的房间租过来搞事情。
void func(int &x){
...
}
指针的引用首先别被这个写法搞晕,‘*’依然当做类型的一部分;&理解成租借这一行为,不影响传入的类型;如下就依然是要求得到一个存放了地址的int指针类型。
void func(int *&x){
...
}
上面指针变量做参数的最后讨论的第二种错误可以通过指针的引用来修改。
#include <stdio.h>
void swap(int *&x, int *&y){
int *temp = x;
x = y;
y = temp;
}
int main(){
int a=1,c=2;
int *p1=&a,*p2=&c;
swap(p1,p2);
return 0;
}
这段代码可以理解成小弟swap租了老大的p1、p2房间,在租的期间他自己给这俩房间起名叫x、y,然后他自己新建一个用来装法阵的房间temp,接下来将x房间里的法阵和y房间里的法阵交换。结果租期满后退还给老大的俩房间里的法阵自然也交换了。
最后注意:C是不支持引用的,一定要确保是C++程序。
结构体(struct)
基本属性
struct studentInfo{
int data;
studentInfo *next; //只能定义同类型指针,不能定义同类型变量
}stu,*p;
通过->来访问结构体内元素。
构造函数
不需要返回类型,且函数名与结构体名相同,参数自己根据情况设,可以有多个参数不同的构造函数。
studentInfo(int name){
this.name = name;
next = NULL;
}