[原文链接] C++冷知识(逐渐更新) – By 种下一颗草莓

1. 输入 输出

4种输入输出方式

  1. scanf 和 printf
  2. cin 和 cout
  3. puts gets
  4. 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++不支持数组整体赋值,可以在声明数组时整体初始化。

  1. 在main函数外面定义数组时,默认值全是0。

  2. 在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函数虽然没有改变引用的指向,但不保证在更复杂的情况下把指向改变了。