最新文章

20260227 120039 Cpp 入门第六课

第六章:处理文字——字符串

你好!欢迎来到第六章!在前面的章节,我们学习了数字和数组。但生活中我们更多接触的是文字,比如名字、句子、文章。在C++中,文字数据叫做字符串。这一章我们会学习如何存储和操作字符串,包括古老的C风格字符串和更方便的C++ string 类。学完本章,你就能编写处理文字的程序了!


6.1 字符串程序范例

我们先来看一个简单的程序,它使用了C++中的 string 类型(最方便的一种字符串)。

#include <iostream>
#include <string>   // 使用string需要包含这个头文件
using namespace std;

int main() {
    string name;    // 定义一个字符串变量,名字叫name

    cout << "请输入你的名字:";
    cin >> name;    // 输入字符串(注意:不能有空格)

    cout << "你好," << name << "!欢迎学习C++字符串!" << endl;

    return 0;
}

运行示例(假设输入“小明”):

请输入你的名字:小明
你好,小明!欢迎学习C++字符串!

这个程序展示了字符串的基本输入输出。接下来我们会深入学习字符串的各种用法。


6.2 字符串的用法

6.2.1 字符的操作

字符串是由一个个字符组成的。在C++中,字符用 char 类型表示。我们先学会操作单个字符。

字符的定义和赋值
char ch1 = 'A';      // 单个字符用单引号括起来
char ch2 = 'b';
char ch3 = '5';
char ch4 = '!';
字符的输入输出
char ch;
cout << "请输入一个字符:";
cin >> ch;           // 输入一个字符
cout << "你输入的字符是:" << ch << endl;
字符的ASCII码

每个字符在计算机中都有一个对应的整数编码,叫做ASCII码。例如 'A' 的ASCII码是65,'a' 是97,'0' 是48。字符可以和整数进行运算。

char ch = 'A';
cout << (int)ch << endl;   // 输出65,将字符转为整数
cout << char(65) << endl;  // 输出A,将整数转为字符
字符的运算

因为字符本质上是整数,所以可以进行加减运算。

char ch = 'A';
ch = ch + 1;                // 变成 'B'
cout << ch << endl;

// 小写转大写
char lower = 'b';
char upper = lower - 32;    // 因为小写字母比大写字母ASCII码大32
cout << upper << endl;      // 输出'B'
常用字符判断(需要包含 <cctype> 头文件)

C++提供了许多判断字符类型的函数,非常方便。

函数 作用
isalpha(ch) 判断是否是字母
isdigit(ch) 判断是否是数字
islower(ch) 判断是否是小写字母
isupper(ch) 判断是否是大写字母
isspace(ch) 判断是否是空格
tolower(ch) 转换为小写
toupper(ch) 转换为大写
#include <iostream>
#include <cctype>   // 字符处理函数
using namespace std;

int main() {
    char ch;
    cout << "请输入一个字符:";
    cin >> ch;

    if (isalpha(ch)) {
        cout << ch << " 是字母" << endl;
        if (islower(ch)) {
            cout << "它是小写字母,大写形式是:" << char(toupper(ch)) << endl;
        } else {
            cout << "它是大写字母,小写形式是:" << char(tolower(ch)) << endl;
        }
    } else if (isdigit(ch)) {
        cout << ch << " 是数字" << endl;
    } else {
        cout << ch << " 是其他字符" << endl;
    }

    return 0;
}

6.2.2 字符数组

字符数组是C语言风格的字符串,就是用一个数组来存放多个字符,最后以 '\0' 结尾。

定义和初始化
char str1[10] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 手动加结束符
char str2[10] = "Hello";        // 自动在末尾加 '\0',最常用
char str3[] = "Hello";          // 编译器自动计算大小为6(包括'\0')
访问和遍历
char str[] = "Hello";
for (int i = 0; i < 5; i++) {
    cout << str[i] << " ";      // 输出 H e l l o
}
cout << endl;

// 也可以用while循环,直到遇到'\0'
int i = 0;
while (str[i] != '\0') {
    cout << str[i];
    i++;
}
修改字符数组中的元素
char str[] = "Hello";
str[0] = 'h';   // 修改第一个字符
cout << str;    // 输出 "hello"

注意:字符数组的大小一旦定义就不能改变,所以要保证足够大。

6.2.3 字符串的输入和输出

C风格字符串的输入
  • cin >> 输入,但遇到空格会停止。
  • cin.getline() 可以输入带空格的整行。
#include <iostream>
using namespace std;

int main() {
    char name[50];
    cout << "请输入你的名字(无空格):";
    cin >> name;                // 只能输入一个单词
    cout << "你好," << name << endl;

    // 清空输入缓冲区(因为后面要用getline,之前cin会留下换行符)
    cin.ignore();

    char sentence[100];
    cout << "请输入一句话(可带空格):";
    cin.getline(sentence, 100); // 读取整行,最多99个字符
    cout << "你输入的是:" << sentence << endl;

    return 0;
}
string类的输入
  • cin >> 输入,同样遇空格停止。
  • getline(cin, 字符串变量) 输入整行。
#include <iostream>
#include <string>
using namespace std;

int main() {
    string name, sentence;

    cout << "请输入你的名字:";
    cin >> name;
    cout << "你好," << name << endl;

    cin.ignore();   // 忽略输入缓冲区中的换行符

    cout << "请输入一句话:";
    getline(cin, sentence);   // 读取整行
    cout << "你输入的是:" << sentence << endl;

    return 0;
}

6.2.4 字符串结束符‘\0’

C风格字符串以 '\0'(空字符,ASCII码0)作为结束标志。它告诉程序字符串在哪里结束。

为什么需要 '\0'

因为字符数组的长度可能大于字符串的实际长度,如果没有结束符,程序就不知道到哪里是字符串的结尾。比如:

char str[10] = "Hello";   // 实际存储:H e l l o \0 ? ? ? ?

当输出 str 时,函数会从第一个字符开始一直输出直到遇到 '\0'。如果忘了加 '\0',可能会输出乱码直到遇到内存中的某个0。

手动添加 '\0'

如果自己逐个字符赋值,必须手动加上 '\0'

char str[5];
str[0] = 'C';
str[1] = '+';
str[2] = '+';
str[3] = '\0';   // 必须加!
cout << str;     // 输出 "C++"

如果忘记加,cout 会继续向后输出内存中的内容,直到偶然遇到0,可能导致程序崩溃或输出乱码。

6.2.5 字符串常用函数

C语言提供了一些处理字符串的函数,它们定义在 <cstring> 头文件中。注意这些函数都用于C风格字符串(字符数组)。

函数 作用
strlen(s) 返回字符串s的长度(不包括\0
strcpy(dest, src) 将src复制到dest
strcat(dest, src) 将src连接到dest的末尾
strcmp(s1, s2) 比较s1和s2,相等返回0,s1s2返回正数
strchr(s, c) 在s中查找字符c第一次出现的位置,返回指针
strstr(s1, s2) 在s1中查找子串s2,返回指针
示例代码
#include <iostream>
#include <cstring>   // 字符串函数
using namespace std;

int main() {
    char s1[20] = "Hello";
    char s2[20] = "World";
    char s3[40];

    // 求长度
    cout << "s1长度:" << strlen(s1) << endl;   // 5

    // 复制
    strcpy(s3, s1);   // s3 = "Hello"
    cout << "s3 = " << s3 << endl;

    // 连接
    strcat(s3, " ");  // 加一个空格
    strcat(s3, s2);   // s3 = "Hello World"
    cout << "连接后:" << s3 << endl;

    // 比较
    if (strcmp(s1, s2) == 0) {
        cout << "s1和s2相等" << endl;
    } else {
        cout << "s1和s2不相等" << endl;
    }

    return 0;
}

注意:使用这些函数时,要确保目标数组足够大,否则会溢出。

6.2.6 string类

C++提供了一个更安全、更方便的字符串类型:string。它包含在头文件 <string> 中。使用 string 类可以像普通变量一样操作字符串,不需要担心数组大小和结束符。

定义和初始化
#include <string>
string s1;                // 空字符串
string s2 = "Hello";      // 直接赋值
string s3("World");       // 构造函数
string s4 = s2;           // 复制
string s5 = s2 + " " + s3; // 连接
常用操作
  • 长度s.length()s.size()
  • 访问字符s[i],下标从0开始
  • 赋值s = "new value";
  • 连接:用 ++=
  • 比较:直接用 ==!=<> 等运算符
  • 输入cin >> sgetline(cin, s)
  • 输出cout << s
示例代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1 = "Hello";
    string s2 = "World";
    string s3;

    // 连接
    s3 = s1 + " " + s2;
    cout << "s3 = " << s3 << endl;   // Hello World

    // 长度
    cout << "s3的长度:" << s3.length() << endl;   // 11

    // 访问字符
    for (int i = 0; i < s3.length(); i++) {
        cout << s3[i] << " ";   // H e l l o   W o r l d
    }
    cout << endl;

    // 比较
    if (s1 == s2) {
        cout << "相等" << endl;
    } else {
        cout << "不相等" << endl;
    }

    // 子串
    string sub = s3.substr(6, 5);   // 从下标6开始取5个字符
    cout << "子串:" << sub << endl; // World

    // 查找
    int pos = s3.find("World");
    if (pos != string::npos) {
        cout << "找到World,位置在:" << pos << endl;
    }

    return 0;
}
string类的更多成员函数
  • s.substr(pos, len):返回从pos开始长度为len的子串。
  • s.find(str):查找子串,返回第一次出现的位置,如果找不到返回 string::npos
  • s.replace(pos, len, str):将从pos开始长度为len的子串替换为str。
  • s.insert(pos, str):在pos位置插入str。
  • s.erase(pos, len):删除从pos开始的len个字符。
  • s.empty():判断是否为空。
  • s.clear():清空字符串。

6.2.7 阶段性编程练习(字符串基础)

  1. 练习1:输入一个字符,判断它是字母、数字还是其他字符,并输出对应信息。
  2. 练习2:输入一行字符串(可含空格),统计其中字母、数字、空格的个数。
  3. 练习3:输入一个字符串,将其中的小写字母转为大写,大写字母转为小写,其他字符不变,输出结果。
  4. 练习4:用C风格字符串实现字符串反转(不使用string类的reverse函数)。
  5. 练习5:用string类实现:输入两个字符串,连接后输出,并输出长度。

6.3 编程实例讲解

这里我们通过几个综合实例,进一步巩固字符串的操作。

实例1:统计单词个数(使用string)

题目:输入一行英文句子,统计其中有多少个单词(单词之间用空格分隔)。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cout << "请输入一句英文:";
    getline(cin, s);   // 读取整行

    int count = 0;
    bool inWord = false;   // 标记是否正在一个单词中

    for (int i = 0; i < s.length(); i++) {
        if (s[i] != ' ') {          // 如果当前字符不是空格
            if (!inWord) {          // 并且之前不在单词中,说明新单词开始
                count++;
                inWord = true;
            }
        } else {                     // 遇到空格,表示单词结束
            inWord = false;
        }
    }

    cout << "单词个数:" << count << endl;
    return 0;
}

实例2:判断回文串

题目:输入一个字符串(忽略空格和标点,只考虑字母和数字),判断它是否是回文(正读反读一样,比如 “madam”、”12321”)。

#include <iostream>
#include <string>
#include <cctype>   // 字符处理函数
using namespace std;

int main() {
    string s;
    cout << "请输入一个字符串:";
    getline(cin, s);

    // 先过滤掉非字母数字字符,并转为小写
    string filtered;
    for (int i = 0; i < s.length(); i++) {
        if (isalnum(s[i])) {          // 如果是字母或数字
            filtered += tolower(s[i]); // 转为小写后加入
        }
    }

    // 判断回文
    bool isPalindrome = true;
    int len = filtered.length();
    for (int i = 0; i < len / 2; i++) {
        if (filtered[i] != filtered[len - 1 - i]) {
            isPalindrome = false;
            break;
        }
    }

    if (isPalindrome) {
        cout << "是回文" << endl;
    } else {
        cout << "不是回文" << endl;
    }

    return 0;
}

实例3:凯撒密码(加密)

题目:输入一个字符串和一个偏移量k,将字符串中的每个英文字母替换为字母表中后移k位的字母(循环,例如z后移1位变成a),其他字符不变,输出加密后的字符串。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    int k;
    cout << "请输入要加密的字符串:";
    getline(cin, s);
    cout << "请输入偏移量:";
    cin >> k;

    for (int i = 0; i < s.length(); i++) {
        char ch = s[i];
        if (ch >= 'a' && ch <= 'z') {
            ch = 'a' + (ch - 'a' + k) % 26;
        } else if (ch >= 'A' && ch <= 'Z') {
            ch = 'A' + (ch - 'A' + k) % 26;
        }
        s[i] = ch;
    }

    cout << "加密后的字符串:" << s << endl;
    return 0;
}

实例4:字符串排序(按字典序)

题目:输入若干个人名(字符串),按字典序从小到大排序后输出。

#include <iostream>
#include <string>
using namespace std;

int main() {
    const int N = 5;
    string names[N];
    cout << "请输入" << N << "个人名:" << endl;
    for (int i = 0; i < N; i++) {
        cin >> names[i];   // 假设名字不含空格
    }

    // 冒泡排序
    for (int i = 0; i < N - 1; i++) {
        for (int j = 0; j < N - 1 - i; j++) {
            if (names[j] > names[j + 1]) {   // string可以直接比较
                string temp = names[j];
                names[j] = names[j + 1];
                names[j + 1] = temp;
            }
        }
    }

    cout << "排序后的名字:" << endl;
    for (int i = 0; i < N; i++) {
        cout << names[i] << endl;
    }

    return 0;
}

6.4 第6章编程作业

恭喜你学完了字符串!现在来挑战几个综合题目,检验一下学习成果。

作业1:统计字符串中每个字母出现的次数

输入一行字符串(可含空格),统计其中26个英文字母(不区分大小写)各自出现的次数,并输出。忽略非字母字符。

作业2:简单密码验证

设定一个密码字符串(如 “C++2024”),让用户输入密码,最多允许输入3次,如果正确显示“欢迎”,否则显示“密码错误”并提示剩余次数,3次错误后显示“账户锁定”。

作业3:手机键盘数字映射

在老式手机键盘上,字母映射到数字:2:abc, 3:def, 4:ghi, 5:jkl, 6:mno, 7:pqrs, 8:tuv, 9:wxyz。输入一个单词(只含字母),输出对应的数字串。例如输入 “hello”,输出 “43556”。

作业4:字符串去重

