[原文链接] C++冷知识(逐渐更新) – By 种下一颗草莓
1. 输入 输出
4种输入输出方式
- scanf 和 printf
- cin 和 cout
- puts gets
- getchar putchar
3 4 都是针对字符的,3针对字符串,4针对单个字符。
1. 单个字符
以方式1 4 输入单个字符时,空格 回车都会吸收,算作一个字符。
scanf("%c",&ch);
ch=getchar();
以方式3输入单个字符时,cin>>ch
回车 空格字符不会被吸收,会被过滤掉。
2. 字符串
以方式1 2读入字符串时,效果是一样的,遇到回车 空格即停,即读入的字符串中不会有回车 和 空格。
scanf("%s",str);
cin>>str;
以方式3读入字符串时,空格也会算作字符的一部分,只有碰到回车才会停止。 gets(str);
同理,在输出时,scanf 和 cout都需要手动换行,puts可以自动换行。
辨析:字符串和字符数组
int main()
{
char ch[20],str[20];
for (int i = 0; i < 10; ++i) cin>>ch[i]; 字符数组
cin>>str; 字符串
for (int i = 0; i < 10; ++i) cout<<ch[i];
cout<<endl;
cout<<str<<endl;
return 0;
}
字符串比字符数组多了 ‘\0’ 标志,且’\0’ 也会占 用户定义的数组位置。 但在求数组长度时,不会考虑 ‘\0’ 。
字符串数组常用输出方式:
1. 逐个遍历
int len=strlen(str); for (int i = 0; i < len; ++i) printf("%c",str[i]);
for (int i = 0; str[i] != '\0'; ++i) cout<<str[i];;
2. 整体输出
cout<<str;
printf("%s",str);
所以定义字符串时,开辟的空间必须要大于要输入字符的个数,最小也要是长度+1才能正常存储。
但是如果定义的数组长度小于等于输入的长度时,会出现错误。
#include <bits/stdc++.h>
using namespace std;
int main()
{
char str[5];
scanf("%s",str);
int len=strlen(str);
cout<<len<<endl;
for (int i = 0; i < len; ++i) printf("%c",str[i]);
cout<<endl;
for (int i = 0; str[i] != '\0'; ++i) cout<<str[i];
cout<<endl;
printf("%s",str);
return 0;
}
len=4<5
len=5=5
len=9>5
SUM 总结
int main()
{
char a[] = "abcdefg";
char *pstr = "abcdefg";
char b[]={'a','b','c','d','e','f','g'};
printf("sizeof(a)=%d,strlen(a)=%d\n",sizeof(a),strlen(a));
printf("sizeof(pstr)=%d,strlen(pstr)=%d\n",sizeof(pstr),strlen(pstr));
printf("sizeof(b)=%d,strlen(b)=%d\n",sizeof(b),strlen(b));
}
打印结构如下所示:
sizeof(a)=8,strlen(a)=7
sizeof(pstr)=4,strlen(pstr)=7
sizeof(b)=7,strlen(b)=19
结果说明:
char a[]中的元素包括”abcdefg”以及一个’\0’,pstr也是这样,因为他们都是一个字符串。所以sizeof(a)=8;
strlen(a)=strlen(pstr)=7是因为strlen计算的是字符串中除结尾标志’\0’外的所有字符;
而strlen(b)=19是因为b不是一个正常“字符串”,字符串必须明确以’\0’结尾,而cha b[]只是一个字符数组,当用strlen计算b的长度时,是将b当作一个字符串来进行计算长度,也就是说要遇到’\0’才认为这个“字符串”结束;而b中未有定义元素’\0’,所以strlen会沿着b结尾的内存地址一直计数下去,直到遇到’\0’。所以用strlen(b),结果是不可预知的。
理解了上一条,sizeof(b)=7也不难理解,b是一个字符数组,里面只有7个元素,再次强调,b中没有’\0’。
最后一条sizeof(pstr)=4,是一个指针的大小,32位机器上指针的大小=32bit=4byte。所以结果为4。
另外,字符串从下标1读入 读出:
cin>>str+1;
scanf("%s",str+1);
cout<<str+1;
printf("%s\n",str+1);
int main()
{
char str[20];
//cin>>str+1;
scanf("%s",str+1);
int len=strlen(str);
cout<<len<<endl;
for (int i = 0; str[i] != '\0'; ++i) cout<<str[i];
cout<<endl;
for (int i = 1; str[i] != '\0'; ++i) cout<<str[i];
cout<<endl;
printf("%s\n",str+1);
cout<<str+1;
return 0;
}
另外,字符数组和字符串都可以和string类型进行转换。
string s(str);
string ss; ss=str+1;
当下标为1的字符串进行转换时:改变起始地址位置,一直到数组结束。
string s(str+1);
string ss; ss=str+1;
string类型也可以拼接字符串和字符数组。
int main()
{
char ch[]={'c','h','i','n','a'};
char str[20];
cin>>str;
string s="c";
s+="aabb";
s+=str;
s+=ch;
cout<<s;
return 0;
}
2. 初始化函数memset
按字节初始化,一个字节8位。适合初始化0,-1,0x3f3f3f3f
0的补码 全0; 1的补码全1;0x3f3f3f3f的每个字节都是0x3f
假设有一个数组:int a[N];
memset(a,0,sizeof(a));
memset(a,-1,sizeof(a));
memset(a,0x3f,sizeof(a));
现在的计算机中,一个int类型大概都是32位,就是4个字节。
对于有符号数而言,最高位表示符号位,因此,最大就是0x7f ff ff ff,是232-1,大概是3e9。
但是一般无穷大都愿意取0x3f 3f 3f 3f,大概是1e9多,因为方便初始化。
0x7fffffff溢出后就是0x80000000,是 -232
同理,对于无符号数,最大就是x0ff ff ff ff。 32位全是1。溢出后就是0。
long long 64位。
0x3f3f3f3f,每一个字节都是0x3f,即0011 1111
0x7f也可以,每个字节都是0111 1111,不过一般都倾向于0x3f,已经够大了,还不容易溢出。
此外,如果定义的是有符号数的话,可以初始化为0xcfcfcfcf,每一位都是0xcf,1100 1111 ,
还可以是0xaf,0xef。
定义int类型的话,
初始化0xaf,值为-1347440721,小于-1e9
初始化0xcf,值为-808464433,略大于-1e9
初始化0xef,值为-269488145,-2e8差不多。
数组初始化
int a[N];
`memset(a,0x3f,sizeof(a)); `
单个变量初始化
int t;
memset(&t,0x3f,sizeof t);
3. 数组初始化
C/C++不支持数组整体赋值,可以在声明数组时整体初始化。
- 在main函数外面定义数组时,默认值全是0。
-
在main函数中定义,无论数组有多大,全部初始化为0的操作很简单,如int a[3000]={0};就可以将a的3000个元素全部置0;若要赋其他值,例如全部赋值为7,写成int a[3000]={7};则不行,这只给a[0]赋值为7,其余的都是0。
4. 多组输入
当非法输入时停止
C语言中:使用scanf: while(~scanf("%d%d",&n,&m))
C++中使用cin 可以直接写:while(cin>>n>>m)
当n和m均为0时,则停止:
C中:while (~scanf("%d%d",&n,&m),n||m)
C++中:while (cin>>n>>m,n||m)
5. 结构体赋值
edges[1]={1,2,3};
6. 链式前向星
一个数组模拟多条链表,用于哈希拉链法、邻接表存图。
int h[N],e[M],ne[M],idx;
h[N] 可以看做头结点,每个顶点后面都连一个链表,存储与它相连的顶点。
由于是数组模拟,idx可以类比链表存储时的指针,把整个数组想象成内存空间,idx相当于指针。
e[M] ne[M] 看作是一个结构体中的两个元素,存储的是顶点信息:e[ ]存储与头结点对应下标有边相连的下一个顶点是几号,ne[ ]存储下一个相连顶点的位置,ne的作用可类比p\to next 。与头结点下标的顶点相连的顶点可能有多个,都存在一条单链表上。
由h[N]头结点先指向一个位置,ne[ ]数组继续往下遍历,直到遇到-1 结束,e数组获取该顶点。
struct Edge
{
int edge,next;
}edges[M];
N个顶点,M条边
e即edge,ne即next缩写
7. 宏定义
#define和typedenf要区分开:typedef long long ll;
#define read(x) scanf("%d",&x)
#define read(x,y) scanf("%d%d",&x,&y)
替换scanf输入,比较快。
#define fir(i,a,b) for(int i=a;i<=b;i++)
8. 二进制拆分
假设有一个数n,将其拆成8位二进制数:
int n=0xff;
for (int i=7;i>=0;i--) {
int t=n&(1<<i);
cout<<t<<" ";
}
输出:
128 64 32 16 8 4 2 1
int n=0xff;
for (int i=7;i>=0;i--) {
int t=n>>i&1;
cout<<t<<" ";
}
输出:
1 1 1 1 1 1 1 1
9. 求组合数
递推: C(a,b)=C(a-1,b-1)+C(a-1,b)
初始化:
C(0,0)=1; C(0,k)=0;(k>0)
C(k,0)=1;
memset(f,0,sizeof f);
for (int i=0;i<=n;i++) {
f[i][0]=1;
for (int j=1;j<=i;j++)
f[i][j]=f[i-1][j-1]+f[i-1][j];
}
10. 字符数组输入
回车 空格都有ASCII码,因此%c 字符输入会吸收空格、回车。
#include <iostream>
#include <cstring>
#define read(x) scanf("%c",&x)
using namespace std;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
char g[10][10];
getchar();
for (int i=0;i<n;i++) {
for (int j=0;j<m;j++) read(g[i][j]);
getchar();
}
for (int i=0;i<n;i++) {
for (int j=0;j<m;j++) printf("%c",g[i][j]);
puts("");
}
return 0;
}
读入字符数组,然后变字符串。
for (int i=0;i<n;i++) {
for (int j=0;j<m;j++) read(g[i][j]);
getchar(); g[i][m]='\0';
}
for (int i=0;i<n;i++) puts(g[i]);
11. 优先队列自定义排序
priority_queue
默认是大根堆,从大到小排序。
pority_queue< int,vector<int>,greater<int> > q;
小根堆,从小到大排序,必须这么写。
pority_queue< int,vector<int>,less<int> > q;
大根堆,从大到小排序,可以简写成priority_queue<int> q;
greater函数,小根堆排序的实现:
struct cmp{
bool operator()(int x,int y)
{
return x>y; "大于号时,从小到大排序"
}
};
priority_queue<int,vector<int>,cmp>q;
当堆里的元素是结构体时,只能写成:
priority_queue<node> q;
全写形式就不能写了,自定义排序的实现。
struct node
{
int x,y;
};
bool operator<(const node& a,const node& b) {
if(a.x!=b.x) return a.x>b.x; //先按x升序排列
else return a.y>b.y; //如果x一样,按y升序排列
}
或者把operator函数放在结构体里面,多了一个friend修饰词。
struct node
{
int x,y;
friend bool operator<(const node& a,const node& b) {
if(a.x!=b.x) return a.x>b.x; //先按x升序排列
else return a.y>b.y; //如果x一样,按y升序排列
}
};
12. pair函数自定义排序
typedef pair<int,int> PII;
PII a[100];
sort(a,a+4);
默认按第一个关键字升序排列,如果第一个关键字相同,按第二个关键字升序排列。
自定义cmp函数,按认按第一个关键字降序排列,如果第一个关键字相同,按第二个关键字降序排列。
bool cmp(PII a,PII b)
{
if (a.first!=b.first) return a.first>b.first;
else return a.second>b.second;
}
sort(a,a+4,cmp);
按第一个关键字升序排列也可以写成sort(a,a+4,less());
按第一个关键字降序排列也可以写成sort(a,a+4,greater());
如果是按第二个关键字排序,就不得不写cmp函数了。
13. 结构体sort排序
struct node
{
int x,y;
}a[100];
bool cmp(node a,node b) {
if (a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
sort(a,a+4,cmp);
或者写成:
bool operator < (const node& a,const node& b) {
if (a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
sort(a,a+4); "此时只用这样写。"
这是排序函数写在外面的情况,而且第二种方法下只能有1中排序方法,第一种方法可以根据cmp1、cmp2有多种排序方法。
排序函数写在里面:
struct node
{
int x,y;
bool operator < (const node& a) const {
if (x!=a.x) return x<a.x;
else return y<a.y; //升序排列
}
}a[100];
const声明的是常量,由于传递过来的是引用(与c中的指针相似),而我们不希望这个引用的指向在函数中发生改变,因为在main函数中可能还会用到,所以为了保险起见选用const声明更为合适。本例中的cmp函数虽然没有改变引用的指向,但不保证在更复杂的情况下把指向改变了。