数据类型选择

  1. 整型int取值范围:$-2^{31} \sim +(2^{31}-1)$,题目要求绝对值在$10^9$以内 或 32位整数,则用int型表示

  2. 长整型long long取值范围:$-2^{63} \sim +(2^{63}-1)$,题目要求绝对值在$10^{18}$以内 或 64位整数,则用long long型表示

    注:如果 long long 型赋 $> 2^{31}-1$的初值,需要在初值后加上LL,否则编译错误。如long long num = 123456789012345LL;

  3. 浮点型数据一律用double存储,而不用float(精度低,6~7位)

  4. 字符常量使用**ASCII码(范围0~127)**统一编码。

    • 0~9 的ASCII码值:48~57
    • AZ 的ASCII码值:6590
    • az 的ASCII码值:97122 (比大写字母的ASCII码值大32)
  5. 强制类型转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <cstdio>

    int main() {
    double r = 12.56;
    int a = 3, b = 5;
    printf("%d\n", (int)r); //输出12
    printf("%.1f", (double)a / (double)b);//输出0.6
    return 0;
    }

输入输出

C语言 C++ 比较
#include <cstdio>
scanf函数
printf函数
#include <iostream>
using std::cin;
using std::cout;
cin
cout
cin 和 cout 无需指定输入输出格式
消耗的时间比 scanf 和 printf 多得多
推荐使用C语言的 scanf 和 printf
只有在十分必要的时候使用 cin 和 cout

输入和输出格式

类型 输入 输出
long long scanf("%lld", &n); printf("%lld", n);
double scanf("%lf", &db); printf("%f", db);
字符串 scanf("%s", str);
(无需 取地址符&)
printf("%s", str);
char c1 = getchar();
能读入换行符\n
putchar(c1);

实用的输出格式

  1. %md

    使不足m位的int型变量以m位进行右对齐输出,高位用空格补齐;若变量本身超过m位,则保持原样。

  2. %0md

    使不足m位的int型变量以m位进行右对齐输出,高位用0补齐;若变量本身超过m位,则保持原样。

  3. %.mf

    让浮点数保留m位小数(四舍六入五成双)输出。如果要四舍五入,需要用到round函数

    四舍六入五成双:

    • 5后有数时:舍5入1
    • 5后无数时:
      • 5前为奇数,舍5入1
      • 5前为偶数,舍5不进(0是偶数)

C++标准库头文件

C++标准库 包含内容
<iostream> C++标准输入和输出函数 的函数原型
<cstdio> C风格标准输入和输出函数 的函数原型
<iomanip> 格式化数据流的流操纵符 的函数原型
<cmath> 数学库函数 的函数原型
<ctime> 处理时间和日期的函数 的函数原型
<cstdlib> 数与文本的转换、内存分配、随机数及其他各种工具函数 的函数原型
<random> 产生非确定性的随机数 的功能库
<cctype> 测试 字符特定属性(如是否为数字、标点符号等)函数 的函数原型
转换 大小写字母的函数 的函数原型
<iterator> 访问 C++标准库容器中数据 的类
<algorithm> 操作 C++标准库容器中数据 的函数
<string> C++标准库的 string类 的定义
<cstring> C风格字符串处理函数 的函数原型

<cmath>

  1. double fabs(double x)

    浮点型取绝对值

  2. double floor(double x)ceil(double x)

    向下取整向上取整

  3. double round(double x)
    针对小数点后一位四舍五入

    另,不使用函数进行四舍五入的方法

    num = num + 0.5

  4. double pow(double r, double p)

    求 r 的 p次方

    $10^n$还有另外的表示方法,如

    • $10^3$ 可写成 1e3
    • $2\times10^6$可写成2e6
    • $3\times10^{-6}$可写成3e-6
  5. double sqrt(double x)

    求 x 的算术平方根

  6. double log(double x)double log10(double x)

    求 x 以 $e$ 为底的对数 和 以10为底的对数

    针对任意底数求对数的函数,必须用换底公式,即$\log_ab = \ln b/\ln a$

  7. double exp(double x)

    指数函数$e^x$

  8. 三角函数

    x为弧度

    • sin(double x)
    • cos(double x)
    • tan(double x)

<ctime>

  1. time_t time(time_t *seconds)

    返回自 格林尼治标准时间的1970年1月1日0时 起 到现在秒数。如果seconds不为空,返回值也存储在seconds


<cstdlib>

  1. int rand()

    rand函数生成 [0, RAND_MAX] 之间的一个整数

    直接由rand函数生成的数的范围常常不能满足具体需要,可以通过比例缩放(scaling)方法得到所需的随机数。例如模拟投掷六面骰子,结果可以用rand() % 6 + 1表示。其中数字6称为比例缩放因子

  2. void srand(unsigned int seed)

    rand函数实际上生成的是 伪随机数。程序每次执行时产生的序列都是重复的,将其调整为每次执行时都产生不同的随机数序列的过程称为随机化,可以通过srand函数实现srand函数接收一个unsigned int值, rand函数使用的随机数发生器 “播种”,从而产生不同的随机数序列。为了在随机化时不用每次都输入“种子”,可以使用如下语句:

    srand(static_cast<unsigned int>(time(NULL) ) );


<cctype>

  1. int isalnum(int c)

    字符是否为数字或字母

  2. int isalpha(int c)

    字符是否是字母

  3. int isdigit(int c)

    字符是否是十进制数字

  4. int islower(int c)

    是否是小写字母

  5. int isupper(int c)

    是否是大写字母

  6. int tolower(int c)

    把大写字母 转换为 小写字母

    不会改变非字母字符的值

  7. int toupper(int c)

    把小写字母 转换为 大写字母

    不会改变非字母字符的值


<algorithm>

  1. max(x, y)min(x, y)

  2. abs(x)x必须是整数,因此更推荐使用<cmath>下的fabs(x)

  3. swap(x, y)

  4. reverse(it1, it2)

    可以将 数组指针在[it1, it2)之间的元素 或 容器的迭代器在[it1, it2)之间的元素 进行反转

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int a[10] = {10, 11, 12, 13, 14, 15};
    reverse(a, a + 4); //将 a[0] ~ a[3]反转
    for(int i = 0; i < 6; i++)
    cout << a[i] << " ";
    //输出结果:13 12 11 10 14 15

    string str = "abcdefghi";
    reverse(str.begin() + 2, str.begin() + 6); //对str[2] ~ str[5]反转
    for(int i = 0; i < str.size(); i++) {
    cout << str[i];
    }
    //输出结果:abfedcghi
  5. next_permutation函数

    给出一个序列在全排列中的下一个序列

    1
    2
    3
    4
    int a[10] = {1, 2, 3};
    do{
    cout << a[0] << a[1] << a[2] << endl;
    } while(next_permutation(a, a + 3)); //a[0]~a[2]之间的序列求解下一个序列
  6. fill函数

    对数组或容器中某一段区间元素赋相同的值。和<cstring>memset不同(基本只用于赋0或-1),可以赋数组类型对应范围中的任意值

    1
    2
    3
    4
    5
    int a[5] = {1, 2, 3, 4, 5}
    fill(a, a + 5, 233); //a[0] ~ a[4] 均赋值为 233
    for(int i = 0; i < 5; i++)
    cout << a[i] << " ";
    //输出结果:233 233 233 233 233
  7. sort函数

    C语言的qsort函数的使用需要运用指针,且写法上没有C++的sort函数简洁。sort函数根据具体情形使用不同的排序方法,效率较高,在实现中规避了经典快速排序中可能出现的会导致实际复杂度退化到$O(n^2)$的极端情况

    因此更推荐使用C++的sort函数进行代码编写。

    sort(首元素地址, 尾元素地址的下一个地址, 比较函数cmp(非必填))

    如果不写比较函数cmp,则默认升序排序。如果想要降序排序,需要编写比较函数cmp:

    1
    2
    3
    bool cmp(int a, int b) { //返回值为true时,a被排在b之前
    return a > b; //降序
    }

    在STL标准容器中,只有vectorstringdeque可以使用sort,因为像setmap这种容器是用红黑树实现的,元素本身有序,故不允许使用sort排序

  8. lower_boundupper_bound

    lower_bound(first, last, val)用来寻找在数组或容器中的[first, last)范围内第一个值$\geq$val的元素的位置

    upper_bound(first, last, val)用来寻找在数组或容器中的[first, last)范围内第一个值>val的元素的位置

    如果是数组,则返回该位置的指针;如果是容器,则返回该位置的迭代器。如果没有找到相应元素,则返回可以插入该元素的位置的指针或迭代器

  9. max_elementmin_element

    max_element(first, last)用来寻找在数组或容器中的[first, last)范围内最大元素的位置

    min_element(first, last, val)用来寻找在数组或容器中的[first, last)范围内最小元素的位置

    如果是数组,则返回该位置的指针;如果是容器,则返回该位置的迭代器