输入一个字符串,去掉其中重复的字符(只保留第一次出现的字符),输出结果。例如输入 “hello world”,输出 “helo wrd”(注意空格也要保留,但重复的空格只保留一个?可以自己定义规则)。

作业5:单词反转

输入一句英文,将句子中的单词顺序反转,但单词内部的字母顺序不变。例如输入 “I love C++”,输出 “C++ love I”。提示:可以用字符串流或手动分割单词。

20260227 120004 Cpp 入门第五课

第五章:批量存储数据——数组

你好!欢迎来到第五章!在前面的章节,我们学会了用变量存储单个数据,比如一个年龄、一个成绩。但如果要存储全班50个人的成绩,难道要定义50个变量吗?那太麻烦了!这时候就需要数组——它可以一次性定义多个相同类型的变量,就像一排带编号的柜子,每个柜子里可以放一个数据。这一章我们就来学习如何使用数组。


5.1 一维数组

5.1.1 数组程序范例

先看一个简单的例子:定义一个能装5个整数的数组,然后给它们赋值并输出。

#include <iostream>
using namespace std;

int main() {
    int a[5];          // 定义一个数组,名字叫a,里面可以放5个整数

    // 给数组的每个元素赋值
    a[0] = 10;         // 第一个格子(下标0)放10
    a[1] = 20;         // 第二个格子(下标1)放20
    a[2] = 30;
    a[3] = 40;
    a[4] = 50;

    // 输出数组的每个元素
    cout << a[0] << " " << a[1] << " " << a[2] << " " << a[3] << " " << a[4] << endl;

    return 0;
}

运行结果

10 20 30 40 50

5.1.2 数组的用法

什么是数组?

数组就是一组相同类型的数据的集合,它们在内存中连续存放。每个数据叫做数组元素,通过下标(也叫索引)来访问。下标从0开始编号。

  • 定义格式:数据类型 数组名[元素个数];
    例如:int score[5]; 定义了一个名为score的数组,可以存5个整数。

  • 访问元素:数组名[下标],下标范围是0到元素个数-1。
    例如:score[0] = 98; 给第一个元素赋值。

数组的初始化

可以在定义时直接赋值:

int a[5] = {10, 20, 30, 40, 50};   // 全部初始化
int b[] = {1, 2, 3, 4, 5};          // 不写个数,编译器自动计算为5
int c[5] = {1, 2};                   // 只给前两个赋值,后面3个默认为0
使用循环遍历数组

数组通常和循环一起使用,因为可以用循环变量作为下标。

int a[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
    cout << a[i] << " ";   // 依次输出每个元素
}
数组的注意事项
  • 下标不能越界,比如定义 int a[5];,只能使用 a[0]a[4],使用 a[5] 会导致未定义行为(可能程序崩溃)。
  • 数组一旦定义,大小不能改变。

5.1.3 编程实例讲解

实例1:从键盘输入5个整数,求它们的和与平均值
#include <iostream>
using namespace std;

int main() {
    int a[5];
    int sum = 0;

    cout << "请输入5个整数:";
    for (int i = 0; i < 5; i++) {
        cin >> a[i];          // 输入存到数组
        sum += a[i];          // 累加
    }

    double avg = (double)sum / 5;   // 平均值
    cout << "和:" << sum << ",平均值:" << avg << endl;

    return 0;
}
实例2:找出数组中的最大值和最小值
#include <iostream>
using namespace std;

int main() {
    int a[5] = {34, 67, 12, 89, 45};
    int max = a[0];   // 假设第一个是最大值
    int min = a[0];   // 假设第一个是最小值

    for (int i = 1; i < 5; i++) {   // 从第二个开始比较
        if (a[i] > max) {
            max = a[i];
        }
        if (a[i] < min) {
            min = a[i];
        }
    }

    cout << "最大值:" << max << endl;
    cout << "最小值:" << min << endl;

    return 0;
}
实例3:数组元素逆序(把数组反过来)
#include <iostream>
using namespace std;

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int temp;

    // 交换对称位置的元素
    for (int i = 0; i < 5 / 2; i++) {
        temp = a[i];
        a[i] = a[4 - i];   // 4是最后一个元素的下标
        a[4 - i] = temp;
    }

    cout << "逆序后的数组:";
    for (int i = 0; i < 5; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

运行结果:5 4 3 2 1

5.1.4 阶段性编程练习

  1. 练习1:定义一个包含10个整数的数组,用循环给数组赋值为1到10,然后输出。
  2. 练习2:输入8个整数,存入数组,然后输出所有大于平均数的数。
  3. 练习3:输入10个整数,查找某个数(由用户输入)是否在数组中,如果在,输出它的位置(下标),否则输出“未找到”。
  4. 练习4:输入一个正整数n(≤20),再输入n个整数,将它们从小到大输出(可以先不管排序,直接输出,排序下一节学)。但这里可以先练习用两个循环选择最小输出,或者简单用冒泡排序提前体验。

5.2 数组排序

排序就是把一组数按从小到大(升序)或从大到小(降序)排列。我们这里学习最简单的冒泡排序

5.2.1 排序程序范例

#include <iostream>
using namespace std;

int main() {
    int a[5] = {34, 67, 12, 89, 45};
    int n = 5;

    // 冒泡排序
    for (int i = 0; i < n - 1; i++) {           // 外层循环控制比较的轮数
        for (int j = 0; j < n - 1 - i; j++) {   // 内层循环控制每轮比较的次数
            if (a[j] > a[j + 1]) {               // 如果前一个比后一个大,交换
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    cout << "排序后的数组:";
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

运行结果:12 34 45 67 89

5.2.2 数组排序的用法

冒泡排序的原理

想象有一排泡泡,轻的往上浮,重的往下沉。冒泡排序就是每次比较相邻的两个数,如果顺序不对(比如前大后小,我们要升序),就交换它们。这样每一轮都会把最大的数“沉”到最后。

  • 第一轮:从第一个元素开始,依次比较相邻的两个,如果前>后,交换。第一轮结束后,最大的数就到了最后。
  • 第二轮:再从第一个开始,比较到倒数第二个(因为最后一个已经最大了),把第二大的数放到倒数第二。
  • 重复n-1轮,所有数就排好了。
代码解释
  • 外层循环 i 控制轮数,一共需要 n-1 轮(因为最后剩下一个不用排)。
  • 内层循环 j 控制比较范围,每一轮比较的范围逐渐缩小,因为末尾已经排好的元素不用再比。所以 j 从0到 n-1-i。
  • 如果 a[j] > a[j+1],交换。
其他排序方法(拓展了解)
  • 选择排序:每一轮找到最小(或最大)的元素,放到前面。
  • 插入排序:像打扑克牌,每次把一张牌插入到已排好序的手牌中。

但初学者先掌握冒泡排序就好。

5.2.3 编程实例讲解

实例4:输入n个数,升序输出
#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入数字个数:";
    cin >> n;
    int a[100];   // 假设最多100个数

    cout << "请输入" << n << "个整数:";
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    // 冒泡排序
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    cout << "排序后:";
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}
实例5:降序排序(从大到小)

只需要把比较条件 a[j] > a[j+1] 改成 a[j] < a[j+1] 即可。

if (a[j] < a[j + 1]) {   // 如果前一个小于后一个,交换,让大的往前移
    // 交换
}
实例6:对字符串数组排序(了解)

数组也可以是字符串类型,比如 string names[5]; 排序方法和整数类似,直接用 >< 比较字符串(按字典序)。

5.2.4 阶段性编程练习

  1. 练习1:输入10个整数,用冒泡排序将它们从大到小输出。
  2. 练习2:输入n个学生的成绩,排序后输出,并输出最高分和最低分。
  3. 练习3:用选择排序实现升序排列(自学选择排序算法并实现)。
  4. 练习4:输入n个整数,去掉重复的数字后输出(提示:可以先排序,然后输出时跳过相邻相同的)。

5.3 二维数组

5.3.1 二维数组程序范例

二维数组就像一张表格,有行和列。比如定义一个3行4列的二维数组,并输出:

#include <iostream>
using namespace std;

int main() {
    int a[3][4] = {      // 3行4列
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 用双重循环输出每个元素
    for (int i = 0; i < 3; i++) {        // i控制行
        for (int j = 0; j < 4; j++) {    // j控制列
            cout << a[i][j] << "\t";     // \t 是制表符,对齐
        }
        cout << endl;                     // 每行结束换行
    }

    return 0;
}

运行结果

1   2   3   4
5   6   7   8
9   10  11  12

5.3.2 二维数组的用法

定义二维数组

格式:数据类型 数组名[行数][列数];
例如:int score[5][3]; 表示5行3列,可以存放5个学生3门课的成绩。

初始化
  • 按行初始化:
    cpp int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
  • 连续赋值(会自动按行填):
    cpp int a[2][3] = {1, 2, 3, 4, 5, 6}; // 等价于上面
  • 部分初始化:未指定的元素默认为0。
访问元素

使用两个下标:数组名[行下标][列下标],行和列都从0开始。
例如:a[1][2] 表示第2行第3列的元素(在数学中常称为第2行第3列,但在编程中下标从0开始,所以实际是第2行第3个)。

遍历二维数组

几乎总是用嵌套循环:外层循环遍历行,内层循环遍历列。

5.3.3 编程实例讲解

实例7:输入一个3×4矩阵,求所有元素的和
#include <iostream>
using namespace std;

int main() {
    int a[3][4];
    int sum = 0;

    cout << "请输入3行4列的矩阵:" << endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a[i][j];
            sum += a[i][j];
        }
    }

    cout << "所有元素的和:" << sum << endl;
    return 0;
}
实例8:求矩阵中的最大值及其位置
#include <iostream>
using namespace std;

int main() {
    int a[3][4] = {
        {34, 56, 12, 89},
        {23, 45, 67, 90},
        {11, 22, 33, 44}
    };
    int max = a[0][0];
    int max_i = 0, max_j = 0;   // 记录最大值的位置

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (a[i][j] > max) {
                max = a[i][j];
                max_i = i;
                max_j = j;
            }
        }
    }

    cout << "最大值:" << max << ",位于第" << max_i+1 << "行第" << max_j+1 << "列" << endl;
    return 0;
}
实例9:矩阵转置(行列互换)

假设有一个3行2列的矩阵,转置后变成2行3列。

#include <iostream>
using namespace std;

int main() {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int b[2][3];   // 转置后的矩阵

    // 转置:b[j][i] = a[i][j]
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            b[j][i] = a[i][j];
        }
    }

    // 输出转置后的矩阵
    cout << "转置后的矩阵:" << endl;
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

运行结果

转置后的矩阵:
1 3 5
2 4 6

5.3.4 阶段性编程练习

  1. 练习1:定义一个4×4的二维数组,用循环赋值为1到16,然后按矩阵形式输出。
  2. 练习2:输入一个3×3矩阵,计算主对角线(从左上到右下)上元素的和。
  3. 练习3:输入两个2×3矩阵,求它们的和(对应位置相加),输出结果矩阵。
  4. 练习4:输入一个5×5矩阵,判断它是否关于主对角线对称(即转置后等于原矩阵)。

5.4 第5章编程作业

恭喜你学完了数组!现在来挑战几个综合题目,综合运用一维、二维数组和排序。

作业1:成绩统计系统

输入一个班的学生人数n(≤30),再输入每个学生的姓名和5门课的成绩。要求:
- 计算每个学生的总分和平均分
- 按总分从高到低排序,并输出排名、姓名、总分、平均分
- 输出每门课的平均分(全班平均)

提示:可以用多个一维数组,或者结构体(但还没学结构体,可以用平行数组:名字数组、总分数组等,排序时同时交换名字和总分)。或者先简单做,只处理成绩。

作业2:矩阵乘法

输入两个矩阵,第一个是m×n,第二个是n×p,计算它们的乘积并输出。m,n,p都不超过10。

作业3:约瑟夫问题

有n个人围成一圈,从第1个人开始报数,数到m的人出列,然后从下一个人继续报数,直到所有人都出列。输出出列顺序。n和m由用户输入。(提示:可以用数组标记是否出列,用循环模拟)

作业4:统计单词

输入一行英文句子(包含空格),统计其中有多少个单词,并输出每个单词的长度。例如输入 “I love C++ programming”,输出单词个数4,以及每个单词的长度(1 4 3 11)。提示:可以用字符串数组,但还没学,可以用字符数组,用循环判断空格。

作业5:杨辉三角(二维数组版)

用二维数组存储杨辉三角的前n行(n由用户输入),然后输出。杨辉三角每个数等于它上方两数之和。

20260227 085717 Cpp 入门第四课

第四章:让程序重复做事——循环结构

你好!欢迎来到第四章!在前面的章节,我们学会了让程序做判断。但是,如果想让程序重复做同一件事很多次,比如输出100遍“你好”,难道要写100行cout吗?当然不用!这时候就需要循环。循环就像跑步,一圈一圈重复,直到达到目标才停下。这一章我们会学习三种循环:forwhiledo...while,还有控制循环的小技巧。


4.1 for循环

4.1.1 for循环程序范例

for 循环就像体育老师喊口令:“从第1个同学开始,到第10个同学结束,每个同学报数!” 我们来看一个程序,输出1到10:

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 10; i++) {
        cout << i << " ";
    }
    return 0;
}

运行结果

1 2 3 4 5 6 7 8 9 10

4.1.2 for循环的用法

for 循环的格式:

for (初始化; 条件; 更新) {
    // 循环体:要重复执行的代码
}
  • 初始化:在循环开始前执行一次,通常用来定义循环变量并赋初值,比如 int i = 1
  • 条件:每次循环开始前判断,如果为真,就执行循环体;如果为假,就退出循环。比如 i <= 10
  • 更新:每次循环体执行完后执行,通常用来改变循环变量的值,比如 i++(i增加1)。

执行顺序
1. 执行初始化(只一次)
2. 判断条件 → 如果真,进入循环体;如果假,结束循环
3. 执行循环体
4. 执行更新
5. 回到第2步

循环变量变化过程

比如上面例子:
- 初始 i=1,判断 1<=10 真 → 输出1,然后 i++ 变成2
- 判断 2<=10 真 → 输出2,i++ 变3
- … 直到 i=10 输出10,i++ 变11
- 判断 11<=10 假 → 退出循环

4.1.3 编程实例讲解

实例1:求1到100的和
#include <iostream>
using namespace std;

int main() {
    int sum = 0;          // 累加器,初始为0
    for (int i = 1; i <= 100; i++) {
        sum = sum + i;    // 把i加到sum上
    }
    cout << "1+2+...+100 = " << sum << endl;
    return 0;
}

