参考书籍:《算法笔记》前两章
变量类型:
-
整型:109以内或者说32位整数,用int来存放;1018以内或者说用64位整数,用
long long
来存放,赋初值时要加上LL; -
浮点型:用double不要使用float。double输出格式:
%f
。%.1lf
指保留一位小数输出; -
字符型:ADCII码:0~127。A:65,a:97。字符常量(必须是单个字符),必须用单引号标注。char类型输出格式:
%c
;\n
:代表换行\0
:代表空字符NULL,ASCII码为0;字符串型:string(c++),只能使用字符数组的方式。字符串常量是双引号标记的字符集,输出格式:
%s
;不能把字符串常量赋值给字符变量 char c = "abc"是错误的。
-
布尔型:整型常量在赋值给布尔型变量会自动转换为True(非零)或者False(零)。-1也会转换为True。
符号常量和const常量:
# define pi 3.14(结尾不加分号)
const double pi = 3.14;
define除了可以定义常量,还可以定义任何语句或片段。如# define ADD(a, b) ((a)+(b))
运算符:
/:两个int相除不会得到double,而是会向下取整。
++i和i++:i++是先使用i再将i加1,++i是先将i加1然后再使用i。
条件运算符:是c语言中唯一的三目运算符。 a?b:c
如果a为真,就执行并返回b的结果,如果a为假,就执行并返回c的结果。
位运算符:由于int的上限是231-1,因此常常把无穷大的数INF设置为230-1(1<<30)-1,可以避免两个数相加超过int的情况。 注意:必须加括号,因为位运算符优先级没有算术运算符高。
const int INF = (1 << 30) - 1;
const int INF = 0x3fffffff;
六种位运算符:
运算符 | 含义 | 语法 | 效果 |
---|---|---|---|
<< | 左移 | a<<x | 整数a按照二进制左移x位 |
>> | 右移 | a>>x | 整数a按照二进制右移x位 |
& | 位与 | a&b | 整数a和b按二进制对齐,按位进行与运算 |
| | 位或 | a|b | 整数a和b按二进制对齐,按位进行或运算 |
^ | 位异或 | a^b | 整数a和b按二进制对齐,按位进行异或运算 |
~ | 位取反 | ~a | 整数a的二进制的每一位进行0变1、1变0的操作 |
scanf输入:
输入字符串(char数组)时:scanf("%s", str);
因为数组比较特殊,数组名称本身就代表了这个数组第一个元素的地址,就不需要加上&。
scanf可以按照格式输入:例如scanf("%d:%d:%d",&a, &b, &c);
scanf读入3 4:%d%d
,因为除了%c外,对其他格式符的输入是以空白符(空格、tab)作为结束标志,即除了%c会把空格当字符读入,其他情况会自动跳过空格。
字符数组使用%s读入的时候以空格和换行作为结束的标志,%c可以读入空格和换行。
printf输出:
对于double类型,输出格式%f
,在scanf中是%lf
。其他类型的格式符与scanf中相同。
三种实用的输出格式:
-
%md
%md
可以使不足m位的int型变量以m位进行右对齐输出,其中高位用空格补齐,本身超过m位就保持原样。int a = 123; printf("%5d\n", a);
-
%0md
当变量不足m位时用0补齐。(在某些题中非常实用)
-
%.mf
可以让浮点数保留m位小数输出。(适用于题目要求浮点数输出保留XX位小数,或精确到小数点后XX位)。如果要求四舍五入,需要用到
round
函数。
getchar输入、putchar输出:
getchar
用来输入单个字符,在某些scanf使用不便的场合使用,putchar
用来输出单个字符。getchar可以存储换行符\n。
typedef:
能给复杂的数据类型起一个别名,在使用中就可以用别名来代替原来的写法。
typedef long long LL;
LL a = 123456789012345;
常用math函数(需要math.h头文件):
fabs(double x)
:double变量取绝对值;floor(double x)、ceil(double x)
: double型向下取整和向上取整,返回类型为double型;pow(double r, double p)
:返回r的p次方,结果也为double型;sqrt(double x)
:返回double类型的算术平方根;log(double x)
:返回double类型的以自然对数为底的对数,要求对任意底数求对数需要用到换底公式;round(double x)
:将double型变量x四舍五入,返回类型也是double类型,若要结果int输出时应当强制类型转换;
break和continue语句:
break
:退出整个循环
continue
:结束当前循环,进入下一次循环
数组:
一维数组:
注意:如果数组大小较大(大概106),需要定义在主函数外,否者会使程序异常退出。
数组就是从某个地址开始的连续若干个位置形成的元素集合。
数组大小必须是整数常量,不可以是变量。未被赋值的元素一般会默认初值为0。
int a[10] = {1, 3, 4, 5, 3, 12, 5, 2, 8, 9};
char str[1000];
//给整个数组赋初值0
a[10] = {0};
a[10] = {};
冒泡排序:
现给一个序列a,其中元素的个数为n,要求按照从小到大的顺序排序。
比较a[j]和a[j+1],如果a[j]大,就交换。整个算法过程共有n-1躺,每趟都从a[0]比较到a[n-1],每趟都选出一个最大的数放到他的最终位置。
冒泡排序的本质是交换,即每次通过交换的方式把当前剩余元素的最大值移到一段,而当剩余元素减少为0时排序结束。
特征: 外层循环每趟使得从右到左逐渐有序
以从小到大排序为例,
第一趟找到最大的数放在最右边。这就是它的最终位置
第二趟又找到当前最大的数放到当前的最右边。现在已经确定了最右边两个数的最终位置
……
第n-1趟就完成了排序
代码实现:
#include <iostream>
using namespace std;
int main(){
int a[5] = {3, 4, 1, 5, 2};
for(int i = 0; i < 5; i ++ ){
for(int j = 0; j < 5 - i; j ++ ){
if(a[j] > a[j + 1]){
swap(a[j], a[j+1])
}
}
}
for(int i = 0; i < 5; i ++ ) printf("%d ", a[i]); //注意不要加&
return 0;
}
二维数组:
int a[5][6] = {{3, 1, 2}, {3,4}, {}, {1, 2, 3, 4, 5}};
未赋值元素默认都是0
结果如下:
3 1 2 0 0 0
8 4 0 0 0 0
0 0 0 0 0 0
1 2 3 4 0 0
0 0 0 0 0 0
可以把二维数组当做一维数组的每个元素都是一个一维数组。
补充: 如果数组比较大(大概106级别),需要定义在主函数外面
memset-对数组中的每一个元素赋相同的值:
给数组中的每一个元素赋相同的值:memset
函数(需要cstring
头文件)和fill
函数(需要algorithm
头文件)。
建议使用memset函数只赋值0和-1,因为memset使用按字节赋值。
示例:
int a[5];
memset(a, -1, sizeof(a));
字符数组:
1. 字符数组初始化:
char str[5] = {'h', 'e', 'l', 'l', 'o'};
char str[5] = "hello"; //仅限于初始化,其他地方不允许这么赋值整个字符串
注意:通过赋值字符串来初始化仅限于初始化,其他地方不允许直接用整个字符串来赋值!!!
2. 字符数组的输入输出:
scanf
对于字符类型有%c
和%s
两种格式(printf
同理),%c
用来输出单个字符,%s
用来输入一个字符串并存在字符数组里。
%c
格式能够识别空格和换行并将其输入,而%s
通过空格和换行来识别一个字符串的结束。
char str[110];
scanf("%s", str);
scanf("%s", str);
//输入hello world
//输出hello, 因为%s遇到空格结束输入,因此world没有读进来
gets输入、puts输出:
gets
用来输入一行字符串(gets识别\n作为输入结束,因此scanf完一个整数后如果要使用gets,需要先用getchar接收整数后的换行符),并存放到一维数组中。
puts
用来输出一行字符串,即将一维数组在界面上输出,并紧跟一个换行。
3. 字符数组存放方式
字符数组是有若干个char类型元素组成,字符数组的末端都是一个空字符\0以表示存放的字符串的结尾。 puts和printf就是通过识别 \0 来进行输出的。
注意:
- int数组没有\0,只有char型数组才有,结束符\0的ASCII码为0
- 如果不是使用
scanf
的%s
格式或者gets
函数输入字符串(例如使用getchar
),一定要在输入的每个字符串的后面加上\0
,否则printf
和puts
输出时会出现乱码。
cstring头文件:
strlen(字符数组)
:可以得到字符数组中第一个 \0 前面的字符的个数strcmp(字符数组1, 字符数组2)
:返回两个字符串大小(按照字典序)的比较结果。数组1大就返回一个正整数,数组1小就返回一个负整数,相等返回0。strcpy(字符数组1, 字符数组2)
:把字符数组2复制给字符数组1(字符数组2在前面),包括结束符\0,在%s输出的时候\0后面的内容不会输出。strcat(字符数组1,字符数组2)
:把一个字符数组2接到字符数组1的后面。
获取字符串长度的几个函数:
C/C++中 strlen(str)
、str.length()
、str.size()
、sizeof(str)
都可以求字符串长度。
- str.length()、str.size()、sizeof(str)是
用于求string类对象
的成员函数 - strlen(str)是用于求字符数组的长度,其参数是
char*
sscanf和sprintf:
两者均在cstdio
头文件下。sscanf
可以理解为string + scanf
char str[100];
scanf(screen, "%d", &n); //可以理解为把屏幕中的数放到n,从左到右
printf(screen, "%d", n ); //同理,从右到左
sscanf(str, "%d", &n); //把字符数组str中的内容以"%d"的格式写到n中,从左到右
sprintf(str, "%d", n); //同理,从右到左
使用sscanf
将字符数组中的内容按照格式写入变量:
#include <cstdio>
int main(){
int n;
double db;
char str[100] = "2048:3.14,hello", str2[100];
sscanf(str, "%d:%lf,%s", &n, &db, str2);
printf("n = %d, db = %.2f, str2 = %s\n", n, db, str2);
return 0;
}
//输出结果:n = 2048, db = 3.14, str2 = hello
使用sprintf
将变量按照格式写入字符数组:
#include <cstdio>
int main(){
int n = 12;
double db = 3.1415;
char str2[100] = "good", str[100];
sprintf(str, "%d %.2f:%s", n, db, str2);
printf("%s\n", str);
return 0;
}
//输出结果:str = 12:3.14,good
函数:
全局变量: 定义在函数外面的变量,在所有程序段内都有效
局部变量: 定义在函数内部,只在函数内部生效,函数结束后局部变量销毁
以数组作为函数参数: 数组作为参数时,第一维不需要写长度,如果是二维数组第二维需要写长度
void test(int a[], int b[][5]){
}
注意: 数组作为参数时,函数中对数组元素的修改能实现对原数组元素的修改,这和普通的局部变量不同
指针:
在c语言中用“指针”来表示内存地址(或者称指针指向了内存地址),若这个内存地址恰好是某个变量的地址,称这个指针指向该变量。
在变量前加上取地址符&
,就表示变量的地址。指针是一个unsigned
类型的整数。
指针变量:
指针变量用来存放指针(或者可以理解为地址)
给指针变量赋值的方式一般是把变量的地址取出来,然后赋给对应类型的指针变量。
int* p = &a;
int*
是指针变量的类型,p
才是用来存储地址的变量名,因此&a
是赋给p
而不是*p
的。对于一个int*
型指针变量p
来说,p+i
是指p所指的int型变量的下i
个int型变量(跨越4iByte)。
指针变量支持自增和自减操作,p++
等同于p = p + 1
。
怎么理解呢?
如果a表示一个房间(地址)里的东西(值),
p相当于房间号(地址),*相当于房间的钥匙
然后 *p 就可以打开房间,获得a的值。
对于指针变量来说,把其存储的地址的类型称为基类型。基类型必须和指针变量存储的地址类型相同。
数组a的首地址为&a[0]
,在c语言中,数组名称也作为数组的首地址使用,有a == &a[0]
成立。(a + i ==&a[i]
)
指针与数组:
数组是由地址上连续的若干个相同类型的数据组合而成的,因此a[0]的地址&a[0]也就是整个数组的首地址。
C语言中,数组名可以作为数组首地址来使用。
两个int型的指针相减,等价于求这两个指针之间相差了几个int。
使用指针变量作为函数参数:
指针类型可以作为函数参数的类型,此时视为把变量的地址传入函数。如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地改变。
使用指针作为参数,交换两个数:
#include <cstdio>
void swap(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
int main(){
int a = 1, b = 2;
int *p1 = &a, *p2 = &b;
swap(p1, p2);
printf("a = %d\nb = %d", a, b);
return 0;
}
引用:
函数的参数是局部变量,对局部变量的修改不会影响到外部的变量。
如果需要对外部变量进行修改,可以用指针。
那如果想不使用指针也能修改传入的参数,那就使用引用。(引用不产生副本,而是给原变量起了个别名),对引用变量的操作就是对原变量的操作。
使用引用,只需要在函数的参数类型后面加上个&就可以了。
#include <cstdio>
void swap(int &x){
x = 1;
}
int main(){
int a = 100;
swap(a);
printf("a = %d", a);
return 0;
}
注意: 由于引用是产生变量的别名,因此常量不可使用引用。
cin和cout:
cin输入不指定格式,也不需要加取地址符&。
而如果想要读入一整行,则需要使用getline
函数。
把一整行读入char型数组str[100]中:
char str[100];
cin.getline(str, 100);
string容器则需要用下面的方式输入:
string str;
getline(cin, str);
补充:
浮点数比较:
浮点数在计算机中的存储总是不精确的,因此需要一个很小的数eps来进行修正。一般为10-8:const int eps = 1e-8;
。
圆周率π:由cos(π) = -1可知π = arccos(-1)。因此可以定义
const double Pi = acos(-1.0);
遇到的库函数:
1.在遇到输出图形的题目时
string row = string(n, a); //row由n个a组成,n整型a字符型
2.gets()方法已经不被PAT编译器支持,采用cin.getline来读取一行字符串。
cin.getline(s,50,'\n'); //s需要是char *或unsigned char *,50表示读入大小(最大),'\n'表示分隔符,可以省略
getline(cin,s); //s需要是string,需要引入string头文件
cin.getline适合知道读入大小,固定char数组,速度快
getline(cin,s)不需要指定大小,但速度慢、生成的文件大,适合偷懒用