常用容器

<vector>

向量(“变长数组”的叫法更容易理解),长度可以根据需要进行变化,比较节省空间。试题中,有时会碰到只用普通数组会超内存的情况,使用vector会让问题的解决便捷许多;另外,vector还可以用来以邻接表的方式存储图

  1. vector的定义

    vector<typename> name

    如果typename也是一个STL(Standard Template Library)容器,在C++11之前,定义的时候,在符号>>之间要加上空格。如:vector<vector<int> > name;,可以理解为两个维都可变长的二维数组

    而vector数组的定义,如:vector<typename> Arrayname[arraySize];一维长度已固定,注意两者的区别

  2. vector常用函数

    1. push_back(typename x):在 vector 末尾添加一个元素 x

    2. pop_back()删除 vector 的末尾元素

    3. begin():取 vector 的首元素地址

    4. end():取 vector 的 尾后(尾元素地址的下一个)地址(美国人思维比较习惯左闭右开

    5. size():获取 vector 中的元素个数

    6. clear()清空 vector 中所有元素

    7. insert(vector<typename>::iterator it, typename x):向vector的 任意迭代器 it 处 插入一个元素 x

      vi.insert(vi.begin() + 2, -1); //将 -1 插入vi[2]

    8. erase()

      • 删除迭代器为 it 处的元素:erase(it);
      • 删除 [firstIt, lastIt) 内所有元素:erase(firstIt, lastIt);
  3. vector容器内元素的访问

    1. 通过下标访问

    2. 通过迭代器iterator访问

      vector<typename>::iterator it;,这样就得到了迭代器it,可以通过***it来访问vector里的元素**。

      例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #include <iostream>
      #include <vector>
      using namespace std;

      int main() {
      vector<int> vi;
      for(int i = 1; i <= 5; i++)
      vi.push_back(i); //在 vi 的 末尾 添加元素 i
      vector<int>::iterator it = vi.begin(); //vi.begin() 为取 vi 的首元素地址
      for(int i = 0; i < 5; i++)
      cout << *(it + i);
      return 0;
      }

      可以看出vi[i]vi.begin() + i等价。在常用的STL容器中,只有vectorstring中,才允许使用vi.begin() + 1这种 迭代器加上整数 的写法

      迭代器还实现了自增和自减的操作,于是还有一种遍历vector中元素的写法:

      1
      2
      3
      //vector 的迭代器不支持 it < vi.end() 写法
      for(vector<int>::iterator it = vi.begin(); it != vi.end(); it++)
      cout << *it;

<set>

集合,是一个内部自动递增排序 且 不含重复元素的容器。试题中,可能出现需要去掉重复元素的情况,而且有可能因为这些元素 比较大 或者 类型不是int型 而不能直接开散列表,这种情况下就可以用set来保留元素本身而不考虑个数。虽然上述情况也可以通过再开一个数组进行下标和元素对应来解决,但set提供了更为直观的接口,并且可以实现自动排序,可以在做某些题时减少思维量

  1. set的定义

    set<typename> name;

    vector相同,如果typename也是一个STL容器,在C++11之前,定义的时候,在符号>>之间要加上空格。如:set<vector<int> > name;,可以理解为两个维都可变长的二维数组

  2. set常用函数

    1. begin():取 set 的首元素地址

    2. end():取 set 的 尾后(尾元素地址的下一个)地址(美国人思维比较习惯左闭右开

    3. size():获取 set 中的元素个数

    4. clear()清空 set 中所有元素

    5. insert(typename x):将 x 插入 set 容器中,并自动递增排序和去重,时间复杂度为$O(logN)$

    6. find(typename value):返回 set 中对应值为 value 的迭代器

      如:set<int>::iterator it = st.find(2); //在 set 中查找2,返回其迭代器

    7. erase()

      • 删除单个元素

        • 删除迭代器为 it 处的元素:erase(it);

          可以结合find函数使用,如:st.erase(st.find(100))

        • 删除值为 value 的元素:erase(value);

      • 删除 [firstIt, lastIt) 内所有元素:erase(firstIt, lastIt)

  3. set容器内元素的访问

    set只能通过 迭代器iterator 访问,因为只有vectorstring中,才允许使用vi.begin() + 1这种 迭代器加上整数 的写法,因此只能按如下方式枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <iostream>
    #include <set>
    using namespace std;

    int main() {
    set<int> st;
    st.insert(3);
    st.insert(5);
    st.insert(2);
    st.insert(3);
    //set 的迭代器不支持 it < vi.end() 写法
    for(set<int>::iterator it = st.begin(); it != st.end(); it++)
    cout << *it;
    return 0;
    }

    输出为:2 3 5,可以发现,set内的元素自动递增排序,且自动去除了重复元素。

  4. 延伸

    set中元素是唯一的,如果处理元素不唯一的情况,需要使用multiset。C++11 标准中还增加了unordered_set以散列代替set内部的红黑树(一种自平衡二叉查找树)实现,使其可以用来去重但不排序的需求,速度比set快得多。


<string>

  1. string中内容的访问

    1. 通过下标访问

      一般来说,可以直接像字符数组那样去 访问string,如果要读入和输出整个字符串,则**只能用cincout**。那么有办法用printf输出string吗?

      string的函数c_str()可以将string类型转换为字符数组进行输出。示例:

      1
      2
      string str = "abcd";
      printf("%s\n", str.c_str());
    2. 通过迭代器访问

      有些函数比如insert()erase()要求以迭代器为参数进行访问。由于string不像其他STL容器那样需要参数,因此可以直接如下定义:

      1
      2
      3
      string::iterator it;
      for(it = str.begin(); it != str.end(); i++)
      cout << *it;

      stringvector一样,允许使用vi.begin() + 1这种 迭代器加上整数 的写法

  2. string常用函数

    1. operator+=

      string的加法,可以将两个string直接拼接

    2. compare operator

      两个string类型可以直接使用 比较符 比较大小,比较规则是按字典序

    3. begin():取 string 的首元素地址

    4. end():取 string 的 尾后(尾元素地址的下一个)地址(美国人思维比较习惯左闭右开

    5. size()/length():返回 string 的长度

    6. clear():清空 string 中的数据

    7. insert

      • 在 pos 位置插入字符串:insert(int pos, string str)原来位置的字符串顺延

      • 在 迭代器it 位置插入字符串:

        insert(it, firstIt, lastIt)firstItlastIt待插字符串的首尾迭代器,用来表示串 [firstIt, lastIt) 将被插在 it 位置上

    8. string::npos:是一个常数,本身值为**-1,但由于是unsigned_int类型,因此实际上可以认为是最大值**。用于作为find函数失配时的返回值。

    9. find()

      • str.find(string str1):当 str1 是子串时,返回在 str 中第一次出现的位置;如果不是字串,则返回string::npos
      • str.find(string str1, int pos):从 pos 位置开始匹配 str1
    10. erase()

      • 删除单个元素:erase(it),目标字符之后的字符字串 前移
      • 删除一个区间内的所有元素,目标区间之后的字符字串 前移
        • erase(firstIt, lastIt),删除 [firstIt, lastIt) 间的所有字符
        • erase(int pos, int length),pos 是删除的起始位置
    11. substr(int pos, int length)返回从 pos 位置开始,长度为 length 的字串

    12. replace()

      • str.replace(int pos, int lenth, str1):把 str 从 pos 位置开始,长度为 length 的字串替换为 str1,替换位置之后的字符串不变
      • str.replace(it1, it2, str1):把 str 的迭代器 [it1, it2) 范围的字串替换为 str1,替换位置之后的字符串不变

<map>

映射map**可以将 任何基本类型(包括STL容器) 映射到 任何基本类型(包括STL容器)**。

来看一个情况:判定给的一些数字在某个文件中是否出现过。如果数字很大,那么就不便建立散列的数组。这时,可以通过map,将这些数字当成字符串,建立stringint的映射

  1. map的定义

    map<typename1, typename2> mp;< >内第一个是 键(key) 的类型,第二个是 值(value) 的类型。map 会以 键 从小到大的顺序自动排序(因为 map 内部是使用红黑树实现的,set也是如此)map 的一个键只能对应一个值。如果是 int 型映射到 int 型,就相当于普通的 int 型数组。

    如果是字符串到整型的映射,必须使用 string 而不能使用 char数组map<string, int> mp;,因为数组不能作为键值

  2. map常用函数

    1. begin():取 map 的首元素地址

    2. end():取 map 的 尾后(尾元素地址的下一个)地址(美国人思维比较习惯左闭右开

    3. find(key):返回 键key的对应映射值 的迭代器

      map<char int>::iterator it = mp.find('b');

    4. erase()

      • 删除单个元素:
        • erase(it):it 为待删除映射的迭代器
        • erase(key):key 为待删除映射的键
      • 删除一个区间内的所有映射:erase(firstIt, lastIt)
    5. size():获取映射的对数

    6. clear():清空 map 中的所有映射

  3. map容器内元素的访问

    1. 通过下标访问

      例如定义为map<char, int> mp的map来说,可以直接使用mp['c']的方式来访问对应的整数。

    2. 通过迭代器访问

      map<typename1, typename2>::iterator it;,因为 map 的每一对映射都有两个 typename,这决定了必须能通过一个 it 来同时访问键和值。事实上,map可以使用it->first来访问键,it->second来访问值。

  4. 延伸

    map 的 键和值是唯一的(一对一),而**如果需要一个键对应多个值(一对多),就只能用multimap**。C++11 标准中还增加了unordered_map,以散列代替 map 内部的红黑树实现,使其可以只处理映射而不按key值排序,速度比 map 快得多。


<queue>

队列,先进先出。

  1. queue的定义

    queue<typename> name;

  2. queue常用函数

    1. push(x):将 x 进行入 队尾
    2. front()back():分别获得队首和队尾元素
    3. pop():令队首元素出队
    4. empty()检测 queue 是否为空
    5. size():返回队列内元素个数
  3. queue容器内元素的访问

    由于队列是一种先进先出的限制性数据结构,因此只能通过front()来访问队首元素,back()访问队尾元素

  4. queue的常见用途

    当需要实现广度优先搜索(BFS)时,可以不用手动实现一个队列,以提高程序的准确性。需要注意的时是,使用front()pop()函数之前,必须用empty()判断队列是否为空,否则可能因为队空而出现错误。

    延伸:STL的容器中还有两种跟队列有关:

    • 双端队列deque首尾皆可插入和删除的队列。

    • 优先队列priority_queue:使用实现的默认当前队列 最大元素置于队首的容器。

priority_queue
  1. priority_queue的定义

    priority_queue<typename> name;

  2. priority_queue常用函数

    1. push(x):将 x入队,时间复杂度为O(logN)
    2. top():获得队首(即堆顶)元素
    3. pop():令队首元素(即堆顶)元素出队,时间复杂度为O(logN)
    4. empty():检测优先队列是否为空
    5. size():返回优先队列内元素个数
  3. priority_queue容器内元素的访问

    **只能通过top()函数访问队首元素(优先级最高的元素)**。

  4. priority_queue元素优先级的设置

    1. 基本数据类型的优先级设置

      下列两种优先队列的定义是等价的(以int型为例)

      1
      2
      priority_queue<int> q;
      priority_queue<int, vector<int>, less<int> > q;
      • vector<int>:用来承载底层数据结构—堆的容器
      • less<int>:表示数字大的优先级越大(默认优先级);greater<int>即为数字小的优先级越大

      因此,如果想让最小的元素放在队首,需要如下定义:

      priority_queue<int, vector<int>, greater<int> > q;

    2. 结构体的优先级设置

      比如定义水果的结构体:

      1
      2
      3
      4
      struct fruit {
      string name;
      int price;
      }

      现在希望按水果价格高的为优先级高,就需要**重载小于号<**:

      • 方式一:

        1
        2
        3
        4
        5
        6
        7
        8
        struct fruit {
        string name;
        int price;
        friend bool operator < (fruit f1, fruit f2) {
        return f1.price < f2.price; //水果价格高的为优先级高
        // return f1.price > f2.price; //水果价格低的为优先级高
        }
        };

        其中,friend友元bool operator < (fruit f1, fruit f2)<进行了重载。此时就可以直接定义fruit类型的优先队列,其内部就是以水果价格高的为优先级高priority_queue<fruit> q;

      • 方式二:将重载的函数写在结构体外

        1
        2
        3
        4
        5
        6
        7
        struct cmp {
        bool operator () (fruit f1, fruit f2) {
        return f1.price > f2.price;
        }
        }

        priority_queue<fruit, vector<fruit>, cmp> q;

      即便是基本数据类型或者其他STL容器(如 set)也可以通过同样的方式来定义优先级。如果结构体内的数据较为庞大,建议使用引用来提高效率

      1
      2
      3
      4
      5
      6
      7
      friend bool operate < (const fruit &f1, const fruit &f2) {
      reutrn f1.price > f2.price;
      }
      //或如下
      bool operator () (const fruit &f1, const fruit &f2) {
      return f1.price > f2.price;
      }
  5. priority_queue的常见用途

    可以解决一些贪心问题,也可以对 Dijkstra 算法进行优化(因为优先队列的本质是堆)。需要注意的时是,使用top()函数之前,必须用empty()判断队列是否为空,否则可能因为队空而出现错误。


<stack>

  1. stack的定义

    stack<typename> name;

  2. stack常用函数

    1. push(x):将 x 压入栈
    2. top():获得栈顶元素
    3. pop():弹出栈顶元素
    4. empty():检测 stack 是否为空
    5. size():返回 stack 内元素的个数
  3. stack容器内元素的访问

    只能通过top()函数访问栈顶元素

  4. stack的常见用途

    stack 用来模拟实现一些 递归,防止程序对栈内存的限制而导致程序运行出错。对有些题目来说,如果用普通的函数进行递归,一旦递归层数过深,则会导致程序运行崩溃。如果用栈来模拟递归算法的实现,则可以避免这一方面的问题


<utility>

pair

当想要将两个元素绑在一起作为一个合成元素,又不想因此定义结构体时,使用pair可以很方便地作为一个替代品,即 pair 可以看作 内部有两个元素 的结构体

  1. pair的定义

    注意:因为 映射map 的内部实现中涉及 pair,因此添加 map头文件 时会自动添加 utility头文件,因此,记不住 utility头文件,则可以用 map头文件 来代替

    pair 有两个参数:pair<typeName1, typeName2> name;

    • 如果想在定义 pair 时进行初始化,只需要跟上一个小括号,填写两个初始化元素即可:

      pair <string, int> p("haha", 5);

    • 如果想临时构建一个 pair

      • pair<string, int>("haha", 5)
      • 使用自带的make_pair函数:make_pair("haha", 5)
  2. pair中元素的访问

    pair 中只有两个元素,分别是firstsecond,只需要按 正常结构体的方式 访问即可。

  3. pair常用函数

    比较操作数

    两个 pair 类型数据可以直接使用比较符比较大小,比较规则是先以first的大小作为标准,first相等时去比较second的大小

  4. pair的常见用途

    • 代替二元结构体及其构造函数,节省编码时间

    • 作为map键值对来进行插入:

      1
      2
      3
      map<string, int> mp;
      mp.insert(make_pair("heihei", 5));
      mp.insert(pair<string, int>("haha", 10));

数组

  1. 一维数组初始化

    如果数组没有初始化,数组中每个元素为随机数。一维数组的初始化,未被赋初始值的元素将默认初始化为0,因此,如果想要给整个数组初始化为0,只需第一个元素初始化为0即可。int a[10] = {0};(不适用C语言)

  2. 二维数组初始化

    二维数组初始化需要按第一维的顺序,依次用大括号给出第二维初始化的情况,未被赋初始值的元素将默认初始化为0

    1
    2
    3
    4
    5
    6
    7
    8
    int a[4][2] = {{3}, {}, {8, 4}}; //第二行使用大括号跳过
    /**
    数组初始化为:
    3 0
    0 0
    8 4
    0 0
    */
  3. 如果数组大小较大(大概$10^6$级别),需要定义在主函数外(即全局变量)

    否则程序会异常退出(局部变量来自系统栈,允许申请空间较小;全局变量来自静态存储区,允许申请的空间较大)

  4. 对数组中每一个元素赋相同的值

    需要#include <cstring>

    void* memset(数组名, 值, sizeof(数组名));

    memset 按字节赋值,即对每个字节赋同样的值,组成int型的4个字节就会被赋成相同的值。故建议只用于赋 0或 -1(0的二进制补码为全0,-1的二进制补码为全1),不易出错。

    如果要对数组赋其他数字(如 1),则使用 fill函数(执行速度比 memset 慢)


字符串

字符数组

  • 输入

    • #include <cstdio>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      - `scanf("%s", str);` 识别**空格或换行**作为输入结束,且**空格或回车符会留在缓冲区**

      - `char c = getchar();` 能够读入**空格和换行符**

      - `gets(char* str);` 识别**换行**作为输入结束,且**回车符会被舍弃,不在缓冲区**

      因此,**scanf一个输入后,如果要使用gets,需要先用 getchar 接收scanf后的空格或换行符**。

      另外,**PAT使用C++提交时**,使用`gets()`函数会出现**编译错误**,建议使用C++函数。

      - ```c++
      #include <iostream>
      using std::cin;
      - `cin >>` - `cin.getline(数组名称, 数组大小)`
      1
      2
      3
      4
      5
      6
      char str[100];
      /*
      指定最多读入 9 个字符,超出即报错
      (实际数组最多能存放99个字符,空字符\0 占用一个字符位)
      */
      cin.getline(str, 10);
  • 输出

    • #include <cstdio>
      
      1
      2
      3
      4
      5
      6
      7
      8

      - `printf(str);`
      - `putchar(char c);`
      - `puts(char* str);` 输出数组内容后,**紧跟一个换行**

      - ```c++
      #include <iostream>
      using std::cout;
      - `cout <<`
  • 字符数组的存放方式

    字符数组的末尾都有一个 空字符\0(即NULL),表示存放的字符串的结尾。空字符\0getsscanf输入字符串时,会自动添加在输入的字符串后面,并占用一个字符位putsprintf就是通过识别\0作为字符串的结尾进行输出

    • 空字符\0占用一个字符位,因此字符数组的长度一定要比实际存储字符串的长度 至少多1
    • 如果使用getchar()输入字符串,一定要在每个字符串后加入\0,否则printf和puts输出字符串会因无法识别字符串末尾而输出一大堆乱码

C++ 的标准库类 string

1
2
#include <string>
using std::string;

若引用了<iostream>头文件,创建string对象时,无须引用<string>,因为<iostream>有对std::string的间接引用。但时要对string对象运用相关的函数操作,仍须引用<string>

  • 输入一整行

    getline(cin, str)

  • 比较字符串大小

    • str1.compare(str2)

      **按字典顺序(顺序越靠后,越大)**,返回两个字符串大小的比较


<cstring>

  1. strlen(str)

    得到字符数组中第一个\0前 的字符个数

  2. strcmp(str1, str2)

    **按字典顺序(顺序越靠后,越大)**,返回两个字符串大小的比较结果:

    • str1 < str2,返回一个负整数(不同编译器处理不同,不一定是 -1)
    • str1 == str2,返回一个0
    • str1 > str2,返回一个正整数(不同编译器处理不同,不一定是 +1)
  3. strcpy(str1, str2)

    把 str2 复制给 str1,包括结束符\0

  4. strcat(str1, str2)

    把 str2 拼接到 str1 后面


字符串与数字的相互转换

C语言

方法一:
1
#include <cstdlib>

字符串 转为 数字

  • atoi(str):将字符串转为 int型
  • atol(str):将字符串转为 long long型
  • atof(str):将字符串转为 double型

方法二: sscanf 与 sprintf

如果想从屏幕输入 int型变量n 并将 n 输出到屏幕:

1
2
3
4
5
scanf("%d", &n);
printf("%d", n);
//其实可以表示成如下
scanf(screen, "%d", &n);
printf(screen, "%d", n);

sscanfsprintf的格式如出一辙,只是把screen换成了字符数组

1
2
sscanf(str, "%d", &n); //将字符数组str中的内容以"%d"格式写到n中
sprintf(str, "%d", n); //把n以"%d"的格式写到str数组中

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdio>

int main() {
int n;
double db;
char str[100] = "2048:3.14,hello", str2[100];
/*
从字符串读取格式化输入
使得 n = 2048, db = 3.14, str2 = hello
*/
sscanf(str, "%d:%lf,%s", &n, &db, str2);
n = 12;
db = 3.1415;
str2[100] = "good";
/*
发送格式化输出到字符串
使得 str = "12:3.14,good"
*/
sprintf(str, "%d:%.2f,%s", n, db, str2);
return 0;
}

C++:

数字 转为 字符串

1
2
3
4
5
#include <string>
using namespace std;

string str = to_string(num);
len = str.length();

数组作为函数参数

  • 参数中数组的第一维不需要填写长度(如果是二维数组,第二维需要填写长度)
  • 在函数中对数组元素的修改 等同于 对原数组元素的修改(与普通局部变量不同)

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <cstdio>

void change(int a[], int b[][5]) {//参数数组 第一维不需要填写长度,第二维需要填写长度
a[0] = 1;
a[1] = 3;
a[2] = 5;
b[0][0] = 1;
}

int main() {
int a[3] = {0}; //不适用C语言
int b[5][5] = {0}; //不适用C语言
change(a, b);
for (int i = 0; i < 3; i++) {
printf("%d\n", a[i]);
}
/*
输出结果为:
1
3
5
*/
return 0;
}
  • 数组不允许作为返回类型出现,想要返回数组,只能通过参数返回

指针

  • C语言习惯于把*放在变量名之前:int *p;(声明多个指针时,易统一)

  • C++习惯把*放在数据类型之后:int* p;

  • 地址赋给p而不是*p

    1
    2
    3
    int a;
    int* p;
    p = &a; //等价于 int* p = &a;
  • 指针的加减法

    • 两个指针相减,得到两个地址的距离
    • p + 1指 p所指的int型变量 的下一个int型变量地址
    • 指针支持自增和自减操作

指针变量作为函数参数

例子:交换两个数

只有在获取地址的情况下对元素进行操作,才能真正修改变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <cstdio>

void swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}

int main() {
int a = 1, b = 2;
int *p1 = &a, *p2 = &b;
swap(p1, p2);
return 0;
}

错误写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void swap1(int* a, int* b) {
int* tmp;
*tmp = *a;
*a = *b;
*b = *tmp;
}
/*
tmp未初始化,存放的地址很大概率指向系统工作区间,不能修改,故后续报错
初始化tmp即可
*/
void swap2(int* a, int* b) {
int x;
int* tmp = &x;
*a = *b;
*b = *tmp;
}

引用

引用的含义

引用是C++的语法,在编程时极为实用。如果要修改传入函数的参数,且不使用指针,可以通过C++的引用。引用不产生副本,只是给原变量起个别名

引用的方法:在函数的参数变量名前加个&即可。要将引用&取地址运算符&区分开来,引用并不是取地址的意思

1
2
3
4
5
6
7
8
9
10
11
12
#include <cstdio>

void change(int &x) {
x = 1;
}

int main() {
int x = 10;
change(x);
printf("%d\n", x); //输出1
return 0;
}

指针的引用

例子:交换两个数

1
2
3
4
5
6
//错误写法
void swap(int* a, int* b) {
int* tmp = a;
a = b; //交换的地址 是 值传递,不会修改原指针的地址
b = tmp;
}

通过引用实现交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <cstdio>

void swap(int* &a, int* &b) { //通过引用实现交换
int* tmp = a;
a = b;
b = tmp;
}

int main() {
int a = 1, b = 2;
int *p1 = &a, *p2 = &b;
swap(p1, p2); //不能写成 swap(&a, &b);
return 0;
}

引用是产生变量的别名,常量不可使用引用。故上述

swap(p1, p2); //不能写成 swap(&a, &b);


结构体(struct)的使用

访问结构体内的元素

假设p是一个指向结构的指针,可以用**p->结构成员的形式(等价于(*p).结构成员)**,引用相应的结构成员。


申请动态内存空间

如需要定义链表的结点时。

C语言有mallocfree函数用于内存的申请和释放,C++有newdelete运算符用于内存的申请和释放。**推荐使用C++进行内存的申请和释放(用法更简洁)**。

  • 申请内存空间 — new运算符:

    使用new + 类型名即可分配一块该类型的内存空间,返回申请的同变量类型的指针typename* p = new typename;

    示例:

    1
    2
    int* p = new int;
    node* p = new node;

    通常情况下,内存空间的申请不会失败。失败一般发生在使用new申请了较大的动态数组,如:int* p = new int[1000000];申请失败会发生异常

  • 释放内存 — delete运算符:

    需要释放的内存空间的指针变量为参数:delete(p);,执行之后,**指针变量p没有消失,只是指向了空地址NULL**。

输入输出

C语言 C++ 比较
scanf函数
printf函数
cin
cout
cin 和 cout 无需指定输入输出格式
消耗的时间比 scanf 和 printf 多得多

头文件

内容 C语言 C++
输入输出库 #include <stdio.h> #include <cstdio>
#include <iostream>
数学函数 #include <math.h> #include <cmath>
字符串有关函数 #include <string.h> #include <cstring>

变量类型

布尔型变量

C语言 C++ 比较
#include <stdbool.h> 可直接使用 true(存储时为1)
false(存储时为0)

字符串

内容 C语言 C++
表示方法 通过 字符数组 表示 通过 string类 表示
字符串长度的表示 #include <string.h>
strlen(str)
#include <string>
str.length()
字符串拼接 #include <string.h>
strcat(str1, str2); //把str2拼接到str1之后
str1 += str2;

排序函数

  • C语言 — qsort
1
2
3
4
5
6
7
#include <stdlib.h>

int cmp(const void *a, const void *b) {
return *(int *)a - *(int *)b; //升序
}

qsort(排序数组, 元素数量, 每个元素的大小(可用sizeof获得), 比较函数cmp);
  • C++ — sort
1
2
3
4
5
6
7
#include <algorithm>

bool cmp(int a, int b) {
return a > b; //降序
}

sort(首元素地址, 尾元素地址的下一个地址, 比较函数cmp(非必填));
cmp函数的区别 C语言 C++
返回值类型 int bool
排序的判断 返回值 > 0, a 将被排在b后面;
返回值 < 0, a 将被排在b前面;
默认升序
返回值为true时,a将被排在b前面
比较方式 元素相减
不能用$>$、$<$比较符(返回无负值
$>$、$<$比较符

qsort函数的使用需要运用指针,且写法上没有sort函数简洁。sort函数根据具体情形使用不同的排序方法,效率较高,在实现中规避了经典快速排序中可能出现的会导致实际复杂度退化到$O(n^2)$的极端情况

因此更推荐使用C++的sort函数进行代码编写。


申请动态内存空间

内存泄露是指申请到的内存空间在使用过后没有释放,导致一些程序内存消耗过快,最后无内存可分配的情况。一般考试中,分配的内存空间在程序结束时即被释放,因此即便不是放空间,也不会产生什么影响,并且内存大小一般也足够一道题的使用。但是从编程习惯上,需要养成即时释放空间的习惯

  • C语言

    <stdlib.h>

    • 申请内存空间 — malloc函数:

      需要申请的内存空间为参数,返回指向这块空间的指针,因为返回的指针是void*型,因此需要强制转换为对应类型typename* p = (typename*)malloc(sizeof(typename));

      示例:

      1
      2
      int* p = (int*)malloc(sizeof(int));
      node* p = (node*)malloc(sizeof(node));

      通常情况下,内存空间的申请不会失败。失败一般发生在使用malloc申请了较大的动态数组,如:int* p = (int*)malloc(1000000 * sizeof(int));。**申请失败会返回空指针NULL**。

    • 释放内存 — free函数:

      需要释放的内存空间的指针变量为参数:free(p);,执行之后,**指针变量p没有消失,只是指向了空地址NULL**。

  • C++

    • 申请内存空间 — new运算符:

      使用new + 类型名即可分配一块该类型的内存空间,返回申请的同变量类型的指针typename* p = new typename;

      示例:

      1
      2
      int* p = new int;
      node* p = new node;

      通常情况下,内存空间的申请不会失败。失败一般发生在使用new申请了较大的动态数组,如:int* p = new int[1000000];申请失败会发生异常

    • 释放内存 — delete运算符:

      需要释放的内存空间的指针变量为参数:delete(p);,执行之后,**指针变量p没有消失,只是指向了空地址NULL**。

准备

由于PE方式只适用于安装windows,因此选择常规的制作启动U盘进行Ubuntu的安装。

操作系统镜像下载(桌面版,文件名后缀 .iso.torrent)

3G及以上空间的U盘/移动硬盘(用于装载系统镜像)

制作启动U盘

  • 方式一:Windows 10 可以直接打开 iso镜像文件,不用下载任何工具

    1. 格式化U盘

    2. 双击ISO文件,然后选中打开的所有文件,复制到U盘即可

  • 方式二:下载U盘制作制作工具Rufus(无需安装,可直接运行)

    1. 选择设备(需要被制作的U盘/移动硬盘)

      • 只有一个外接设备时会自动读取
      • 如果外接设备是移动硬盘,需要显示高级设备选项 — 显示USB外置硬盘
    2. 选择操作系统镜像

    3. 分区类型理论上(联想Y480不知道为什么无法通过UEFI成功安装))

      硬盘格式与引导方式、MBR与GPT、UEFI 与LEGACY bios的区别

      • 若选择GPT,之后系统安装时选择UEFI启动推荐

      • 若选择MBR,之后系统安装时选择BIOS启动,否则报错(该模式一定能够安装成功)

    4. 其余选项默认,点击开始,即可制作启动U盘

    Rufus制作启动U盘

安装系统

  1. 重启电脑,进入选择启动方式界面(联想笔记本:F12;惠普笔记本:F9)

  2. 选择启动U盘

    • 想要安装系统的硬盘的格式为GPT,选择UEFI启动,即 EFI USB Device
    • 启动U盘分区类型为MBR,选择BIOS启动,即 选择对应的U盘
  3. 欢迎 — 中文 — 安装Ubuntu

  4. 键盘布局 — 汉语

  5. 无线 — 不连接

  6. 更新和其他软件 — 最小安装

  7. 安装类型 — 其他选项 — 进行分区

    硬盘大小110GB

    • UEFI启动:

      空间大小 主分区/逻辑分区 类型
      200MB 逻辑分区 EFI
      8GB
      (和内存大小一致)
      主分区 swap(交换)
      40GB 逻辑分区 ext4
      挂载至 “/”
      剩余空间 逻辑分区 ext4
      挂载至 “/home”
    • BIOS启动:

      空间大小 主分区/逻辑分区 类型
      200MB 主分区 biosxx
      8GB (和内存大小一致) 主分区 swap(交换)
      40GB 逻辑分区 ext4 挂载至 “/”
      剩余空间 逻辑分区 ext4 挂载至 “/home”
  8. 完成安装,根据提示重启即可(可拔去启动U盘/硬盘,避免再次从启动U盘启动)


将 从主服务器下载 更换为 国内镜像站点下载

​ 显示应用程序 - 软件和更新 - Ubuntu软件 - 下载自 - 其他站点 - 选择最佳服务器(自动选择最合适的站点)


更新系统文件

打开终端的快捷键:Ctrl+Alt+T

1
2
sudo apt update
sudo apt upgrade

获得各种开发工具

sudo apt-get install build-essential

  • 安装文字编辑器vim浏览器chromium

    1
    2
    sudo apt-get install vim
    sudo apt-get install chromium-browser
  • 树视图

    sudo apt-get install tree

下载

官网下载Cmder(压缩包,解压即用),有两种不同的版本可供选择:

  • Mini
  • Full(附带msysgit工具)

可根据设备是否已安装git自行选择


配置

添加系统环境变量

我的电脑 - 右键属性 - 高级系统设置 - 环境变量 - 系统变量,在Path中添加Cmder路径


添加Cmder到右键菜单

  1. Win + R - 输入Cmder,确认,打开Cmder
  2. 点击右下方的Create new console
  3. Startup command or {Task} name - {bash::bash}
  4. Run as administrator
  5. Start,即打开一个管理员权限的控制台
  6. 输入 Cmder.exe /REGISTER ALL,回车。即可在每个文件夹中鼠标右键右键 - Cmder here,打开Cmder

默认开启设置

Cmder窗口右下角右键Show system menu - General - Choose your startup task or even a shell with arguments: - {bash::bash} - Save settings


关闭Tab不提示

Cmder窗口右下角右键Show system menu - General - Confirm - Close confirmations下的复选框全不勾选 - Save settings


参考:Cmder 使用 笔记

简介

Dev-C++是一个SourceForge的项目,早已停止更新

缺点:

  • 分辨率低,脱节于屏幕素质的提升,使用感官不佳
  • 中文注释容易乱码,且没有明显的选项用来修改编码格式

下载安装

安装过程中的语言没有中文选项,选择English即可,按照默认选项安装。首次运行时,选择使用软件时的语言为简体中文即可,按照默认选项完成初运行配置。


主题

  • 菜单栏 - 工具 - 编辑器选项 - 语法 - 预设 - Obsidian 黑曜石主题

工具 - 编辑器选项

语法 - 预设 - Obsidian 黑曜石主题

  • 菜单栏 - 工具 - 编辑器选项 - 基本

    • 显示编辑器提示
    • 显示函数提示
    • 高亮显示当前行 - 色彩 - Black


字体

菜单栏 - 工具 - 编辑器选项 - 显示 - 字体 - YaHei Consolas Hybrid

字体下载: Consolas和微软雅黑混合字体


快捷键

功能 快捷键
自动整理代码 Ctrl + Shift + A
代码补全 Ctrl + Space(和输入法切换快捷键冲突)

自定义快捷键:工具 - 快捷键选项 - 菜单项的底部 - Show Code Completion(代码补全),自定义即可。


添加 C99/C++11 标准

工具 - 编译选项 - 编译时加入以下命令 - -std=c99-std=C++11


调试

初次启用调试,出现“项目没有调试信息,您想打开项目调试选项并重新生成吗”

菜单栏 - 工具 - 编译选项 - 代码生成/优化 - 连接器 - 产生调试信息 - Yes

调试窗口查看变量的实时数据

开启调试后,有三种方式可以查看变量的实时数据:

  1. 左侧调试窗口 - 空白处鼠标右键 - 添加查看
  2. 下侧调试窗口 - 添加查看
  3. 代码界面鼠标在变量名处停留,会显示当前变量值


参考:

准备:下载卡硬工具箱免安装版至U盘或其他移动设备。

以下任何一步有问题,退货。

  1. 检查笔记本外壳是否有指纹、裂痕、凹陷,机身是否有变形,屏幕是否完好
  2. USB接口是否有抽插痕迹。
  3. 不插电源,直接按电源键,若能开机,则很可能是二手
  4. 插上电源,按电源键开机。
  5. 进入系统,系统初始化设置(若没有设置界面,直接进入了系统,二手)。
  6. 不连接网络,防止后续检查有问题无法退货。
  7. 进入桌面后,插上有卡硬工具箱的U盘。
    1. 显示器工具 - 检测屏幕坏点。
    2. 硬盘工具 - Diskinfo - 检查通电次数和通电时间(一般通电时间在24小时内,通电次数在150次以内,可以判定为新机硬盘)。

上述步骤没有出现问题后,依然不要联网,在无理由退货时间内使用电脑,以便后续使用不满意时方便退货。


参考:笔记本电脑验机指南

彻底拆机清灰(涂硅脂、更换)

清理键盘

  1. 键盘在D面有两个螺丝固定,先卸下D面挡板,再卸下键盘螺丝、固定光驱的螺丝。
  2. 按照彻底拆机的拆卸键盘方法即可取下键盘。

  • 电脑屏幕色彩管理软件:LUT Manager
  • MAC自带的颜色配置文件:MAC.icc
  • 适用系统:Windows

  1. 下载LUT Manager和MAC.icc
  2. 将MAC.icc放到系统盘/windows/system32/spool/drivers/color/目录下
  3. 打开控制面板(查看方式-图标)- 颜色管理 - 高级 - 更改系统默认值 - 高级 - 使用Windows显示器校准 - 关闭
  4. 设备 - 显示器 - 使用我对此设备的设置 - 添加 - 选择MAC.icc - 设置为默认配置文件 - 关闭

参考:矫正色差的软件

优势

  1. 可以更改硬盘格式与引导方式
  2. 可以备份原有系统的数据
  3. 自带修复软件,可以在不重装系统的情况下修复系统

准备

  • PE安装工具下载:微PE工具箱
  • 操作系统镜像下载:MSDN
  • 8G及以上空间的U盘/移动硬盘(主要用于存放系统镜像)

安装系统

  1. 安装PE到U盘/移动硬盘

  2. 将操作系统镜像放到U盘(或移动硬盘对应的存储分区)

  3. 重启电脑,进入选择启动方式界面(联想笔记本:F12;惠普笔记本:F9)

  4. 选择PE所在设备,即启动PE

  5. 启动桌面上的Windows安装器

    • 第一行:选择操作系统镜像文件

    • 第二行(选择引导驱动器):想要安装系统的硬盘的格式(硬盘格式与引导方式、MBR与GPT、UEFI 与LEGACY bios的区别)不同,需要选择的盘区也不同。

      1. GPT格式:选择ESP分区(大约90~250MB大小的一个隐藏盘区)
      2. MBR格式:选择要装系统的盘区即可

      两种方式都需要右边的三个指示灯不为红色(黄绿两色OK)

    • 第三行(安装磁盘的位置):选择系统将要安装的盘区即可。

    • 第四行:选择安装的Win 10版本。

    完成以上四行内容即可点击开始安装。

    ​ 注:用 DiskGenius 格式化硬盘/转换硬盘格式 时记得勾选对齐分区到此扇区数的整数倍(即4K对齐,无需修改具体参数)。

  6. 重启系统,进行Win 10的初始化设置,建议进入桌面后再联网,操作过程会快一些。


安装驱动

去官网下载驱动安装,或者下载驱动精灵免安装版安装驱动(另外,Windows自身的更新会下载部分相关驱动)。

注:部分旧机型官网没有Windows10版本的相关驱动,不建议官网下载相关旧驱动,很可能不兼容。直接使用驱动精灵无脑安装基本驱动即可。

注注:博主的老机子联想Y480的触摸板在驱动精灵中没有相关的驱动,无法使用Win10的触摸板手势,但是鲁大师的驱动检测功能下可以看到触摸板驱动,选择升级即可使用手势。博主猜测此方法同样适用于部分老机型,不得不赞美一下娱乐大师。

注注注:Elitebook 830 G5的固态硬盘是三星的PM961,需要加上Turbo驱动提速,官网没有直接的链接,可以通过驱动精灵安装。


参考

Win 10全面官方装系统教程

变量名

传统的C语言用法中,变量名使用小写字母,符号常量名使用大写字母


类型限定符

类型限定符signed与unsigned可以用于限定char类型或任何整型

  • signed (默认)
    如char类型 signed char,取值范围为-128~127
  • unsigned无符号类型
    总是正值或0,例如char类型,unsigned char 取值范围为0~255

定义常量

  • 方法一: const 数据类型 变量名 = 常量;推荐

    例: const int AMOUNT=100;

    const也能配合数组参数使用,表明函数不能修改数组元素的值。

  • 方法二:#define 标识符 常量
    末尾没有分号
    例:#define LOWER 0


定义布尔类型

  • 需要在开头写如下代码:#include <stdbool.h>

布尔类型为bool (Java中为Boolean)


数组

例:int number[100]; (Java中为 int[] number = new int[100];)

(C99开始,可以用变量定义数组大小

二维数组的初始化

  • 列数必须给出,行数可以交给编译器来数

  • 每行一个{},用逗号分隔,

  • 最后的,可以存在,有古老的传统

  • 如果内容省略,表示补0

  • 也可以用定位(C99 ONLY)

    1
    2
    3
    int a[10] = {
    [0] = 2, [2] = 3, 6,
    };
    • [n]在初始化数据中给出定位
    • 没有定位的数据接在前面的位置后面
    • 其他位置的值补零
    • 也可以不给出数组大小,让编译器算
    • 特别适合初始数据稀疏的数组

数组的大小

sizeof(a)/sizeof(a[0])

  • sizeof给出整个数组所占据的内容的大小,单位是字节
  • sizeof(a[0])给出数组中单个元素的大小,相除就得到了数组的单元个数
  • 数组作为函数的参数时 实际是指针(数组的地址),需要用另一个参数来传入数组的大小
    • 不能在[]中给出数组的大小
    • 不能再利用sizeof来计算数组的元素个数

数组的赋值

  • 数组变量本身不能被赋值
  • 要把一个数组的所有元素交给另一个数组,必须采用遍历

字符类型

char是一种整数,也是一种特殊的类型——字符。

  • ''也是一个字符
  • printfscanf里用%c来输入输出字符

逃逸字符

字符 意义
\b 回退一格
\t 到下一个表格位
\n 换行
\r 回车
\" 双引号
\' 单引号
\\ 反斜杠本身

函数

C语言的编译器自上而下,按顺序分析代码

函数的先后顺序很重要。

C语言的函数可以将声明和定义分离,从而顺利通过编译,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void sum(int begin, int end); //声明,函数原型, 如果没有参数,括号内最好填写 void,更严谨

int main() { //main的参数部分也可以写 void
sum(1,10);
sum(20,30);

return 0;
}

// 定义
void sum(int begin, int end) {
int i = 0;
int sum = 0;
for (i = begin; i <= end; i++) {
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}

参数传递

  • 调用函数时,给的值可以与参数类型不完全匹配,编译器自行转换类型,(Java则对类型转换要求严格)。
  • C语言调用函数传参数时,是值传递

指针与数组

  • 一元运算符&
    获取变量的地址,它的操作数必须是变量,没有其他的运算包括其中。
    p = &c; 称p为“指向”c的指针。

  • 一元运算符*
    间接寻址运算符。*作用于指针时,将访问指针所指向的变量。

指针

指针就是保存地址的变量

1
2
3
4
int i;
int *p = &i;

int *p,q; //p为指针,值为所指向的地址;*p是int类型变量,为所指向地址上的值;

指针应用场景

  1. 交换两个变量的值

    1
    2
    3
    4
    5
    void swap(int *pa, int *pb) {
    int t = *pa;
    *pa = *pb;
    *pb = t;
    }
  2. 函数需要返回多个值,某些值就只能通过指针返回

指针与const

  1. 指针是const(在const前):表示一旦得到了某个变量的地址,不能再指向其他变量;不影响改变该地址上的值*

    1
    2
    3
    int *const q = &i; //q是const
    *q = 26; //所指变量的值可以更改
    q++; //ERROR!
  2. 所指的类型是const(在const后):表示不能通过指针去修改那个变量(*不会使那个变量成为const**)

    1
    2
    3
    4
    const int *p = &i; // int const *p 作用相同
    *p = 26; //ERROR! (*p)是const
    i = 26; //OK
    p = &j; //OK

数组参数

以下四种函数原型等价

  • int sum(int *arr, int n);
  • int sum(int *, int);
  • int sum(int arr[], int n);
  • int sum(int [], int)

数组变量是特殊的指针

  • 数组无需用&取地址

  • 数组的单元表达的是变量,需要用&取地址

  • []运算符可以对数组做,也可以对指针做
    p[0] <==> a[0]

  • *运算符可以对指针做,也可以对数组做

  • 数组变量是const的指针,所以不能被赋值
    int a[] <==> int *const a=...


字符串

C语言的字符串以字符数组的形态存在

  • 不能用运算符对字符串做运算
  • 通过数组的方式可以遍历字符串

以整数0结尾的一串字符为字符串。(0或\0是一样的,但是和'0'不同)

  • 0标志字符串的结束,但不是字符串的一部分
  • 字符串以数组的形式存在,以数组或指针(主)的形式访问
  • string.h里有很多处理字符串的函数

字符串变量

1
2
3
char *str = "Hello";
char word[] = "Hello";
char line[10] = "Hello";

字符串常量

例如:Hello,字符串会被编译器变成一个字符数组放在某处,这个数组的长度是5+1,结尾还有表示结束的0

例:char* s = "Hello, world!";

  • s是一个指针,初始化为指向一个字符串常量
    • 由于这个常量存储的地方,实际上s为const char* s。(历史原因,编译器接受不带const的写法)
    • 试图对s所指的字符串做写入会导致严重的后果
  • 如果需要修改字符串,应该用数组:char s[] = "Hello, world!";

选择指针还是数组形式处理字符串?

  • 数组(字符串就存放在当前位置,如果要构造一个字符串

    • 作为本地变量,空间被自动回收
  • 指针(不知道字符串的存储位置,如果要处理一个字符串

    • 处理参数
    • 动态分配空间

读入、输出数据

  • 需要在开头写如下代码:#include <stdio.h>

读取数据,需要在变量名前加上&,从而赋值给变量。

EOF(End Of File)

可以通过printf("%d", EOF);读取EOF的数值,一般的设备上值是-1。
EOF操作:

  • windows:Ctrl + Z
  • unix:Ctrl + D

浮点数的输入输出

  • 输入:scanf("%lf", ...);
  • 输出:
    • printf("%f", ...); //float和double类型,printf函数都使用%f进行说明
    • printf("%ld", ...); //对应long整型的参数

字符的输入/输出

  • 输入 getchar()
    从文本流中读入下一个输入字符,并将其作为结果值返回。
  • 输出 putchar(c)
    打印一个字符

字符串的输入输出

1
2
3
char string[8];
scanf("%s", string);
printf("%s", string);

scanf读入一个单词(到空格、tab或回车为止)


  • program <infile
    从输入文件infile中读取字符。
  • otherprogram | program
    将程序otherprogram的标准输出 通过管道,重定向到程序program的标准输入上。

位运算符

  • &
  • |
  • ^ 异或
  • ~ 求反码
  • >> 右移(高位出现的空位,原来高位是什么,就用什么补该空位;>>>无符号右移,高位的空位用0补

静态变量

  • 外部

    • static声明限定外部变量与函数,可以将对象的作用域限定为被编译源文件的剩余部分
    • 通过static限定外部对象,可以达到隐藏外部对象的目的。
    • 如果把函数名声明为static类型,则该函数名除了对该函数的声明所在的文件可见外,其他文件都无法访问。
  • 内部

    • static类型的内部变量,不管其所在函数是否被调用,都会存在。(一直占据存储空间;自动变量:随着函数的 调用/退出 而 存在/消失)

寄存器变量

register声明告诉编译器,它所声明的变量在程序中使用频率较高


结构(和java中的类 概念类似,只有变量没有函数)

关键字struct引入结构声明。

结构类型

关键字struct后面的名字是可选的,称为结构标记

  • 有结构标记

    1
    2
    3
    4
    5
    6
    struct point {
    int x;
    int y;
    };
    struct point pt = {320, 200}; //定义了一个struct point类型的变量pt
    struct point pt2 = {.y = 200, .x = 320};
  • 无结构标记

    1
    2
    3
    4
    struct {
    int x;
    int y;
    } p1, p2; //p1、p2都是无标记结构,里面有x和y

结构与函数

结构的合法操作只有几种:作为一个整体赋值和赋值(包括向函数传递参数以及从函数返回值),通过&运算符取地址,访问其成员。

例:函数makepoint,带有两个整型参数,并返回一个point类型的结构

1
2
3
4
5
6
struct point makepoint(int x, int y) {
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}

结构指针的使用频度非常高,为了使用方便,C语言提供了另一种简写方式。假设p是一个指向结构的指针,可以用p->结构成员的形式(等价于(*p).结构成员),引用相应的结构成员。

1
2
3
4
5
6
struct point* getStruct(struct point *p) {
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("%d %d", p->x, p->y);
return p;
}

C语言提供一个编译时的一元运算符sizeof,可以用来计算任一对象的长度

sizeof 对象sizeof(类型名)会返回一个整型值,等于指定对象或类型占用的存储空间字节数

类型定义 (typedef)

typedef用来建立新的 数据类型名
例如,声明typedef int Length;
将Length定义为与int具有同等意义的名字,Length与类型int完全相同。

1
2
3
4
5
6
7
8
typedef struct ADate {//ADate同样可以略去
int month;
int day;
int year;
} Date; //简化了复杂的名字

//也可正常构造结构后,按如下方式定义
typedef struct ADate Date;

动态分配内存

C99之前无法用变量作为数组定义的大小,当时如何解决该问题?

malloc函数:在需要时,向操作系统申请存储空间,需要#include <stdlib.h>

void* malloc(size_t size);

  • 向malloc申请的空间的大小以字节为单位
  • 返回的结果是void*,需要类型转换为需要的类型。例如int *a = (int*)malloc(n*sizeof(int))

因为程序中的某些地方可能不通过 malloc调用 申请空间,所以,malloc管理的空间不一定是连续的

释放空间

free(a);
malloc得到的空间一定要有free的习惯,只能free申请来的空间的首地址


常用函数

gets()

gets()函数包含于stdio.h头文件,会一直读取用户输入,直至换行为止;而scanf一直读至空格键


字符串函数

  • strlen 字符串长度
    size_t strlen (const char *s); 返回s的字符串长度,不包括结尾的0

  • strcmp 比较字符串
    int strcmp (const char *s1, char *s2); 比较字符串的大小,返回两者 第一个不同的字符的差值

  • strncmp 比较字符串

    int strncmp (const char *s1, const char *s2, size_t n) 比较字符串,n为比较的字符数量,若前n个字符相同,返回0

  • strcpy 复制
    char* strcpy(char *restrict dst, const char *restrict src); 把src的字符串复制给dst
    restrict表明src和dst不重叠,返回dst

    复制字符串的操作示例:

    1
    2
    char *dst = (char*)malloc(strlen(src)+1);
    strcpy(dst, src);

    memcpy复制

    void* memcpy (void*dest, const void *src, size_t n);

    strcpy相比,memcpy并不是遇到’\0’就结束,而是一定会拷贝完n个字节。

  • 字符串中找字符
    char* strchr(const char *s, int c);
    char* strrchr(const char *s, int c);
    返回NULL表示没有找到

  • 字符串中找字符串
    char* strstr(const char *s1, const char *s2);
    char* strcasestr(const char *s1, const char *s2); //忽略大小写 查找字符串

  • 连接字符串

    char *strcat(char *dest, const char *restrict src);将参数 src 字符串复制到参数 dest 所指的字符串尾部


标准库函数 qsort排序

C语言有qsort();C++有sort();Java有Arrays

qsort()声明在stdlib.h文件中。
void qsort(void *base,size_t nelem,size_t width,int (*cmp)(const void *,const void *));

  • base:
    要排序的数组

  • nmemb:
    数组中的元素数目

  • size:
    每个数组元素占用内存空间,可使用sizeof获得

  • cmp:
    比较两个数组元素的比较函数,返回值是int类型。比较函数的第一个参数值a与参数b,此函数需要自定义

    • 返回值 > 0, a 将被排在b后面;
    • 返回值 < 0, a 将被排在b前面;
    • 示例:
      1
      2
      3
      int cmp(const void *a, const void *b) { //升序
      return *(int *)a - *(long int *)b; //不能用'>'、'<'比较符(返回无负值)
      }

转换字符大小写

函数声明在<ctype.h>文件中:

int tolower(int c)

int toupper(int c)


枚举(enumeration)

  • 枚举是用关键字enum来声明的一种自定义的数据类型:

    enum 枚举类型名字 {标识符0, 标识符1, ..., 标识符n};

  • 枚举类型名字第一个字母最好大写,花括号中的标识符是常量符号,只使用大写字母,类型是int,值依次从0到n

    enum Color {RED, YELLOW, GREEN};

  • 主要应用:当需要一些可以排列起来的常量值时,定义枚举就是为了给这些常量值名字

  • 枚举类型可以跟上enum作为类型:void f(enum Color c);Color t = RED;

  • 声明枚举量的时候可以指定值:enum Color {RED = 1, YELLOW, GREEN = 5, BLUE};

    YELLOW的值为2,BLUE的值为6。

  • 不同的枚举常量可以取相同的整数值,但最好采用唯一值,有助于预防难以发现的逻辑错误。


链表

构造结点

1
2
3
4
typedef struct _node {
int value;
struct _node *next;
} Node;

构造链表

1
2
3
typedef struct {
Node *head;
} List;

全局变量

全局变量初始化

  • 没有做初始化的全局变量会得到0值
    • 指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量
  • 它们的初始化发生在main函数之前

如果函数内部存在与全局变量同名的变量,则全局变量被隐藏

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量,没有做初始化的静态变量会得到0值
  • 当函数离开的时候,静态本地变量会继续存在并保持其值
  • 静态本地变量的初始化只在第一次进入该函数时,以后进入函数会保持上次离开的值

静态本地变量是特殊的全局变量


编译预处理指令

  • #开头的是编译预处理指令

#define 用来定义宏

  • #define <名字> <值>(结尾没有分号,不是C语句)
  • 名字必须是一个单词,值可以是各种东西
  • 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值(文本替换)

  • 如果一个宏的值中有其他宏的名字,也会被替换
  • 如果宏的值超过一行,最后一行之前的行末需要加\
  • 宏的值后边的注释不会被当作宏的值的一部分
1
2
3
4
#define PI 3.14159
#define PI2 2*PI
#define PRT printf("%f ", PI); \
printf("%f\n", PI2)

预定义的宏

  • _LINE_ :当前行号
  • _FILE_ :文件路径
  • _DATE_ :日期
  • _TIME_ :时间

带参数的宏的原则

  • 一切都要括号

    • 整个值要括号
    • 参数出现的每个地方都要括号

    例:#define RADTODEG(x) ((x)*57.29578)

  • 可以带多个参数

    #define MIN(a,b) ((a)>(b)?(b):(a))

  • 也可以组合(嵌套)使用其他宏

#include 头文件

将included的文件的全部内容原封不动d地插入到所在位置,因此也不一定要在.c文件最前面#include

  • #include "xxx.h" (要求编译器首先在当前目录寻找该文件,如果没有,到编译器指定目录去找)
  • #include <xxx.h> (让编译器只在指定目录寻找)

把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型。#include和宏一样在是编译预处理指令。

#include不是用来引入库的stdio.h中只有printf等函数的原型,用来保证调用时给出的参数值是正确的类型。printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中。现在的C语言编译器默认会引入所有的标准库。

头文件

  • 在使用和定义这个函数的地方都应该#include这个头文件
  • 一般的做法是任何.c文件都有对应同名的.h文件,把所有对外公开的函数原型和全局变量的声明放进去

不对外公开的函数

  • 在函数前加上static使其成为只能在所在编译单元中被使用的函数
  • 在全局变量前面加上static使其成为只能在所在编译单元中被使用的全局变量

变量的声明

  • int i;是变量的定义(产生代码)
  • extern int i;是变量的声明(不产生代码)

标准头文件结构

  • 运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef __LIST_HEAD__
    #define __LIST_HEAD__

    #include "node.h"

    typedef struct _list {
    Node* head;
    Node* tail;
    } List;

    #endif

文件

格式化输出

%[flags][width][.prec][hlL]type

  • Flag
Flag 含义
- 左对齐
+ 强制显示符号(正数会显示+)
(space) 正数留空
0 0填充
  • width
width或prec 含义
number 最小字符数(总长,包括小数点后的位数)
* 下一个参数是字符数,例:printf("%*d, 6, 123");
.number 小数点后的位数
.* 下一个参数是小数点后的位数
  • hlL
hlL(类型修饰) 含义
hh 单个字节
h short
l long
ll long long
L long double
  • type
type 用于
i 或 d int
u unsigned int
o 八进制
x 十六进制
X 字母大写的十六进制
f 或 F float
e 或 E 指数
g float
G float
a 或 A 十六进制浮点数
c char
s 字符串
p 指针
n 读入/写出的个数

格式化输入

%[flag]type

  • flag
flag 含义
* 跳过
数字 最大字符数
hh char
h short
l long, double
ll long long
L long double
  • type
type 含义
d int
i 整数,也可以是16进制、8进制
u unsigned int
o 8进制
x 16进制
a, e, f, g float
c char
s 字符串
[…] 允许的字符(例:*[^,]是到,之前的所有字符都跳过)
p 指针

位运算

按位运算

  • &

    相同位上都为1,结果为1;否则结果为0

  • |

    相同位上至少一个是1,结果为1;否则结果为0

  • ~ 取反

    把1位变0,0位变1

  • ^ 异或

    如果两个位相等,结果为0;两个位不相等,结果为1。

    对一个变量用同一个值异或两次,变量不变(可用于加密)

移位运算

  • << 左移

    • i << j:i中所有的位向左移动j个位置,右边填入0

    • x <<= n 等价于x *= $2^n$

  • >> 右移

    • i >> j:i中所有的位向右移j个位置。unsigned类型,左边填入0;signed类型,左边填入原来的最高位
    • x >>= n 等价于x /= $2^n$
0%