运行结果:5050

实例2:输出偶数

题目:输出1到20之间的所有偶数。

#include <iostream>
using namespace std;

int main() {
    for (int i = 2; i <= 20; i += 2) {   // i每次增加2
        cout << i << " ";
    }
    return 0;
}

运行结果

2 4 6 8 10 12 14 16 18 20
实例3:倒序输出

题目:输出10到1。

#include <iostream>
using namespace std;

int main() {
    for (int i = 10; i >= 1; i--) {
        cout << i << " ";
    }
    return 0;
}

运行结果

10 9 8 7 6 5 4 3 2 1

4.1.4 阶段性编程练习

  1. 练习1:用for循环输出1到50,每行5个数。(提示:可以用if (i % 5 == 0) cout << endl;换行)
  2. 练习2:求1到100所有奇数的和。
  3. 练习3:输入一个整数n,计算n的阶乘(n! = 1×2×3×…×n)。
  4. 练习4:输出所有的“水仙花数”。水仙花数是指一个三位数,其各位数字的立方和等于该数本身,如153 = 1³+5³+3³。(提示:用循环遍历100-999,分离个十百位判断)

4.2 while循环

4.2.1 while循环程序范例

while 循环就像“当条件满足时,就一直做某事”。比如“当还有糖果时,就吃一颗”。看这个例子,输出1到10:

#include <iostream>
using namespace std;

int main() {
    int i = 1;           // 初始化
    while (i <= 10) {    // 条件
        cout << i << " ";
        i++;             // 更新
    }
    return 0;
}

运行结果:和for循环一样。

4.2.2 while循环的用法

格式:

while (条件) {
    // 循环体
}
  • 先判断条件,如果为真,执行循环体;然后再次判断,直到条件为假退出。
  • 注意:循环体内要有改变条件的语句,否则会变成死循环(无限循环)。

4.2.3 编程实例讲解

实例4:计算1到n的和(n由用户输入)
#include <iostream>
using namespace std;

int main() {
    int n, i = 1, sum = 0;
    cout << "请输入一个整数:";
    cin >> n;

    while (i <= n) {
        sum += i;   // 等价于 sum = sum + i
        i++;
    }
    cout << "1+2+...+" << n << " = " << sum << endl;
    return 0;
}
实例5:统计位数

题目:输入一个正整数,统计它是几位数。比如输入12345,输出5。

#include <iostream>
using namespace std;

int main() {
    int num, count = 0;
    cout << "请输入一个正整数:";
    cin >> num;

    while (num > 0) {
        num = num / 10;   // 去掉最后一位
        count++;          // 位数加1
    }
    cout << "它是" << count << "位数" << endl;
    return 0;
}

解释:每次除以10,就减少一位,直到变成0。

4.2.4 阶段性编程练习

  1. 练习1:用while循环输出1到50的平方数(1², 2², … 50²)。
  2. 练习2:输入一个整数,倒序输出它(比如输入123,输出321)。不能用数组,只能用循环。
  3. 练习3:求两个数的最大公约数(用辗转相除法,提示:用while循环,条件是b!=0,循环内求余并交换)。
  4. 练习4:猜数字游戏(用while循环,直到猜中为止)。随机生成一个1-100的数,用户猜,提示“大了”“小了”,直到猜中,统计猜的次数。

4.3 do…while循环

4.3.1 do…while循环程序范例

do...while 循环和 while 类似,但它是先执行一次循环体,再判断条件。就像“先做一次,然后看是否要继续做”。比如:

#include <iostream>
using namespace std;

int main() {
    int i = 1;
    do {
        cout << i << " ";
        i++;
    } while (i <= 10);
    return 0;
}

4.3.2 do…while循环的用法

格式:

do {
    // 循环体
} while (条件);
  • 先执行一次循环体,然后判断条件。如果条件为真,继续下一次循环;如果为假,退出。
  • 注意:最后的分号不能少。
  • 特点:至少执行一次循环体,即使条件一开始为假。
对比while
  • while:先判断,可能一次都不执行。
  • do...while:先执行,至少执行一次。

4.3.3 编程实例讲解

实例6:输入密码,直到正确

题目:假设密码是123456,让用户输入密码,如果错误就提示重新输入,直到正确。

#include <iostream>
using namespace std;

int main() {
    int password, correct = 123456;
    do {
        cout << "请输入密码:";
        cin >> password;
        if (password != correct) {
            cout << "密码错误,请重新输入。" << endl;
        }
    } while (password != correct);

    cout << "密码正确,欢迎进入系统!" << endl;
    return 0;
}
实例7:求平均分(至少输入一次)

题目:输入若干个成绩,以-1结束,计算平均分(保证至少输入一个有效成绩)。

#include <iostream>
using namespace std;

int main() {
    int score, sum = 0, count = 0;
    do {
        cout << "请输入成绩(-1结束):";
        cin >> score;
        if (score != -1) {
            sum += score;
            count++;
        }
    } while (score != -1);

    if (count > 0) {
        double avg = (double)sum / count;
        cout << "平均分:" << avg << endl;
    }
    return 0;
}

4.3.4 阶段性编程练习

  1. 练习1:用do…while循环输出1到10的立方。
  2. 练习2:编写一个菜单程序,显示选项,让用户选择,直到用户选择退出。(类似:1.开始游戏 2.设置 3.退出,输入3退出)
  3. 练习3:输入一个正整数,用do…while循环判断它是否是素数(提示:用i从2到sqrt(n)试除,一旦整除就标记不是素数)。
  4. 练习4:模拟ATM取款,初始余额1000元,每次输入取款金额,如果余额不足提示,直到输入0退出。

4.4 continue和break

4.4.1 continue和break程序范例

有时候我们需要在循环中间提前结束本次循环或整个循环。break 用来跳出整个循环,continue 用来跳过本次循环剩下的部分,直接进入下一次循环。

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            break;    // 当i=5时,跳出整个循环
        }
        cout << i << " ";
    }
    cout << endl;

    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            continue; // 当i=5时,跳过输出,继续下一次循环
        }
        cout << i << " ";
    }
    return 0;
}

运行结果

1 2 3 4 
1 2 3 4 6 7 8 9 10

4.4.2 continue和break的用法

  • break:立即终止当前循环(for、while、do…while、switch),程序继续执行循环后面的代码。
  • continue:跳过本次循环中剩余的代码,直接进入下一次循环(对于for循环,会先执行更新部分,再判断条件)。
使用场景
  • break:当找到想要的结果后,没必要继续循环了。
  • continue:当遇到某些不需要处理的情况时,跳过本次,继续处理下一个。

4.4.3 编程实例讲解

实例8:找出第一个能被7整除的数

题目:从1开始找,找到第一个能被7整除的数就停止。

#include <iostream>
using namespace std;

int main() {
    int i = 1;
    while (true) {   // 无限循环,用break退出
        if (i % 7 == 0) {
            cout << "第一个能被7整除的数是:" << i << endl;
            break;
        }
        i++;
    }
    return 0;
}
实例9:输出1-20中不能被3整除的数
#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 20; i++) {
        if (i % 3 == 0) {
            continue;   // 跳过输出
        }
        cout << i << " ";
    }
    return 0;
}

4.4.4 阶段性编程练习

  1. 练习1:输出1-100中所有能被5整除但不能被3整除的数。
  2. 练习2:求两个数的最大公约数,用循环和break优化(找到即停止)。
  3. 练习3:输入一个正整数,判断它是否是素数(用break优化,一旦发现整除就跳出循环)。
  4. 练习4:模拟一个简单的加法考试,随机出10道题,每道题答对加10分,答错可以继续做下一题,最后显示得分。如果中途输入-1,则提前退出考试。

4.5 嵌套循环

4.5.1 嵌套循环程序范例

循环里面再套循环,就是嵌套循环。最常见的是打印图形。比如打印一个3行5列的矩形:

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 3; i++) {        // 控制行数
        for (int j = 1; j <= 5; j++) {    // 控制列数
            cout << "* ";
        }
        cout << endl;                     // 每行结束后换行
    }
    return 0;
}

运行结果

* * * * * 
* * * * * 
* * * * * 

4.5.2 嵌套循环的用法

  • 外层循环执行一次,内层循环会完整执行一轮。
  • 常用于处理二维结构,如打印图形、遍历表格等。
  • 注意循环变量的作用域和命名,通常用 i, j, k 表示。
执行过程:

外层 i=1,内层 j 从1到5输出5个星号,然后换行;
外层 i=2,内层 j 再从1到5输出5个星号,换行;
外层 i=3,同样。

4.5.3 编程实例讲解

实例10:打印直角三角形

题目:输入行数n,打印一个直角三角形,第一行1个,第二行2个,…第n行n个*。

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入行数:";
    cin >> n;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {   // 每行输出i个星号
            cout << "* ";
        }
        cout << endl;
    }
    return 0;
}

运行示例(n=5):

* 
* * 
* * * 
* * * * 
* * * * * 
实例11:打印九九乘法表
#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            cout << j << "×" << i << "=" << i * j << "\t"; // \t是制表符,用于对齐
        }
        cout << endl;
    }
    return 0;
}
实例12:冒泡排序(简单介绍)

虽然还没学数组,但可以先看看嵌套循环的威力:排序。这里用最简单的冒泡排序对5个数排序。

#include <iostream>
using namespace std;

int main() {
    int a[5] = {5, 3, 8, 1, 2};   // 数组,暂时理解为5个盒子
    int n = 5;

    // 冒泡排序
    for (int i = 0; i < n - 1; i++) {           // 外层控制比较轮数
        for (int j = 0; j < n - 1 - i; j++) {   // 内层控制每轮比较次数
            if (a[j] > a[j + 1]) {               // 如果前面的比后面的大,交换
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 输出排序后的结果
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    return 0;
}

运行结果:1 2 3 5 8

4.5.4 阶段性编程练习

  1. 练习1:打印倒直角三角形:第一行n个,第二行n-1个,…最后一行1个*。
  2. 练习2:打印菱形(输入奇数行数,打印菱形图案)。
  3. 练习3:输出100以内的所有素数(用嵌套循环,外层遍历2-100,内层判断每个数是否是素数)。
  4. 练习4:模拟一个简单的“猜数字”游戏升级版:程序随机生成一个4位数(每位不重复),用户猜,每次给出提示:数字正确且位置正确的个数,数字正确但位置不对的个数。用嵌套循环实现比较。

4.6 第4章编程作业

恭喜你学完了循环!现在来挑战几个综合题目,检验一下学习成果。

作业1:斐波那契数列

斐波那契数列:1, 1, 2, 3, 5, 8, 13, … 从第三项开始,每一项等于前两项之和。输入一个正整数n,输出前n项。

作业2:百钱百鸡问题

公鸡5元一只,母鸡3元一只,小鸡1元三只。用100元买100只鸡,问公鸡、母鸡、小鸡各多少只?用循环枚举所有可能。

作业3:求完数

完数是指一个数恰好等于它的因子之和(不包括自身)。例如6 = 1+2+3。找出1000以内的所有完数。

作业4:猜数字游戏(带难度选择)

实现一个猜数字游戏,可以选择难度:
- 简单:1-50,猜10次
- 中等:1-100,猜7次
- 困难:1-200,猜5次
每次提示猜大了还是猜小了,如果次数用完还没猜中,游戏结束。

作业5:打印杨辉三角

输入行数n,打印杨辉三角(等腰三角形格式)。例如n=5:

    1
   1 1
  1 2 1
 1 3 3 1
1 4 6 4 1

提示:可以用二维数组或组合数公式,但这里要求只用循环(需要一点数学知识,可以选做)。


好了,第四章的内容就到这里!你已经学会了如何让程序重复做事,这是编程中非常强大的能力。加油!🚀

20260227 085102 Cpp 入门第三课

第三章:让程序学会判断——分支结构

你好!欢迎来到第三章!在前两章,我们学会了让计算机输入、输出和计算。但程序只会按顺序执行,就像一条直线。如果想让程序根据不同情况做出不同反应,就需要用到分支结构。这一章我们会学习如何让程序“动脑筋”,根据条件决定做什么。就像你在生活中,如果下雨就带伞,如果晴天就去公园,程序也可以这样!


3.1 if语句

3.1.1 if语句程序范例

最简单的判断就是:如果……就……。看看这个程序,它判断你的年龄是否满18岁。

#include <iostream>
using namespace std;

int main() {
    int age;
    cout << "请输入你的年龄:";
    cin >> age;

    if (age >= 18) {
        cout << "你已经成年了!" << endl;
    }

    cout << "程序结束。" << endl;
    return 0;
}

运行结果示例1(输入20):

请输入你的年龄:20
你已经成年了!
程序结束。

运行结果示例2(输入15):

请输入你的年龄:15
程序结束。

3.1.2 if语句的用法

if 语句的格式:

if (条件) {
    // 条件为真时执行的语句
}
  • 条件:通常是一个比较表达式,比如 age >= 18score == 100。条件的结果要么是(true),要么是(false)。
  • 如果条件为真,就执行大括号 {} 里的代码;如果为假,就跳过这些代码,继续执行后面的语句。
  • 如果大括号里只有一条语句,可以省略大括号,但初学者建议都加上,避免出错。
比较运算符
运算符 含义 例子
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b

注意:判断是否相等要用两个等号 ==,一个等号 = 是赋值,千万不能搞混!

3.1.3 编程实例讲解

实例1:判断奇偶数

题目:输入一个整数,如果它是偶数,就输出“偶数”。

#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;

    if (num % 2 == 0) {   // 能被2整除就是偶数
        cout << num << " 是偶数" << endl;
    }

    return 0;
}
实例2:判断是否及格

题目:输入一门课的成绩(0-100),如果大于等于60,输出“及格”。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "恭喜,及格了!" << endl;
    }

    return 0;
}

3.1.4 阶段性编程练习

  1. 练习1:输入一个整数,如果它是负数,输出“这是一个负数”。
  2. 练习2:输入一个整数,如果它能被5整除,输出“能被5整除”。
  3. 练习3:输入一个字符,如果它是大写字母(’A’到’Z’),输出“大写字母”。(提示:字符比较可以用 ch >= 'A' && ch <= 'Z',但逻辑运算符还没学,可以先不练,或者用后面知识。)
  4. 练习4:输入两个整数,如果第一个数大于第二个数,输出“第一个数更大”。

3.2 if…else语句

3.2.1 if…else程序范例

if 只能处理条件为真的情况,但如果条件为假也想做点什么,就需要 if...else,就像“如果……就……否则……”。

#include <iostream>
using namespace std;

