类可以将变量、数组和函数完美地打包在一起。
——yxc
1.类
类中的变量和函数被统一称为类的成员变量。
private后面的内容是私有成员变量,在类的外部不能访问;public后面的内容是公有成员变量,在类的外部可以访问。
2.结构体与类的唯一区别
结构体和类的作用是一样的。不同点在于类默认是private,结构体默认是public。
习惯上把结构较为简单的函数较少的代码定义成结构体,较为复杂的麻烦的代码定义成类。
成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在内存中次序有关,而内存中的排列顺序早在编译期就根据变量的定义次序决定了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> using namespace std;
class Person{ private: int age,height; double money; public: string name;
void say(){ cout << "Hello, " << name << endl; } int get_age() { return age; } void add_money(double x) { money += x; } private: string books[100]; }d,Persons[100];
int main() { Person C; Person Per[120]; C.name = "John"; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream> using namespace std; struct Person{ int age,height; double money; Person (){} Person(int _age,int _height):age(_age),height(_height) {} Person(int _age,int _height,double _money):age(_age),height(_height),money(_money) {} }; Person b; int main() { Person p ; Person c = {18,180}; cout << p.money << endl; cout << b.money << endl; cout << c.money << endl; return 0; }
|
编译器关于构造函数使用与变量初始化相同的规则来初始化数据成员。对象a在函数体外定义,其int、double类型数据成员被初始为0;对象b在函数体内定义,构造函数不会对其进行初始化(符合内置类型变量初始化规则),其中存放的都是随机值。
3.指针与引用(回顾指针的用法)
指针即地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> using namespace std; char a,b; int main() { char c = 'a',d; cout << (void*)&a << endl; cout << (void*)&b << endl; cout << (void*)&c << endl; cout << (void*)&d << endl; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using namespace std;
int main() { int a = 10; int* p = &a; cout << "before:" << a << ' '<< *p << endl; *p = 12; cout << "after:" << a <<' '<< *p << endl; return 0; }
|
指针指向存放变量的值的地址。因此我们可以通过指针来修改变量的值。
这类似于我们通过数组下标来修改数组中的值。
数组名是一种特殊的指针。指针可以做运算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> using namespace std; int main() { char c; int a[5] = {1,2,3,4,5}; cout << a << endl; for (int i = 0;i < 5;i ++) cout << (void*)&a[i] << endl; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> using namespace std; int main() { char c; int a[5] = {1,2,3,4,5};
int* p = a; cout << p << endl; cout << p + 1 << endl; cout << *p << endl; cout << *(p + 2) << endl; return 0; }
|
引用
引用和指针类似,相当于给变量起了个别名。
1 2 3 4 5 6 7 8 9
| #include <iostream> using namespace std; int main() { int a = 10; int* p = &a; int& p = a; return 0; }
|
4.链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream> using namespace std; struct Node{ int val; Node* next;
Node(int _val) : val(_val),next(NULL){} }; int main() { Node node = Node(1); Node* p = new Node(1); }
|
如图:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> using namespace std; struct Node{ int val; Node* next;
Node(int _val) : val(_val),next(NULL){} }; int main() { auto p = new Node(1); auto q = new Node(2); auto o = new Node(3);
p->next = q; q->next = o; Node* head = p; cout << "before:\t" << endl; for (Node* i = head;i;i = i->next) cout << i->val << endl; Node* u = new Node(4); u->next = head; head = u; cout << "after:\t" << endl; for (Node* i = head;i;i = i->next) cout << i->val << endl; head->next = head->next->next; return 0; }
|
链表的头结点(head):大部分情况下指的是第一个结点的地址。而不是它的值。记牢!

5.acwing.21.斐波那契数列
输入一个整数 n ,求斐波那契数列的第 n 项。
假定从0开始,第0项为0。(n<=39)(0,1,1,2,…)
样例
1 2 3 4 5 6 7 8
| class Solution { public: int Fibonacci(int n) { if (n <= 1) return n; return Fibonacci(n-1) + Fibonacci(n-2); } };
|
6.acwing.84.求1 + 2 + 3 + … + n
求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution { public: int getSum(int n) { int res = n; n > 0 && (res += getSum(n-1)); return res; } };
class Solution { public: int getSum(int n) { char a[n][n+1]; return sizeof(a)>>1; } };
|
7.acwing.87.把字符串转换成整数
请你写一个函数StrToInt,实现把字符串转换成整数这个功能。
当然,不能使用atoi或者其他类似的库函数。
样例
注意:
你的函数应满足下列条件:
- 忽略所有行首空格,找到第一个非空格字符,可以是 ‘+/−’ 表示是正数或者负数,紧随其后找到最长的一串连续数字,将其解析成一个整数;
- 整数后可能有任意非数字字符,请将其忽略;
- 如果整数长度为0,则返回0;
- 如果整数大于INT_MAX(2^31 − 1),请返回INT_MAX;如果整数小于INT_MIN(−2^31) ,请返回INT_MIN;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class Solution { public: int strToInt(string str) { int sign = 0;long long n = 0;int num[40] = {0}; int i = 0; for (char &c:str) { if (c == '+' ) {sign = 1;continue;} else if (c == '-') {sign = -1;continue;} else if (sign != 0|| (c >= '0' && c <= '9')) { if (c >= '0' && c <= '9') {num[i] = c -48;i ++;} else break; } else if(c != ' ') break; } if (i && !sign) sign = 1; for (int j = 0;j < i;j ++) { n += (long long)num[j]*pow(10,i-1-j); } if (sign *n > pow(2,31)-1) return INT_MAX; else if (sign *n < -1*pow(2,31)) return INT_MIN; else return sign * n; } };
class Solution { public: int strToInt(string str) { int k = 0; while (k < str.size() && str[k] == ' ') k ++; long long res = 0; int minus = 1; if (k < str.size()) { if (str[k] == '-') {minus = -1;k ++;} else if (str[k] == '+') k ++; } while (k < str.size() && str[k] <= '9' && str[k] >= '0') { res = res * 10 + str[k] - '0'; if (res > 1e11) break; k ++; } res *= minus; if (res > INT_MAX) res = INT_MAX; if (res < INT_MIN) res = INT_MIN; return res; } };
|
8.acWing 28. 在O(1)时间删除链表结点
tips:链表题多画图,方便思考,空想还是有难度的!
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
样例
1 2 3 4
| 输入:链表 1->4->6->8 删掉节点:第2个节点即6(头节点为第0个节点)
输出:新链表 1->4->8
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: void deleteNode(ListNode* node) { node->val = node->next->val; node->next = node->next->next; } };
class Solution { public: void deleteNode(ListNode* node) { *(node) = *(node->next); } };
|
9.acwing.36.合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
样例
1 2 3
| 输入:1->3->5 , 2->4->5
输出:1->2->3->4->5->5
|
算法:(二路归并) O(n)
新建头部的保护结点dummy,设置cur指针指向dummy。
若当前l1指针指向的结点的值val比l2指针指向的结点的值val小,则令cur的next指针指向l1,且l1后移;否则指向l2,且l2后移。
然后cur指针按照上一部设置好的位置后移。
循环以上步骤直到l1或l2为空。
将剩余的l1或l2接到cur指针后边。
时间复杂度
两个链表各遍历一次,所以时间复杂度为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: ListNode* merge(ListNode* l1, ListNode* l2) { ListNode *dummy = new ListNode(0); ListNode *cur = dummy; while (l1 != NULL && l2 != NULL) { if (l1 -> val < l2 -> val) { cur -> next = l1; l1 = l1 -> next; } else { cur -> next = l2; l2 = l2 -> next; } cur = cur -> next; } cur -> next = (l1 != NULL ? l1 : l2); return dummy -> next; } };
|
10.acwing.35.反转链表
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
思考题:
样例
1 2 3
| 输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
|
算法1:(链表操作,迭代) O(n)
迭代也就是循环的意思。
翻转即将所有节点的next指针指向前驱节点。
由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
- 空间复杂度分析:遍历时只有3个额外变量,所以额外的空间复杂度是 O(1)。
- 时间复杂度分析:只遍历一次链表,时间复杂度是 O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Solution { public: ListNode* reverseList(ListNode* head) { if (!head || !head->next) return head; ListNode* p = head; ListNode* q = p->next; while (q) { ListNode* o = q->next; q->next = p; p = q,q = o; } head->next = nullptr; return p; } };
class Solution { public: ListNode* reverseList(ListNode* head) { ListNode *prev = nullptr; ListNode *cur = head; while (cur) { ListNode *next = cur->next; cur->next = prev; prev = cur, cur = next; } return prev; } };
|
算法2:(链表操作,递归) O(n)
首先我们先考虑 reverseList 函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。
所以我们可以先递归处理 reverseList(head->next)
,这样我们可以将以head->next
为头节点的链表翻转,并得到原链表的尾节点tail,此时head->next
是新链表的尾节点,我们令它的next指针指向head,并将head->next
指向空即可将整个链表翻转,且新链表的头节点是tail。
- 空间复杂度分析:总共递归 n 层,系统栈的空间复杂度是 O(n),所以总共需要额外 O(n) 的空间。
- 时间复杂度分析:链表中每个节点只被遍历一次,所以时间复杂度是 O(n)。


head是每一层函数的返回值res。
讲解链接:https://www.bilibili.com/video/av83907207?t=1
1 2 3 4 5 6 7 8 9 10
| class Solution { public: ListNode* reverseList(ListNode* head) { if (!head || !head->next) return head; ListNode *tail = reverseList(head->next); head->next->next = head; head->next = nullptr; return tail; } };
|