int main() {
    int age;
    cout << "请输入你的年龄:";
    cin >> age;

    if (age >= 18) {
        cout << "你已经成年了,可以看电影!" << endl;
    } else {
        cout << "你还未成年,需要家长陪同。" << endl;
    }

    return 0;
}

运行示例(输入15):

请输入你的年龄:15
你还未成年,需要家长陪同。

3.2.2 if…else语句的用法

格式:

if (条件) {
    // 条件为真时执行的代码
} else {
    // 条件为假时执行的代码
}
  • ifelse 是成对出现的。
  • 条件为真,执行 if 后面的代码块;条件为假,执行 else 后面的代码块。
  • 两者只会执行其中一个。

3.2.3 编程实例讲解

实例3:判断奇偶,输出不同信息
#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;

    if (num % 2 == 0) {
        cout << num << " 是偶数" << endl;
    } else {
        cout << num << " 是奇数" << endl;
    }

    return 0;
}
实例4:判断是否通过考试
#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "通过考试!" << endl;
    } else {
        cout << "未通过,继续努力!" << endl;
    }

    return 0;
}

3.2.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是否大于0,输出“正数”或“非正数”(包括0和负数)。
  2. 练习2:输入一个年份,判断它是否是闰年。闰年条件:能被4整除但不能被100整除,或者能被400整除。(提示:条件较复杂,可以先留到学了逻辑运算再做,或者直接给出条件。)
  3. 练习3:输入一个整数,判断它是否是7的倍数,输出相应信息。
  4. 练习4:输入两个整数,输出较大的那个数。

3.3 分支的嵌套

3.3.1 分支嵌套程序范例

有时候,我们需要在一个判断里面再做判断,就像“如果下雨,就带伞;如果雨很大,就穿雨衣”。这就是分支嵌套

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入你的成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "及格了!";
        if (score >= 90) {
            cout << "而且成绩优秀!" << endl;
        } else {
            cout << "继续加油!" << endl;
        }
    } else {
        cout << "不及格,要努力了!" << endl;
    }

    return 0;
}

运行示例(输入95):

及格了!而且成绩优秀!

3.3.2 分支嵌套的用法

  • ifelse 的代码块里,可以再放其他的 if...else 语句。
  • 嵌套的层数不要太多,否则代码会变得难读。一般两层就够用了。

注意else 总是和它上面最近的 if 配对。为了避免混乱,一定要用好大括号 {}

3.3.3 编程实例讲解

实例5:根据分数评定等级

题目:输入成绩(0-100),输出等级:
- 90-100:优秀
- 80-89:良好
- 70-79:中等
- 60-69:及格
- 0-59:不及格

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        if (score >= 90) {
            cout << "优秀" << endl;
        } else if (score >= 80) {   // 这里用了else if,是多重选择,但也可以嵌套
            cout << "良好" << endl;
        } else if (score >= 70) {
            cout << "中等" << endl;
        } else {
            cout << "及格" << endl;
        }
    } else {
        cout << "不及格" << endl;
    }

    return 0;
}

这个例子其实混合了嵌套和 else if,我们会在下一节正式学习多重选择。

实例6:判断三个数中的最大值
#include <iostream>
using namespace std;

int main() {
    int a, b, c;
    cout << "请输入三个整数:";
    cin >> a >> b >> c;

    if (a >= b) {
        if (a >= c) {
            cout << "最大值是:" << a << endl;
        } else {
            cout << "最大值是:" << c << endl;
        }
    } else {
        if (b >= c) {
            cout << "最大值是:" << b << endl;
        } else {
            cout << "最大值是:" << c << endl;
        }
    }

    return 0;
}

3.3.4 阶段性编程练习

  1. 练习1:输入三个整数,输出最小值。(参考上面的最大值)
  2. 练习2:输入一个年份,判断它是否是闰年,并输出对应的信息。如果是闰年,再判断它是否是世纪闰年(能被400整除)。
  3. 练习3:输入一个整数x,求分段函数的值:
    - 当 x > 0 时,y = 2x + 1
    - 当 x = 0 时,y = 0
    - 当 x < 0 时,y = x - 1
    输出y的值。
  4. 练习4:模拟自动售货机:输入金额(整数),如果金额大于等于商品价格(比如5元),则输出“购买成功”,并找零;否则输出“金额不足”。(可以嵌套判断,比如金额足够时再判断是否恰好等值)

3.4 多重选择分支

3.4.1 多重选择分支程序范例

当有多个条件需要依次判断时,可以用 if...else if...else 结构。就像成绩分等级一样。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 90) {
        cout << "优秀" << endl;
    } else if (score >= 80) {
        cout << "良好" << endl;
    } else if (score >= 70) {
        cout << "中等" << endl;
    } else if (score >= 60) {
        cout << "及格" << endl;
    } else {
        cout << "不及格" << endl;
    }

    return 0;
}

3.4.2 多重选择分支的用法

格式:

if (条件1) {
    // 条件1为真执行
} else if (条件2) {
    // 条件1为假且条件2为真执行
} else if (条件3) {
    // 条件1、2为假且条件3为真执行
} else {
    // 所有条件都为假执行
}
  • 程序从上往下判断,遇到第一个为真的条件,就执行对应的代码块,然后跳出整个 if...else if...else 结构。
  • 最后的 else 是可选的,表示所有条件都不满足时做什么。

3.4.3 编程实例讲解

实例7:根据月份判断季节

题目:输入月份(1-12),输出对应的季节:
- 3-5月:春季
- 6-8月:夏季
- 9-11月:秋季
- 12,1,2月:冬季

#include <iostream>
using namespace std;

int main() {
    int month;
    cout << "请输入月份:";
    cin >> month;

    if (month >= 3 && month <= 5) {
        cout << "春季" << endl;
    } else if (month >= 6 && month <= 8) {
        cout << "夏季" << endl;
    } else if (month >= 9 && month <= 11) {
        cout << "秋季" << endl;
    } else if (month == 12 || month == 1 || month == 2) {
        cout << "冬季" << endl;
    } else {
        cout << "月份输入错误" << endl;
    }

    return 0;
}
实例8:简单的计算器(加减乘除)
#include <iostream>
using namespace std;

int main() {
    double a, b;
    char op;   // 运算符
    cout << "请输入表达式(如 3 + 5):";
    cin >> a >> op >> b;

    if (op == '+') {
        cout << a + b << endl;
    } else if (op == '-') {
        cout << a - b << endl;
    } else if (op == '*') {
        cout << a * b << endl;
    } else if (op == '/') {
        if (b != 0) {
            cout << a / b << endl;
        } else {
            cout << "除数不能为0" << endl;
        }
    } else {
        cout << "不支持的运算符" << endl;
    }

    return 0;
}

3.4.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是正数、负数还是零。
  2. 练习2:输入一个0-6的数字,输出对应的星期几(0代表星期日,1代表星期一,以此类推)。
  3. 练习3:输入一个年份和月份,输出该月的天数(考虑闰年2月)。
  4. 练习4:编写一个程序,根据用户输入的消费金额,计算折扣后的应付金额:
    - 满100元打9折
    - 满200元打8折
    - 满300元打7折
    - 其他情况无折扣

3.5 switch语句

3.5.1 switch语句程序范例

当判断的条件是整型字符型固定值时,用 switch 语句更清晰。比如根据数字输出星期几。

#include <iostream>
using namespace std;

int main() {
    int day;
    cout << "请输入星期几(1-7):";
    cin >> day;

    switch (day) {
        case 1:
            cout << "星期一" << endl;
            break;
        case 2:
            cout << "星期二" << endl;
            break;
        case 3:
            cout << "星期三" << endl;
            break;
        case 4:
            cout << "星期四" << endl;
            break;
        case 5:
            cout << "星期五" << endl;
            break;
        case 6:
            cout << "星期六" << endl;
            break;
        case 7:
            cout << "星期日" << endl;
            break;
        default:
            cout << "输入错误,请输入1-7之间的数字" << endl;
    }

    return 0;
}

3.5.2 switch语句的用法

格式:

switch (表达式) {
    case 常量1:
        语句;
        break;
    case 常量2:
        语句;
        break;
    ...
    default:
        语句;
}
  • 表达式的结果必须是整型(int、char等),不能是浮点型或字符串。
  • case 常量:常量必须是固定的值,不能是变量。
  • 程序会找到与表达式值相等的 case,然后从那里开始执行,直到遇到 breakswitch 结束。
  • break 用来跳出 switch,如果不写 break,会继续执行下一个 case 的代码(这叫做“穿透”)。
  • default 是可选的,当所有 case 都不匹配时执行。

3.5.3 编程实例讲解

实例9:根据成绩等级给出评语
#include <iostream>
using namespace std;

int main() {
    char grade;
    cout << "请输入成绩等级(A、B、C、D):";
    cin >> grade;

    switch (grade) {
        case 'A':
            cout << "优秀,继续努力!" << endl;
            break;
        case 'B':
            cout << "良好,再接再厉!" << endl;
            break;
        case 'C':
            cout << "中等,要加油!" << endl;
            break;
        case 'D':
            cout << "及格边缘,需加强!" << endl;
            break;
        default:
            cout << "无效等级" << endl;
    }

    return 0;
}
实例10:简易四则运算(用switch)
#include <iostream>
using namespace std;

int main() {
    double a, b;
    char op;
    cout << "请输入表达式(如 3+5):";
    cin >> a >> op >> b;

    switch (op) {
        case '+':
            cout << a + b << endl;
            break;
        case '-':
            cout << a - b << endl;
            break;
        case '*':
            cout << a * b << endl;
            break;
        case '/':
            if (b != 0)
                cout << a / b << endl;
            else
                cout << "除数不能为0" << endl;
            break;
        default:
            cout << "不支持的运算符" << endl;
    }

    return 0;
}

3.5.4 阶段性编程练习

  1. 练习1:输入一个数字1-12,输出对应的月份英文名(January, February…)。
  2. 练习2:输入一个字符,判断它是元音字母(a, e, i, o, u,包括大小写)还是辅音字母。(提示:多个case可以共用一段代码,比如 case 'a': case 'e': ...
  3. 练习3:模拟一个简单的菜单:
    - 输入1:显示“开始游戏”
    - 输入2:显示“加载存档”
    - 输入3:显示“退出游戏”
    - 其他:显示“无效选项”
  4. 练习4:输入两个整数和一个运算符(+、-、*、/、%),用switch完成运算。

3.6 逻辑运算

3.6.1 逻辑运算程序范例

有时候一个条件需要多个判断组合,比如“成绩在80到90之间”需要同时满足大于等于80且小于等于90。这就需要逻辑运算符。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 80 && score <= 90) {   // 并且
        cout << "成绩良好" << endl;
    }

    if (score < 60 || score > 100) {    // 或者
        cout << "成绩异常" << endl;
    }

    if (!(score >= 60)) {                // 非
        cout << "不及格" << endl;
    }

    return 0;
}

3.6.2 逻辑运算的用法

运算符 名称 含义 例子
&& 两边都为真,结果才为真 a>0 && a<10
|| 两边至少一个为真,结果为真 a==0 || b==0
! 取反,真变假,假变真 !(a>b)

优先级! 最高,然后是 &&,最后是 ||。可以用括号改变顺序。

真值表(简单理解)
  • true && truetrue
  • true && falsefalse
  • false && truefalse
  • false && falsefalse
  • true || truetrue
  • true || falsetrue
  • false || truetrue
  • false || falsefalse
  • !truefalse
  • !falsetrue

3.6.3 编程实例讲解

实例11:判断闰年(用逻辑运算)
#include <iostream>
using namespace std;

int main() {
    int year;
    cout << "请输入年份:";
    cin >> year;

    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
        cout << year << " 是闰年" << endl;
    } else {
        cout << year << " 不是闰年" << endl;
    }

    return 0;
}
实例12:判断三角形类型

题目:输入三条边长,判断是否能构成三角形,如果能,是等边、等腰还是普通三角形。

#include <iostream>
using namespace std;

int main() {
    double a, b, c;
    cout << "请输入三条边长:";
    cin >> a >> b >> c;

    // 三角形条件:任意两边之和大于第三边
    if (a + b > c && a + c > b && b + c > a) {
        if (a == b && b == c) {
            cout << "等边三角形" << endl;
        } else if (a == b || a == c || b == c) {
            cout << "等腰三角形" << endl;
        } else {
            cout << "普通三角形" << endl;
        }
    } else {
        cout << "不能构成三角形" << endl;
    }

    return 0;
}

3.6.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是否同时满足:能被3整除,且能被5整除。
  2. 练习2:输入三个整数,判断它们是否都相等。
  3. 练习3:输入一个字符,判断它是否是大写字母或小写字母。
  4. 练习4:输入一个年份和月份,判断该月有多少天(使用逻辑运算处理闰年2月)。

3.7 第3章编程作业

恭喜你学完了分支结构!来挑战几个综合题目吧。

作业1:简单的猜拳游戏

编写程序,让用户输入剪刀(0)、石头(1)、布(2),电脑随机出拳(用 rand() % 3),判断胜负并输出结果。提示:需要随机数种子 srand(time(0))

作业2:个人所得税计算器

输入月收入(整数),计算应缴个人所得税。假设税率如下:
- 不超过3000元的部分,税率3%
- 超过3000至12000元的部分,税率10%
- 超过12000至25000元的部分,税率20%
- 超过25000元的部分,税率25%

输出应缴税额(保留两位小数)。比如月收入10000,计算方法是:30003% + (10000-3000)10% = 90 + 700 = 790元。

作业3:日期合法性判断

输入年、月、日,判断这个日期是否合法(考虑闰年2月天数)。

作业4:求解一元二次方程

输入一元二次方程的系数 a, b, c(a≠0),计算判别式 Δ = b² - 4ac,根据Δ的情况输出根:

  • Δ > 0:两个不同的实根
  • Δ = 0:两个相等的实根
  • Δ < 0:无实根

输出根的值(如果有),保留两位小数。


好了,第三章的内容就到这里!你已经学会了让程序做判断。加油!

20260227 084620 Cpp 入门第二课

欢迎回来!在上一章,我们学会了如何让计算机开口说话(输出)。现在,我们要学习如何让计算机拥有“记忆”和“计算”的能力,这就是变量输入。这一章我们将一起探索C++中最重要的基础概念,保证让你轻松掌握!


2.1 变量

2.1.1 变量程序范例

我们先来看一个简单的程序,它定义了两个变量,并进行加法运算。

#include <iostream>
using namespace std;

int main() {
    int a;          // 定义一个整型变量,名字叫a
    int b;          // 定义另一个整型变量b
    int c;          // 定义变量c,用来存结果

    a = 10;         // 把10赋值给a
    b = 20;         // 把20赋值给b
    c = a + b;      // 计算a加b,结果存到c

    cout << c;      // 输出c的值

    return 0;
}

运行结果

30

2.1.2 变量的用法

什么是变量?

变量就像是一个小盒子,我们可以在里面放东西(数据)。每个盒子都有名字(变量名)和能放的东西的类型(数据类型)。

  • 变量名:盒子的名字,比如 ascoreage
  • 数据类型:盒子能装什么类型的东西,比如整数、小数、字符等。
常用的数据类型
类型名 中文含义 能装什么 例子
int 整型 整数 10, -5, 0
double 双精度浮点型 小数 3.14, -2.5
char 字符型 单个字符 ‘A’, ‘b’, ‘9’
string 字符串型 一串文字 “Hello”, “C++”

注意string 类型需要包含 <string> 头文件,但我们先主要学 intdouble

变量的定义和赋值
  • 定义变量:告诉计算机我要一个新盒子,并指定类型。
  int age;        // 定义整型变量age
  double price;   // 定义双精度变量price
  char grade;     // 定义字符变量grade
  • 赋值:往盒子里放东西。
  age = 10;           // 把10放进age盒子
  price = 19.9;       // 把19.9放进price盒子
  grade = 'A';        // 把字符A放进grade盒子
  • 定义时直接初始化(一边定义一边放东西):
  int age = 10;
  double price = 19.9;
  char grade = 'A';
变量的命名规则

给变量起名字要遵守规则,就像每个人要有合法的身份证号一样:
1. 只能由字母(a-z,A-Z)、数字(0-9)和下划线(_)组成。
2. 不能以数字开头。
3. 不能是C++的关键字(比如 intreturn 等)。
4. 尽量起有意义的名字,比如 scores 好。

合法名字myAge, _temp, number1
非法名字2num(数字开头),int(关键字),my-name(有连字符)

2.1.3 编程实例讲解

实例1:交换两个变量的值

题目:有两个盒子a和b,分别装着苹果和橘子,现在要交换它们的内容。

思路:需要第三个临时盒子来帮忙。

#include <iostream>
using namespace std;

int main() {
    int a = 5;      // 盒子a有5个苹果
    int b = 3;      // 盒子b有3个橘子
    int temp;       // 临时盒子

    cout << "交换前:a=" << a << ", b=" << b << endl;

    temp = a;       // 把a的东西放到临时盒子
    a = b;          // 把b的东西放到a
    b = temp;       // 把临时盒子的东西放到b

    cout << "交换后:a=" << a << ", b=" << b << endl;

    return 0;
}

运行结果

交换前:a=5, b=3
交换后:a=3, b=5

2.1.4 阶段性编程练习

  1. 练习1:定义三个整型变量 x, y, z,分别赋值为 7, 8, 9,然后计算它们的和并输出。
  2. 练习2:定义两个 double 型变量,赋值为 2.5 和 3.7,输出它们的乘积。
  3. 练习3:定义一个字符变量存储你名字的首字母,输出这个字母。
  4. 练习4:交换两个变量的值,但不使用临时变量(提示:可以用加法,比如 a = a + b; b = a - b; a = a - b; 试试看)。

2.2 输入

2.2.1 输入程序范例

前面的程序都是我们直接赋值,现在我们要让用户从键盘输入数据。看这个例子:

#include <iostream>
using namespace std;

int main() {
    int age;                // 定义一个变量存放年龄

    cout << "请输入你的年龄:";   // 提示用户输入
    cin >> age;             // 从键盘读取一个整数,存到age里

    cout << "你的年龄是:" << age << "岁" << endl;

    return 0;
}

运行示例(假设用户输入15):

请输入你的年龄:15
你的年龄是:15岁

2.2.2 输入的用法

cin 的基本用法

cin 是 C++ 的输入命令,和 cout 对应。>> 是提取运算符,意思是从键盘输入流中提取数据存到变量里。

  • 格式:cin >> 变量名;
  • 可以连续输入多个变量:cin >> 变量1 >> 变量2 >> 变量3; 输入时用空格或回车分隔。
输入不同类型的数据
int a;
double b;
char c;

cin >> a >> b >> c;   // 输入:10 3.14 x
  • 输入整数给 int 变量。
  • 输入小数给 double 变量。
  • 输入一个字符(注意不要有空格,比如直接输入 x)。
注意事项
  • 输入的数据类型必须和变量类型匹配,否则可能出错或得到奇怪的结果。
  • 输入字符串(string)需要包含 <string> 头文件,并用 cin >> 字符串变量;,但字符串不能有空格(空格会被当作分隔符)。如果要输入带空格的整行,需要用 getline(cin, 变量);,我们以后再学。

2.2.3 编程实例讲解

实例2:输入两个数,求它们的和
#include <iostream>
using namespace std;

int main() {
    int num1, num2;

    cout << "请输入两个整数(用空格隔开):";
    cin >> num1 >> num2;

    int sum = num1 + num2;
    cout << "它们的和是:" << sum << endl;

    return 0;
}

运行示例

请输入两个整数(用空格隔开):12 8
它们的和是:20

2.2.4 阶段性编程练习

  1. 练习1:编写程序,让用户输入自己的身高(厘米),然后输出“你的身高是xxx厘米”。
  2. 练习2:编写程序,输入一个圆的半径(整数),计算并输出圆的周长(周长 = 2 * 3.14 * 半径)。
  3. 练习3:输入两个小数,计算它们的乘积并输出。
  4. 练习4:输入一个字符,输出该字符在ASCII表中的下一个字符(比如输入 ‘A’,输出 ‘B’)。提示:字符也可以像整数一样加减。

2.3 变量的运算

2.3.1 运算程序范例

C++ 可以对变量进行各种数学运算。看这个例子:

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 3;
    int sum, diff, product, quotient, remainder;

    sum = a + b;          // 加法
    diff = a - b;         // 减法
    product = a * b;      // 乘法
    quotient = a / b;     // 除法(整数除法,结果只取整数部分)
    remainder = a % b;    // 取余(求余数)

    cout << "a + b = " << sum << endl;
    cout << "a - b = " << diff << endl;
    cout << "a * b = " << product << endl;
    cout << "a / b = " << quotient << endl;
    cout << "a % b = " << remainder << endl;

    return 0;
}

运行结果

a + b = 13
a - b = 7
a * b = 30
a / b = 3
a % b = 1

2.3.2 变量运算的用法

算术运算符
运算符 含义 例子 结果(a=10,b=3)
+ 加法 a + b 13
- 减法 a - b 7
* 乘法 a * b 30
/ 除法 a / b 3(注意:整数除法会丢弃小数部分)
% 取余(模) a % b 1(10除以3余1)

注意
- 如果两个数都是整数,/ 执行整数除法,结果也是整数,直接去掉小数部分(不是四舍五入)。
- 如果想要小数结果,至少其中一个数要是 double 类型。例如:double c = 10.0 / 3; 结果约为 3.33333。
- % 只能用于整数。

复合赋值运算符

有时候我们需要对一个变量自身进行运算,比如 a = a + 5;,可以简写为 a += 5;

复合运算符 含义 例子 等价于
+= 加等于 a += 5 a = a + 5
-= 减等于 a -= 3 a = a - 3
*= 乘等于 a *= 2 a = a * 2
/= 除等于 a /= 4 a = a / 4
%= 模等于 a %= 3 a = a % 3
自增和自减
  • a++ 相当于 a = a + 1(先使用a的值,再自增)
  • ++a 相当于 a = a + 1(先自增,再使用a的值)
  • a----a 同理,是减1。

初学者可以先记 a++ 就是 a = a + 1 的简写。

运算符优先级

和数学一样,乘除取余优先级高于加减,括号 () 可以改变优先级。

例如:a + b * c 先算 b * c,再加 a
(a + b) * c 先算括号里的 a + b,再乘 c

2.3.3 编程实例讲解

实例3:计算圆的面积和周长

题目:输入圆的半径(可以是小数),输出面积和周长。公式:面积 = π * r²,周长 = 2 * π * r。取 π = 3.14159。

#include <iostream>
using namespace std;

int main() {
    double r, area, perimeter;
    const double PI = 3.14159;   // 常量,值不能改变

    cout << "请输入圆的半径:";
    cin >> r;

    area = PI * r * r;           // 计算面积
    perimeter = 2 * PI * r;      // 计算周长

    cout << "圆的面积是:" << area << endl;
    cout << "圆的周长是:" << perimeter << endl;

    return 0;
}
实例4:求两个数的平均值
#include <iostream>
using namespace std;

int main() {
    int a, b;
    double avg;   // 平均值可能是小数,用double

    cout << "请输入两个整数:";
    cin >> a >> b;

    avg = (a + b) / 2.0;   // 除以2.0,确保结果是小数

    cout << "平均值为:" << avg << endl;

    return 0;
}

2.3.4 阶段性编程练习

  1. 练习1:输入一个三位整数,分离出它的个位、十位、百位,并输出。例如输入 123,输出“百位是1,十位是2,个位是3”。(提示:用除法和取余)
  2. 练习2:输入两个整数,交换它们的值(使用加减法交换,不用临时变量)。
  3. 练习3:输入一个华氏温度,转换为摄氏温度。公式:摄氏 = (华氏 - 32) * 5 / 9。
  4. 练习4:输入一个秒数(整数),转换为小时:分钟:秒的形式。例如输入 3661,输出 1小时1分钟1秒。

2.4 第2章编程作业

现在,你已经学会了变量、输入和运算,来挑战一下综合题目吧!

作业1:计算总分和平均分

编写一个程序,要求用户输入三门课的成绩(整数),计算总分和平均分(平均分保留一位小数)。

输入示例

请输入三门课的成绩:80 90 75

输出示例

总分:245
平均分:81.7

作业2:简单的计算器

编写程序,让用户输入两个整数,然后分别输出它们的和、差、积、商(如果第二个数是0,则输出“除数不能为0”)、余数。

输入示例

请输入两个整数:10 3

输出示例

10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3
10 % 3 = 1

作业3:数字反转

输入一个三位整数,输出反转后的数(例如输入 123,输出 321)。注意:如果个位是0,反转后要输出没有前导0的数,比如输入 120,输出 21。

提示:先分离个十百,再组合。

作业4:猜数字游戏(拓展)

程序随机生成一个1-100之间的整数(用 rand() 函数),让用户猜,每次告诉用户猜大了还是猜小了,直到猜中为止。(提示:需要用到 #include <cstdlib>#include <ctime>,以及 srand(time(0)); 初始化随机种子,rand() % 100 + 1 生成1-100的随机数。)这个题目有点挑战,但你可以尝试!


恭喜你完成了第2章的学习!你已经掌握了C++编程中最核心的基础:变量、输入和运算。

20260227 084059 Cpp 入门第一课

欢迎来到C++编程的第一课!我是你的编程老师,今天我们要一起探索C++世界的第一个大门——输出。想象一下,你刚学会说话,第一次对世界喊出“你好”,C++的输出语句就是让计算机“说话”的方式。让我们开始吧!


1.1 程序范例:第一个C++程序

我们先来看一个最简单的C++程序,它的作用是在屏幕上显示一行文字。

📝 程序范例:输出“Hello, World!”

#include <iostream>   // 头文件:告诉计算机我们要用到输入输出功能
using namespace std;   // 使用标准命名空间(暂时理解为固定搭配)

int main() {           // 主函数:程序的入口,所有代码从这里开始执行
    cout << "Hello, World!";  // 输出语句:将双引号里的内容显示在屏幕上
    return 0;          // 返回0:告诉操作系统程序正常结束
}

🔍 逐行解释(像讲故事一样)

  • #include <iostream>
    这就像你做饭前要准备锅碗瓢盆。iostream是C++里负责“输入输出”的工具箱,有了它,你才能用cout(读作“see out”)来输出文字。

  • using namespace std;
    这个暂时可以理解为“我要用标准工具箱里的东西”。很多C++程序都写着这一行,我们先记住它就好。

  • int main()
    这是程序的“心脏”,所有代码都要写在大括号{}里。计算机一运行程序,就会先找main()函数。

  • cout << "Hello, World!";
    cout是输出命令,<<是“流向”符号,意思就是把右边的文字送到屏幕上。注意双引号里的内容会原样显示,分号;表示这句话结束(就像句号)。

  • return 0;
    程序结束前给操作系统报个平安:“一切正常,我退出了!”

🖥️ 运行结果

Hello, World!

1.2 程序编译错误处理

写代码就像写作文,难免会写错字或漏标点。编译器(把代码翻译成计算机能懂的语言的工具)会帮你找出错误。

1.2.1 当程序报错的时候怎么处理

常见错误1:忘记分号 ;
cout << "Hello"   // 少了分号
  • 错误信息(可能的样子)
    expected ';' before 'return'
    (意思是:在return前面应该有一个分号)

  • 解决方法:在句子末尾加上;

常见错误2:拼写错误
c0ut << "Hello";   // 把cout写成c0ut(数字0代替字母o)
  • 错误信息
    'c0ut' was not declared in this scope
    c0ut这个单词没有被声明,编译器不认识它)

  • 解决方法:检查单词拼写,注意大小写(C++区分大小写)。

常见错误3:忘记引号
cout << Hello;   // Hello没有加双引号
  • 错误信息
    'Hello' was not declared in this scope
    (编译器以为Hello是一个变量,但它没定义)

  • 解决方法:文字内容必须用双引号括起来。

小技巧:如何读懂错误信息?

  • 错误信息里通常会有行号,告诉你哪一行出错了。
  • 关键词如expected(期望)、not declared(未声明)能帮你推测原因。
  • 别怕,多看几次就熟悉了!

1.2.2 编译过程展示

你可能好奇,我们写的代码是怎么变成计算机能运行的程序的呢?这个过程叫做编译,就像把一份中文菜谱翻译成计算机能懂的机器语言。

步骤:
1. 写代码(源文件,后缀为.cpp)—— 你写的英文/符号组成的程序。
2. 编译(用编译器)—— 编译器把源文件翻译成机器能懂的目标文件(后缀为.obj.o)。
3. 链接—— 把目标文件和需要的库(比如iostream)合并成一个可执行文件.exe)。
4. 运行—— 双击.exe文件,程序就开始执行了!

我们平时在IDE(集成开发环境,如Dev-C++、Code::Blocks)里点一下“运行”按钮,其实它自动完成了编译+链接+运行。


1.3 编程题库介绍

学编程就像学游泳,光看书不下水永远学不会。编程题库就是让你练习的“游泳池”。

1.3.1 编程题库的使用方法和技巧

  1. 先看懂题目:题目会描述输入和输出要求,比如“输出两行文字”“输出一个数字”等。
  2. 在脑子里构思:先想想要用哪些语句,顺序是怎样的。
  3. 写代码:在电脑上敲出来。
  4. 运行测试:看看结果是不是和题目要求的一样。
  5. 如果错了,就调试:检查代码哪里出了问题。

小技巧:一开始可以模仿范例,把里面的文字改成题目要求的文字。


1.3.2 实战练习

下面给你几个简单的输出题目,你可以自己试试看!

🥇 练习1:输出你的名字

在屏幕上输出你的名字,比如“我叫小明”。

预期输出

我叫小明

提示:只需要修改cout语句里的文字。


🥈 练习2:输出两行文字

输出两行文字,第一行是“Hello”,第二行是“C++”。

预期输出

Hello
C++

提示:可以用两个cout语句,每个后面都要有分号。或者用endl换行,比如:

cout << "Hello" << endl;
cout << "C++";

endl是“end line”的缩写,作用是换行。


🥉 练习3:输出一个简单的算式结果

计算 3 + 5 并在屏幕上显示结果。

预期输出

8

提示cout可以直接输出数字,比如 cout << 3+5; 会直接计算并输出8。


🎉 本章总结

  • 学习了第一个C++程序的基本结构:#includemaincoutreturn 0
  • 知道了常见的编译错误和解决方法。
  • 了解了编译的简单过程。
  • 学会了使用编程题库进行练习。

现在,你可以打开你的C++编程环境,动手试试上面的练习啦!如果遇到错误,别着急,对照错误信息慢慢检查,你一定能搞定!

20260223 111222 增长函数Growth Functions概述

增长函数(Growth Functions)概述

在算法分析中,增长函数(也称 asymptotic growth)用于描述一个算法随输入规模 (n) 增大时所需的资源(时间、空间)如何变化。
它们的核心思想是 忽略常数因子和低阶项,只关注最“显著”的那一部分——因为当 (n) 足够大时,这部分支配了整体的资源消耗。

下面从 数学定义 → 记号体系 → 典型例子 → 对比技巧 → 常见误区 → 实践使用 四个层面,系统地介绍增长函数。


1️⃣ 形式化定义

设 $(f, g : \mathbb{N} \rightarrow \mathbb{R}_{\ge 0}) $为非负函数(常表示算法的运行次数或所占空间)。
我们用 渐近记号(asymptotic notation)来刻画它们的相对增长速度。

记号 形式化定义 直观解释
大 O (\displaystyle O(g(n))) (f(n) \in O(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le f(n) \le c\cdot g(n)). 上界:(f) 最多和 (g) 同阶(常数因子无关紧要)。
大 Ω (\displaystyle \Omega(g(n))) (f(n) \in \Omega(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le c\cdot g(n) \le f(n)). 下界:(f) 至少和 (g) 同阶。
大 Θ (\displaystyle \Theta(g(n))) (f(n) \in \Theta(g(n)) \iff f(n) \in O(g(n))) (f(n) \in \Omega(g(n))). 紧确界:(f) 与 (g) 同阶,常数因子可忽略。
小 o (\displaystyle o(g(n))) (\displaystyle f(n) \in o(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le f(n) < c\cdot g(n).) 严格上界:(f) 的增长速度严格低于 (g)。
小 ω (\displaystyle \omega(g(n))) (\displaystyle f(n) \in \omega(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le c\cdot g(n) < f(n).) 严格下界:(f) 的增长速度严格高于 (g)。

等价的极限公式(极限判别法)
[
\begin{aligned}
f \in O(g) &\iff \limsup_{n\to\infty}\frac{f(n)}{g(n)} < \infty,\
f \in \Omega(g) &\iff \liminf_{n\to\infty}\frac{f(n)}{g(n)} > 0,\
f \in \Theta(g) &\iff 0<\lim_{n\to\infty}\frac{f(n)}{g(n)}<\infty,\
f \in o(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = 0,\
f \in \omega(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = \infty.
\end{aligned}
]


2️⃣ 常见函数族(Growth Hierarchy)

类别 典型函数 记号示例 备注
常数 (1) (\Theta(1)) 与输入规模无关
对数 (\log n, \log_{2} n, \ln n) (\Theta(\log n)) 底数不同只差常数因子
线性 (n) (\Theta(n)) 常见遍历、线性搜索
线性对数 (n\log n) (\Theta(n\log n)) 归并排序、堆排序的复杂度
多项式 (n^{k})((k) 为常数) (\Theta(n^{k})) (k=2) → (\Theta(n^{2}))(插入/冒泡)
指数 (c^{\,n})((c>1)) (\Theta(c^{n})) 递归暴力搜索、背包 2‑划分
超指数/阶乘 (n!) (\Theta(n!)) 旅行商(TSP)暴力求解
双指数 (2^{\,2^{n}}) (\Theta(2^{2^{n}})) 极少数理论构造
多项式乘以指数 (n^{k}c^{n}) (\Theta(n^{k}c^{n})) 部分 DP 状态压缩的复杂度

增长顺序(从慢到快)(省略常数因子):

[
1 \;<\; \log n \;<\; n \;<\; n\log n \;<\; n^{2} \;<\; n^{3} \;<\; 2^{\,\log n}=n^{\log 2} \;<\; 2^{n} \;<\; n! \;<\; 2^{\,2^{n}} \;<\; \dots
]

重要事实
- (\log^{k} n = o(n^{\epsilon})) 对任意常数 (k) 与 (\epsilon>0)。
- (n^{c} = o(c^{n})) 对任何常数 (c>1)。
- (\log n = o(n^{\epsilon})) —— 这解释了 “对数时间” 远优于 “线性时间”。


3️⃣ 如何比较两个增长函数

3.1 直接使用极限(L’Hôpital、级数)

例子 1:比较 (f(n)=n^{2}) 与 (g(n)=n\log n)。
[
\lim_{n\to\infty}\frac{n^{2}}{n\log n}= \lim_{n\to\infty}\frac{n}{\log n}= \infty.
]
因此 (n^{2}\in \omega(n\log n)),即 (n\log n = o(n^{2}))。

例子 2:比较 (f(n)=2^{n}) 与 (g(n)=n!)。
利用斯特林公式 (n! \sim \sqrt{2\pi n}\,(n/e)^{n}),则
[
\frac{2^{n}}{n!}\approx \frac{2^{n}}{(n/e)^{n}}= \left(\frac{2e}{n}\right)^{n}\xrightarrow{n\to\infty}0,
]
所以 (2^{n}=o(n!))。

3.2 递归树/主定理的经验法则

  • 多项式 vs. 指数:若递归树的分支因子是常数(如 2),则最终复杂度往往是指数,而如果每层的工作量是 多项式,则整体仍是 多项式(主定理 ①‑③)。
  • 对数乘常数:任何 (c\cdot\log n) 都在 (\Theta(\log n)) 里,因为常数因子被忽略。

3.3 摊销分析(Amortized)

  • 例子:向动态数组(如 vector)中逐次 push_back
  • 每次扩容成本是 (O(n)),但均摊后每次操作为 (O(1)),故总体为 (\Theta(n)),单次操作的 增长函数(O(1))(均摊视角)。

3.4 规则记忆技巧(助记口诀)

规则 解释 示例
常数 < 对数 < 线性 < 线性对数 < 多项式 < 指数 < 阶乘 只看 最高阶,忽略常数 (5n^2 + 3n + 12 = \Theta(n^2))
底数不同的对数等价 (\log_{a} n = \frac{1}{\log a}\log n) (\log_{10} n = \Theta(\log n))
指数函数的底数不同也等价 (a^{n} = \Theta(b^{n}))((a,b>1)) (2^{n} = \Theta(3^{n}))(仅常数因素差)
多项式乘指数 = 指数 (n^{k}c^{n} = \Theta(c^{n})) (n^{5}2^{n} = \Theta(2^{n}))
对数的多项式次方仍是对数 ((\log n)^{k}=o(n^{\epsilon})) ((\log n)^{10} = o(\sqrt{n}))

4️⃣ 典型增长函数实例与常见算法对应

增长函数 典型算法/问题 关键技术
(\Theta(1)) 哈希表的 查找/插入(均摊)、数组随机访问 均摊分析、哈希冲突概率
(\Theta(\log n)) 二分搜索、平衡二叉搜索树(红黑树、AVL) 递归/迭代的分治结构
(\Theta(n)) 线性遍历、计数排序的计数阶段、链表遍历 直接扫描
(\Theta(n\log n)) 归并排序、堆排序、快速排序(平均)、线性时间排序的 基数排序(基数常数) 分治+合并、堆操作
(\Theta(n^{2})) 冒泡/插入/选择排序、所有 (O(n^{2})) 的动态规划(如 LCS 再无优化) 双层循环、DP 表填充
(\Theta(n^{3})) Floyd‑Warshall、三层嵌套循环的矩阵乘法(朴素) 三重循环
(\Theta(2^{n})) 旅行商(暴力 TSP)、子集枚举、递归背包(指数) 位掩码、递归分支
(\Theta(n\,2^{n})) DP on subsets(如 Hamiltonian Path DP) 状态压缩 DP
(\Theta(n!)) 全排列生成、全排列搜索(TSP 朴素) 全排列递归
(\Theta(\log^{k} n)) 递归树高度为 (\log^{2} n) 的 分治 + 合并(如分块 & 线段树合并) 多层分治
(\Theta(\sqrt{n})) 区间 sqrt decomposition(块大小 (\sqrt{n})) 块分技术
(\Theta(n^{\log_{b} a})) 主定理中 (T(n)=a\,T(n/b)+f(n)) 的平衡情况 主定理(Case 1)

5️⃣ 常见误区与如何避免

误区 说明 正确做法
把常数因子当成阶层 认为 “5nn 更差”,忽视渐进意义。 Θ 表示法里,系数被视为 O(1),只关注最高阶项。
低阶项能忽略但不一定 实际运行 中,若 (n) 较小,n^2 可能慢于 100n. 理论实验 结合:对目标规模做基准测试。
把对数底看成意义重大 误以为 log_2 nlog_10 n 差别很大。 使用 (\log n) 表示通用对数,底数差仅常数因子。
oO 混为一谈 认为 o(g)O(g) 相同。 o(g)严格 上界(极限为 0),O(g) 只要求有常数上限。
使用极限时忘记整数化 直接把极限写成 0 而不说明 n → ∞ 的离散性。 说明使用 连续延拓(把函数放到实数)或采用 Stirling、L’Hôpital 等严谨手段。
忘记对函数做非负化 对负数函数直接套用 Θ/Ω 定义会出错。 在算法分析里,运行次数空间 均为非负;若出现负数,先取绝对值或重新建模。

6️⃣ 实践:自检练习

下面列出 10 组 常见比较题,建议自行手算极限或用 Python/Mathematica 验证。

# 比较 你的结论 解释/极限
1 (n \log n) vs. (n^{1.1}) (n\log n = o(n^{1.1})) (\frac{n\log n}{n^{1.1}} = \frac{\log n}{n^{0.1}} \to 0)
2 (2^{n}) vs. (n^{\log n}) (2^{n} = \omega(n^{\log n})) (\log n \cdot \log n = (\log n)^2) vs. (n) in exponent → exponential dominates
3 (\sqrt{n}) vs. (\log n) (\log n = o(\sqrt{n})) (\frac{\log n}{\sqrt{n}} \to 0)
4 (n!) vs. (2^{n}) (2^{n}=o(n!)) Stirling: (n! \approx (n/e)^{n})
5 ((\log n)^{2}) vs. (n^{0.01}) ((\log n)^{2}=o(n^{0.01})) (\frac{(\log n)^{2}}{n^{0.01}} \to 0)
6 (n^{\log n}) vs. (2^{\log^{2} n}) 两者同阶 (\Theta(2^{\log^{2} n})) (\log n = \log_{2} n) → (n^{\log n}=2^{(\log n)^{2}})
7 (n^{\log_{2} 3}) vs. (3^{\log_{2} n}) 同阶 (\Theta(n^{\log_{2} 3})) 通过指数换底公式相同
8 (n^{k}) vs. (c^{n})((c>1)) (n^{k}=o(c^{n})) Exponential dominates any polynomial
9 (n^{\log n}) vs. (2^{n}) (n^{\log n}=o(2^{n})) (\log n\cdot \log n = (\log n)^{2}) vs. linear (n) in exponent
10 (\log (n!)) vs. (n\log n) (\log (n!) = \Theta(n\log n)) Stirling: (\log(n!) = n\log n - n + O(\log n))

自检:如果你对任意一行没有把握,请回到极限公式或使用 Pythonsympy.limit 功能验证。


7️⃣ 代码实验:用 Python 自动判断 O/Θ

下面提供一个简易脚本,使用 SymPy(符号计算)判断两函数的关系。仅作为教学示例,实际使用时请结合手动推理。

# growth_compare.py
import sympy as sp

def compare(f, g, var=sp.Symbol('n', positive=True)):
    """Return relationship between f(n) and g(n):
       'Theta', 'O', 'Omega', 'o', 'omega', or 'incomparable'."""
    ratio = sp.simplify(f/g)
    lim = sp.limit(ratio, var, sp.oo)  # limit as n -> ∞

    if lim.is_number:
        if lim == 0:
            return 'o'          # f=o(g)
        elif lim.is_infinite:
            return 'omega'      # f=ω(g)
        else:  # finite non‑zero constant
            return 'Theta'     # f=Θ(g)
    else:
        # fallback: check limsup/liminf numerically for some large values
        vals = [10**k for k in range(3, 8)]
        nums = [float(ratio.subs(var, v)) for v in vals]
        if max(nums) < 10 and min(nums) > 0.1:
            return 'Theta (numeric)'
        elif max(nums) < 2:
            return 'O (numeric)'
        elif min(nums) > 0.5:
            return 'Omega (numeric)'
        else:
            return 'inconclusive'

# --------------------------------------------------
if __name__ == '__main__':
    n = sp.Symbol('n', positive=True, integer=True)

    examples = [
        (n*sp.log(n), n**1.1),
        (2**n, n*sp.log(n)),
        (sp.sqrt(n), sp.log(n)),
        (sp.factorial(n), 2**n),
        ((sp.log(n))**2, n**0.01),
        (n**sp.log(n,2), 2**(sp.log(n,2)**2)),
        (n**sp.log(3,2), 3**sp.log(n,2)),
    ]

    for f, g in examples:
        print(f"f = {sp.simplify(f)}")
        print(f"g = {sp.simplify(g)}")
        print("Relation :", compare(f, g))
        print("-" * 40)

运行后会输出每对函数的关系(如 Theta, o, omega),帮助 快速验证 直觉。


8️⃣ 进一步阅读与练手资源

类型 资源 链接 适合人群
教材 《算法导论》第 4 版(Cormen、Leiserson、Rivest、Stein) https://mitpress.mit.edu/books/introduction-algorithms 所有层次
讲义 MIT 6.006 (Intro to Algorithms) 课程笔记 https://ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2020 初学者
视频 Stanford CS161 (Design & Analysis) 2023 https://www.youtube.com/playlist?list=PL4B9E7E6E6EE8C71A 进阶
交互式 VisuAlgo – 视图所有常见增长函数的比较图 https://visualgo.net/en 直观理解
练习 LeetCode “Algorithm” 标签(排序、搜索、图等) https://leetcode.com/problemset/all/?topicSlugs=algorithm 刷题
理论 《算法设计》(Kleinberg & Tardos)第 7 章 https://www.cs.cornell.edu/home/kleinber/algorithms/ 深入证明
数学 《Concrete Mathematics》章节 3(渐近分析) https://doi.org/10.1017/CBO9781139644032 确切极限技巧
工具 Anki 记忆卡片(常用函数、记号) https://apps.ankiweb.net/ 长期记忆
项目 实现一个 “增长函数可视化” WebApp(输入两函数,显示极限、绘制曲线) 自行开发 巩固概念 + 编程实践

9️⃣ 小结

  1. 增长函数 抽象掉常数和低阶项,只保留“最高阶”行为。
  2. 大 O / Ω / Θ 给出上、下、紧确界;小 o / ω 表示严格的上下界。
  3. 使用 极限摊销递归树主定理 等工具可以 系统地比较 任意两个函数。
  4. 常见函数族 按速率从慢到快排列,帮助快速判断算法的优劣。
  5. 误区(常数因子、对数底、o 与 O 的混淆)要时刻警惕,理论与实际的差距要通过实验验证。
  6. 实践:写代码实现、手算极限、用可视化工具或 Python 脚本自动对比,逐步形成对“函数增长速率直觉”的肌肉记忆

掌握了这些概念后,你就能在阅读新算法时立刻判断它的 时间/空间规模,并在面试、研究或系统设计时做出 最合适的算法选择。祝学习愉快,成长为 增长函数的驾驭者! 🚀

20260223 111221 增长函数Growth Functions概述

增长函数(Growth Functions)概述

在算法分析中,增长函数(也称 asymptotic growth)用于描述一个算法随输入规模 (n) 增大时所需的资源(时间、空间)如何变化。
它们的核心思想是 忽略常数因子和低阶项,只关注最“显著”的那一部分——因为当 (n) 足够大时,这部分支配了整体的资源消耗。

下面从 数学定义 → 记号体系 → 典型例子 → 对比技巧 → 常见误区 → 实践使用 四个层面,系统地介绍增长函数。


1️⃣ 形式化定义

设 $(f, g : \mathbb{N} \rightarrow \mathbb{R}_{\ge 0}) $为非负函数(常表示算法的运行次数或所占空间)。
我们用 渐近记号(asymptotic notation)来刻画它们的相对增长速度。

记号 形式化定义 直观解释
大 O (\displaystyle O(g(n))) (f(n) \in O(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le f(n) \le c\cdot g(n)). 上界:(f) 最多和 (g) 同阶(常数因子无关紧要)。
大 Ω (\displaystyle \Omega(g(n))) (f(n) \in \Omega(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le c\cdot g(n) \le f(n)). 下界:(f) 至少和 (g) 同阶。
大 Θ (\displaystyle \Theta(g(n))) (f(n) \in \Theta(g(n)) \iff f(n) \in O(g(n))) (f(n) \in \Omega(g(n))). 紧确界:(f) 与 (g) 同阶,常数因子可忽略。
小 o (\displaystyle o(g(n))) (\displaystyle f(n) \in o(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le f(n) < c\cdot g(n).) 严格上界:(f) 的增长速度严格低于 (g)。
小 ω (\displaystyle \omega(g(n))) (\displaystyle f(n) \in \omega(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le c\cdot g(n) < f(n).) 严格下界:(f) 的增长速度严格高于 (g)。

等价的极限公式(极限判别法)
[
\begin{aligned}
f \in O(g) &\iff \limsup_{n\to\infty}\frac{f(n)}{g(n)} < \infty,\
f \in \Omega(g) &\iff \liminf_{n\to\infty}\frac{f(n)}{g(n)} > 0,\
f \in \Theta(g) &\iff 0<\lim_{n\to\infty}\frac{f(n)}{g(n)}<\infty,\
f \in o(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = 0,\
f \in \omega(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = \infty.
\end{aligned}
]


2️⃣ 常见函数族(Growth Hierarchy)

类别 典型函数 记号示例 备注
常数 (1) (\Theta(1)) 与输入规模无关
对数 (\log n, \log_{2} n, \ln n) (\Theta(\log n)) 底数不同只差常数因子
线性 (n) (\Theta(n)) 常见遍历、线性搜索
线性对数 (n\log n) (\Theta(n\log n)) 归并排序、堆排序的复杂度
多项式 (n^{k})((k) 为常数) (\Theta(n^{k})) (k=2) → (\Theta(n^{2}))(插入/冒泡)
指数 (c^{\,n})((c>1)) (\Theta(c^{n})) 递归暴力搜索、背包 2‑划分
超指数/阶乘 (n!) (\Theta(n!)) 旅行商(TSP)暴力求解
双指数 (2^{\,2^{n}}) (\Theta(2^{2^{n}})) 极少数理论构造
多项式乘以指数 (n^{k}c^{n}) (\Theta(n^{k}c^{n})) 部分 DP 状态压缩的复杂度

增长顺序(从慢到快)(省略常数因子):

[
1 \;<\; \log n \;<\; n \;<\; n\log n \;<\; n^{2} \;<\; n^{3} \;<\; 2^{\,\log n}=n^{\log 2} \;<\; 2^{n} \;<\; n! \;<\; 2^{\,2^{n}} \;<\; \dots
]

重要事实
- (\log^{k} n = o(n^{\epsilon})) 对任意常数 (k) 与 (\epsilon>0)。
- (n^{c} = o(c^{n})) 对任何常数 (c>1)。
- (\log n = o(n^{\epsilon})) —— 这解释了 “对数时间” 远优于 “线性时间”。


3️⃣ 如何比较两个增长函数

3.1 直接使用极限(L’Hôpital、级数)

例子 1:比较 (f(n)=n^{2}) 与 (g(n)=n\log n)。
[
\lim_{n\to\infty}\frac{n^{2}}{n\log n}= \lim_{n\to\infty}\frac{n}{\log n}= \infty.
]
因此 (n^{2}\in \omega(n\log n)),即 (n\log n = o(n^{2}))。

例子 2:比较 (f(n)=2^{n}) 与 (g(n)=n!)。
利用斯特林公式 (n! \sim \sqrt{2\pi n}\,(n/e)^{n}),则
[
\frac{2^{n}}{n!}\approx \frac{2^{n}}{(n/e)^{n}}= \left(\frac{2e}{n}\right)^{n}\xrightarrow{n\to\infty}0,
]
所以 (2^{n}=o(n!))。

3.2 递归树/主定理的经验法则

  • 多项式 vs. 指数:若递归树的分支因子是常数(如 2),则最终复杂度往往是指数,而如果每层的工作量是 多项式,则整体仍是 多项式(主定理 ①‑③)。
  • 对数乘常数:任何 (c\cdot\log n) 都在 (\Theta(\log n)) 里,因为常数因子被忽略。

3.3 摊销分析(Amortized)

  • 例子:向动态数组(如 vector)中逐次 push_back
  • 每次扩容成本是 (O(n)),但均摊后每次操作为 (O(1)),故总体为 (\Theta(n)),单次操作的 增长函数(O(1))(均摊视角)。

3.4 规则记忆技巧(助记口诀)

规则 解释 示例
常数 < 对数 < 线性 < 线性对数 < 多项式 < 指数 < 阶乘 只看 最高阶,忽略常数 (5n^2 + 3n + 12 = \Theta(n^2))
底数不同的对数等价 (\log_{a} n = \frac{1}{\log a}\log n) (\log_{10} n = \Theta(\log n))
指数函数的底数不同也等价 (a^{n} = \Theta(b^{n}))((a,b>1)) (2^{n} = \Theta(3^{n}))(仅常数因素差)
多项式乘指数 = 指数 (n^{k}c^{n} = \Theta(c^{n})) (n^{5}2^{n} = \Theta(2^{n}))
对数的多项式次方仍是对数 ((\log n)^{k}=o(n^{\epsilon})) ((\log n)^{10} = o(\sqrt{n}))

4️⃣ 典型增长函数实例与常见算法对应

增长函数 典型算法/问题 关键技术
(\Theta(1)) 哈希表的 查找/插入(均摊)、数组随机访问 均摊分析、哈希冲突概率
(\Theta(\log n)) 二分搜索、平衡二叉搜索树(红黑树、AVL) 递归/迭代的分治结构
(\Theta(n)) 线性遍历、计数排序的计数阶段、链表遍历 直接扫描
(\Theta(n\log n)) 归并排序、堆排序、快速排序(平均)、线性时间排序的 基数排序(基数常数) 分治+合并、堆操作
(\Theta(n^{2})) 冒泡/插入/选择排序、所有 (O(n^{2})) 的动态规划(如 LCS 再无优化) 双层循环、DP 表填充
(\Theta(n^{3})) Floyd‑Warshall、三层嵌套循环的矩阵乘法(朴素) 三重循环
(\Theta(2^{n})) 旅行商(暴力 TSP)、子集枚举、递归背包(指数) 位掩码、递归分支
(\Theta(n\,2^{n})) DP on subsets(如 Hamiltonian Path DP) 状态压缩 DP
(\Theta(n!)) 全排列生成、全排列搜索(TSP 朴素) 全排列递归
(\Theta(\log^{k} n)) 递归树高度为 (\log^{2} n) 的 分治 + 合并(如分块 & 线段树合并) 多层分治
(\Theta(\sqrt{n})) 区间 sqrt decomposition(块大小 (\sqrt{n})) 块分技术
(\Theta(n^{\log_{b} a})) 主定理中 (T(n)=a\,T(n/b)+f(n)) 的平衡情况 主定理(Case 1)

5️⃣ 常见误区与如何避免

误区 说明 正确做法
把常数因子当成阶层 认为 “5nn 更差”,忽视渐进意义。 Θ 表示法里,系数被视为 O(1),只关注最高阶项。
低阶项能忽略但不一定 实际运行 中,若 (n) 较小,n^2 可能慢于 100n. 理论实验 结合:对目标规模做基准测试。
把对数底看成意义重大 误以为 log_2 nlog_10 n 差别很大。 使用 (\log n) 表示通用对数,底数差仅常数因子。
oO 混为一谈 认为 o(g)O(g) 相同。 o(g)严格 上界(极限为 0),O(g) 只要求有常数上限。
使用极限时忘记整数化 直接把极限写成 0 而不说明 n → ∞ 的离散性。 说明使用 连续延拓(把函数放到实数)或采用 Stirling、L’Hôpital 等严谨手段。
忘记对函数做非负化 对负数函数直接套用 Θ/Ω 定义会出错。 在算法分析里,运行次数空间 均为非负;若出现负数,先取绝对值或重新建模。

6️⃣ 实践:自检练习

下面列出 10 组 常见比较题,建议自行手算极限或用 Python/Mathematica 验证。

# 比较 你的结论 解释/极限
1 (n \log n) vs. (n^{1.1}) (n\log n = o(n^{1.1})) (\frac{n\log n}{n^{1.1}} = \frac{\log n}{n^{0.1}} \to 0)
2 (2^{n}) vs. (n^{\log n}) (2^{n} = \omega(n^{\log n})) (\log n \cdot \log n = (\log n)^2) vs. (n) in exponent → exponential dominates
3 (\sqrt{n}) vs. (\log n) (\log n = o(\sqrt{n})) (\frac{\log n}{\sqrt{n}} \to 0)
4 (n!) vs. (2^{n}) (2^{n}=o(n!)) Stirling: (n! \approx (n/e)^{n})
5 ((\log n)^{2}) vs. (n^{0.01}) ((\log n)^{2}=o(n^{0.01})) (\frac{(\log n)^{2}}{n^{0.01}} \to 0)
6 (n^{\log n}) vs. (2^{\log^{2} n}) 两者同阶 (\Theta(2^{\log^{2} n})) (\log n = \log_{2} n) → (n^{\log n}=2^{(\log n)^{2}})
7 (n^{\log_{2} 3}) vs. (3^{\log_{2} n}) 同阶 (\Theta(n^{\log_{2} 3})) 通过指数换底公式相同
8 (n^{k}) vs. (c^{n})((c>1)) (n^{k}=o(c^{n})) Exponential dominates any polynomial
9 (n^{\log n}) vs. (2^{n}) (n^{\log n}=o(2^{n})) (\log n\cdot \log n = (\log n)^{2}) vs. linear (n) in exponent
10 (\log (n!)) vs. (n\log n) (\log (n!) = \Theta(n\log n)) Stirling: (\log(n!) = n\log n - n + O(\log n))

自检:如果你对任意一行没有把握,请回到极限公式或使用 Pythonsympy.limit 功能验证。


7️⃣ 代码实验:用 Python 自动判断 O/Θ

下面提供一个简易脚本,使用 SymPy(符号计算)判断两函数的关系。仅作为教学示例,实际使用时请结合手动推理。

# growth_compare.py
import sympy as sp

def compare(f, g, var=sp.Symbol('n', positive=True)):
    """Return relationship between f(n) and g(n):
       'Theta', 'O', 'Omega', 'o', 'omega', or 'incomparable'."""
    ratio = sp.simplify(f/g)
    lim = sp.limit(ratio, var, sp.oo)  # limit as n -> ∞

    if lim.is_number:
        if lim == 0:
            return 'o'          # f=o(g)
        elif lim.is_infinite:
            return 'omega'      # f=ω(g)
        else:  # finite non‑zero constant
            return 'Theta'     # f=Θ(g)
    else:
        # fallback: check limsup/liminf numerically for some large values
        vals = [10**k for k in range(3, 8)]
        nums = [float(ratio.subs(var, v)) for v in vals]
        if max(nums) < 10 and min(nums) > 0.1:
            return 'Theta (numeric)'
        elif max(nums) < 2:
            return 'O (numeric)'
        elif min(nums) > 0.5:
            return 'Omega (numeric)'
        else:
            return 'inconclusive'

# --------------------------------------------------
if __name__ == '__main__':
    n = sp.Symbol('n', positive=True, integer=True)

    examples = [
        (n*sp.log(n), n**1.1),
        (2**n, n*sp.log(n)),
        (sp.sqrt(n), sp.log(n)),
        (sp.factorial(n), 2**n),
        ((sp.log(n))**2, n**0.01),
        (n**sp.log(n,2), 2**(sp.log(n,2)**2)),
        (n**sp.log(3,2), 3**sp.log(n,2)),
    ]

    for f, g in examples:
        print(f"f = {sp.simplify(f)}")
        print(f"g = {sp.simplify(g)}")
        print("Relation :", compare(f, g))
        print("-" * 40)

运行后会输出每对函数的关系(如 Theta, o, omega),帮助 快速验证 直觉。


8️⃣ 进一步阅读与练手资源

类型 资源 链接 适合人群
教材 《算法导论》第 4 版(Cormen、Leiserson、Rivest、Stein) https://mitpress.mit.edu/books/introduction-algorithms 所有层次
讲义 MIT 6.006 (Intro to Algorithms) 课程笔记 https://ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2020 初学者
视频 Stanford CS161 (Design & Analysis) 2023 https://www.youtube.com/playlist?list=PL4B9E7E6E6EE8C71A 进阶
交互式 VisuAlgo – 视图所有常见增长函数的比较图 https://visualgo.net/en 直观理解
练习 LeetCode “Algorithm” 标签(排序、搜索、图等) https://leetcode.com/problemset/all/?topicSlugs=algorithm 刷题
理论 《算法设计》(Kleinberg & Tardos)第 7 章 https://www.cs.cornell.edu/home/kleinber/algorithms/ 深入证明
数学 《Concrete Mathematics》章节 3(渐近分析) https://doi.org/10.1017/CBO9781139644032 确切极限技巧
工具 Anki 记忆卡片(常用函数、记号) https://apps.ankiweb.net/ 长期记忆
项目 实现一个 “增长函数可视化” WebApp(输入两函数,显示极限、绘制曲线) 自行开发 巩固概念 + 编程实践

9️⃣ 小结

  1. 增长函数 抽象掉常数和低阶项,只保留“最高阶”行为。
  2. 大 O / Ω / Θ 给出上、下、紧确界;小 o / ω 表示严格的上下界。
  3. 使用 极限摊销递归树主定理 等工具可以 系统地比较 任意两个函数。
  4. 常见函数族 按速率从慢到快排列,帮助快速判断算法的优劣。
  5. 误区(常数因子、对数底、o 与 O 的混淆)要时刻警惕,理论与实际的差距要通过实验验证。
  6. 实践:写代码实现、手算极限、用可视化工具或 Python 脚本自动对比,逐步形成对“函数增长速率直觉”的肌肉记忆

掌握了这些概念后,你就能在阅读新算法时立刻判断它的 时间/空间规模,并在面试、研究或系统设计时做出 最合适的算法选择。祝学习愉快,成长为 增长函数的驾驭者! 🚀

20260126 230349 如何通过一人公司进行财富积累

通过一人公司进行财富积累的核心逻辑已在2026年发生了根本性转变:从追求“规模扩张”转向追求“极致的杠杆率”

在这一模式下,财富的积累不再依赖于增加员工人数,而是通过降低运营成本、利用AI技术杠杆、创造常青资产以及实施价值定价来实现。以下是具体的财富积累路径:

1. 确立“利润至上”的财务目标

一人公司的首要目标是实现最小可行性利润(Minimum Viable Profit, MVP),而非盲目追求营收增长。

  • 利润即理智: 营收是虚荣指标,只有扣除所有账单后的利润才是真正的积累。通过保持精简的结构,你可以建立更厚的“利润缓冲带”来应对市场波动。
  • 设定盈利上限: 像Sean D’Souza那样为自己设定一个利润目标(如年利润50万美元),达到目标后便停止扩张,转而通过提高效率和涨价来优化生活质量。

2. 利用技术杠杆实现“95%成本缩减”

在2026年,利用AI代理(AI Agents)替代传统职能部门是财富积累的关键。

  • 数字化员工: 使用Dume.ai、Lindy或PrometAI等工具处理原本需要高薪雇佣的设计、财务建模和行政任务。
  • 成本优势: 传统团队的年成本可能高达30万美元以上,而一套完整的AI技术栈年成本仅在3000至1.2万美元之间,这意味着运营成本可降低95%至98%

3. 从“卖时间”转向“卖资产”

财富积累的终极阶段是实现收入与劳动时间的彻底脱钩。

  • 产品化服务: 不要按时计费,而是将你的专业知识包装成产品化服务常青资产(Evergreen Assets),如自动化在线课程、软件插件或专业模板。
  • 知识积累: 通过“教给他人你所知道的一切”建立信任和权威,从而吸引愿意支付溢价的忠实受众。

4. 实施“价值导向型”定价策略

一人公司应避免陷入低价竞争,而是利用定价作为价值信号。

  • 定价即身份: 价格不仅是经济决策,更是社会信号。过低的价格可能暗示专业性不足。
  • 关注结果: 采用价值导向定价(Value-based Pricing),根据你为客户带来的业务价值或节省的成本来收费,而非投入的工作时间。

5. 极致的客户保留率

在积累过程中,保留现有客户的利润率远高于获取新客户。

  • 成本差异: 获取新客户的成本是留住老客户的5-25倍。
  • 口碑效应: 通过提供极具个性化的卓越服务(如Sean D’Souza送给客户的手写信和巧克力),让老客户成为你的“义务推销员”,从而降低营销支出。

财富积累路径总结表

阶段 核心任务 财富杠杆点
初创期 达到“最小可行性利润” 极低的固定支出
运营期 部署AI工作流自动化 用工具替代人力成本
增长期 提升客单价与价值定价 品牌溢价与个人IP力
成熟期 创造常青数字资产 收入与工作时间完全脱钩

您目前的业务中,有哪些环节仍然高度依赖您的时间投入,而可以通过“产品化”来释放更多财富增量?

20260126 230119 2026 商业新范式一人公司的 7 个反直觉生存洞察

1、引言:当“变大”不再是唯一的成功路径

在 2026 年的商业语境下,一种深层的焦虑正笼罩着每一位创业者。曾经,成功的路径清晰得近乎简陋:获得融资、租下更大的办公室、雇佣更多的人头。然而,在宏观经济波动与 AI 结构性替代的双重夹击下,这种传统的扩张模式正显示出其脆弱的本质。组织的复杂性、管理成本的激增,往往让创始人在实现盈利之前,先被卷入无尽的行政内耗。

现在,一种被称为“一人公司 (Company of One)”的睿智战略正在重塑成功学。这并非因无法扩张而产生的权宜之计,而是一种刻意选择保持小规模的进取心。它质疑“扩张即成功”的逻辑,主张通过极高的技术杠杆捕获全球机遇。2026 年,真正的商业自由不再源于你管理了多少人,而源于你如何编排那些不知疲倦的数字劳动力,从而实现“做得更好”而非“做得更大”。

2、洞察一:增长是一个陷阱,韧性才是护城河

在 2026 年,盲目扩张往往意味着牺牲自主权。Paul Jarvis 的理论深刻揭示了增长的悖论:大公司因固定成本沉重而在波动面前步履蹒跚,而“一人公司”通过韧性、自主权、速度和简约性,构建了一道防御性极强的护城河。这种模式的核心优势在于能够“在硬币上转身 (Pivot on a dime)”,将精力从应对官僚流程转移到深化客户价值。

一人公司最动人的竞争力,在于它能提供大组织永远无法复制的“不具规模性 (Unscalable)”的个人特质。以 Psychotactics 的创始人 Sean D’Souza 为例,他将年利润目标死死锁定在 50 万美元。一旦达标,他便会停止工作去陪伴家人,而非盲目追逐下一个 500 万。为了回馈客户,他会亲手寄出一盒巧克力并附上涂鸦贺卡,这种温暖的连接让他拥有了极高的客户留存率。

“对于一人公司而言,问题始终是‘我能做些什么让我的业务变得更好?’,而不是‘我能做些什么让我的业务变得更大?’” —— Paul Jarvis

3、洞察二:从“雇佣员工”到“编排 AI 代理”

2026 年的职场革命已经从“AI 助手”演进到了“AI 代理 (AI Agents)”的时代。根据 PrometAI 的研究,传统的 5 人精简团队(涵盖设计、开发、文案、分析和客服)每年的成本至少在 30 万美元以上。而在今天,一个由自主 AI 系统驱动的一人公司,其年度运营成本仅需 3,000 至 12,000 美元。这种 95%-98% 的运营成本缩减,让个体拥有了与跨国企业竞争的资本。

这种转变的精髓在于从“人机协作 (Human-in-the-loop)”向“人机监管 (Human-on-the-loop)”的范式转移。2026 年的 AI 代理具备“观察并行动 (Observe-and-Act)”的特征,它们不再等待每一条指令,而是根据预设目标自主监控邮件、筛选线索、调度会议。通过 Gumloop 或 OpenAI Operator 等工具,创始人不再是唯一的执行者,而成了整个微观帝国的编排者,设置目标,让系统自主执行。

“工具不再仅仅是支持,工具就是角色本身。” —— PrometAI

4、洞察三:定价不是成本核算,而是心理实验

在定价策略上,一人公司必须彻底抛弃平庸的“时薪制”。根据 Thread & Theory 的观点,定价是一种深刻的身份认同和价值信号。《市场营销研究杂志》的一项红酒实验显示,同一款酒,当被告知价格是 45 美元而非 5 美元时,品尝者大脑中的愉悦中心会显著活跃。这意味着,价格本身就在塑造消费者的体验。

个体创始人必须学会将“隐形劳动”货币化——即那些源于 10 年模式识别经验的直觉和预见危机的能力。AI 可以生成文案,但它无法复制你经过十年沉淀出的商业嗅觉。定价不应是由于恐惧流失客户而进行的“恐慌性定价”,而应是“基于价值的定价 (Value-based pricing)”。如果你定价过低,你实际上是在向市场传递“我不够专业”的错误信号,从而削弱了你作品的有效性。

“你的定价塑造了你的作品被感知的方式。” —— Thread & Theory

5、洞察四:做“开心果”而不是“香草冰淇淋”

在 AI 生成内容泛滥的 2026 年,平庸就是自杀。大型公司为了追求最大公约数,往往把自己做成了“香草冰淇淋”——稳健、无害,却索然无味。一人公司的生存逻辑是追求“开心果策略 (Polarization)”:让一部分人疯狂热爱,让另一部分人避而远之。

敢于展现鲜明、甚至怪异的品牌人格。正如 Just Mayo 通过争议性营销挑战传统行业,或 Marmite 以“爱它或恨它”作为口号。在 2026 年,你必须成为某种价值观的信号灯,才能吸引到属于你的“部落 (Tribe)”。记住,激不起任何情感反应的产品才是真正的失败。

“大公司追求那种能吸引所有人口统计特征的‘圣杯’产品,但这种‘一刀切’的方法很少奏效,往往导致平庸。” —— Paul Jarvis

6、洞察五:分发是新的护城河,开发已成通用能力

2026年,随着无代码和 AI 构建工具的普及,“如何做出来”已不再是门槛。产品已然成为了通用的商品,而“注意力”才是这个时代的稀缺资源。如果你拥有一个完美的商业想法却无人知晓,那它等同于不存在。

你需要遵循 30/70 原则:仅用 30% 的时间用于构建产品,而将 70% 的精力投入到分发中。一个成熟的一人公司必须建立三层分发架构:由邮件列表和 YouTube 构成的自有受众 (Owned Audience)、利用算法驱动流量的发现渠道 (Discovery),以及精准的付费获取 (Paid)。这种分发层级能确保你不再受制于任何单一平台的规则变动。

“在 2026 年,一个没有受众的完美产品,注定会输给一个拥有 10 万粉丝的平庸产品。” —— PrometAI

7、洞察六:模块化技术栈,像搭乐高一样构建帝国

放弃沉重、昂贵且难以修改的集成软件,转而拥抱“模块化 (Modular)”架构。2026 年的高效技术栈由六个核心层级组成:基础架构层(确立数字身份)、AI 大脑层(战略与文案)、自动化编排层(工作流流转)、营收引擎层(支付与订阅)、增长层(分发)和数据洞察层。

这种架构的核心优势在于其组装速度 (Speed of Assembly)。每个工具都像一块乐高积木,出色地完成一项任务,并能与其他积木干净连接。当业务需求变化或有更先进的 AI 模型出现时,你可以瞬间插拔和更换其中任何一块,而不会导致系统瘫痪。这种轻量化状态让你能够始终保持对微观帝国的绝对控制,而不是被庞大的软件合同绑架。

“每个工具都像一块乐高积木,它出色地完成一项工作,并能与其余部分干净地连接。当业务发生变化时,技术栈也会随之改变。” —— PrometAI

8、洞察七:能量管理胜过时间管理

一人公司最脆弱的环节不是代码或财务,而是创始人的精神状态。当所有的决策都落在一个人肩上时,职业倦怠 (Burnout) 是毁灭性的。我们需要引入“能量银行 (Energy Banking)”概念:将任务分为“存入能量”(创意、运动、深度思考)和“支取能量”(行政杂务、处理投诉)。

利用“冰块与雪人 (Ice cube vs Snow)”法来精简任务:任务的客观核心是“冰块”,而你对它的焦虑和自我怀疑是覆盖在上面的“雪”。通过剥离情绪负担,你能更轻快地执行核心任务。更重要的是,你要学会“战略性不完成 (Strategic Incompletion)”——主动决定哪些次要任务只需要做到 80 分,甚至保持未完成状态,从而为你的核心大脑预留必需的心理空间。

“商业成功不在于快速、大规模地增长,而在于长期建立一个既卓越又具有韧性的东西。” —— Paul Jarvis

9、结语:在微观帝国中实现无限规模

步入 2026 年,成功的定义正在被重写。它不再取决于你雇佣了多少人,而取决于你的技术杠杆率有多高,你的系统有多稳健。

“一人公司”不是一种无奈的妥协,而是一种极致的竞争力。它让你在不牺牲生活质量的前提下,利用全球化的数字基础设施捕获长尾机遇。

最后,请思考一个发人深省的问题:“如果你今天可以不雇佣任何人就构建出一个产品,你会从明天开始吗?”