1. int, long, long long, short 的区别?无符号和有符号区别?float 和 double区别?
int,long, long long 和short 都属于整型,区别是C++标准规定的尺寸的最小值(即该类型在内存中所占的比特数)不同。其中,short是短整型,占16位;int是整型,占16位;long和long long均为长整型,分别占用32位和64位。
大多数整型都可以划分为无符号和有符号类型,在无符号类型中所有比特都用来存储数值,但是仅能表示大于等于0的值;带符号类型则可以表示正数、负数或0.
float和double分别是单精度和双精度浮点数,区别主要是在内存中所占的比特数不同,以及默认规定的有效位数不同。【float 4byte; double 8byte】
2. 指针和引用的区别
指针和引用同为复合类型,都与内存中实际存在的对象有联系。
指针“指向”内存中的某个对象,而引用“绑定到”内存中的某个对象,它们都实现了对其他对象的间接访问,二者的区别主要有两个方面:
1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。
2. 指针无须在定义是赋值,和其他build_in类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。
3. char &a = i; 引用类似于一个常量指针,sizeof(a)是引用对象的大小,单其实a在内存空间中是占一个指针的大小(x86位4个字节,64位8个字节)
3. `void* p`是一种特殊的指针类型,可用于存放任意对象的地址
4. auto和decltype的区别:
1. auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值;
2. 编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。例如,auto一般会忽略掉顶层const,而把底层const保留下来。与之相反,decltype会保留变量的顶层const;
3. 与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。
#include <typeinfo>
#include<iostream>
int main()
{
int a = 3;
auto c1 = a;
decltype(a) c2 = a;
decltype((a)) c3 = a;
const int d = 5;
auto f1 = d;
decltype(d) f2 = d;
std::cout << typeid(c1).name() << std::endl;
std::cout << typeid(c2).name() << std::endl;
std::cout << typeid(c3).name() << std::endl;
std::cout << typeid(f1).name() << std::endl;
std::cout << typeid(f2).name() << std::endl;
}
5. getline函数一次读入一整行;另一种是使用cin一次读入一个词,遇到空白停止
6. 标准库string的输入运算符自动忽略字符串开头的空白(包括空格符、换行符、制表符),从第一个真正的字符开始读起,直到遇见下一处空白为止。如果希望在最终的字符串中保留输入时的空白符,应该使用getline函数代替原来的>>运算符,getline从给定的输入流中读取数据,直到遇到换行符为止,此时换行符也被读取进来,但是并不存储在最后的字符串中。
7. C++11新特性之范围for语句:
for(auto &c : s)这里 c 根据 s 的变量利用auto自动推断出c的类型 等价于 for(char &c : s),其中s为字符串。
通常这个range-based形式的for循环用在遍历容器(vector, array)中的各个元素!
vector<int> number;
<Code for initialize the number>
for(auto &n : number)
//------
for(auto &n : {1, 2, 3, 4})
8. 读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余的部分;
对于字符串处理方面,常用到的读方式是character-by-character,这可以用cin,cin的作用是将数据保存到缓冲区,检测到有按下enter键的时候将缓冲区里的数据读出来,遇到space或者换行符时结束。还有一种常用方式是line-by-line,顾名思义就是按行读取数据,用到的函数是getline,遇到换行符时结束。
//tips: 用ispunct()函数判断是不是字符串
int main()
{
std::string s;
getline(cin, s);
for(auto &c : s)
{
if(!ispunct(c))
std::cout << c;
}
return 0;
}
9. 从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写为大写形式。输出改变后的结果,每个词占一行。
//对vector里string的字符操作
//tips: 使用toupper函数
int main(int arg, char *argv[])
{
vector<string> word;
string n;
while (cin >> n) {
//string temp;
for (auto &ch : n) {
ch = toupper(ch);
//temp += toupper(ch);
}
word.push_back(n);
}
for (auto &i : word)
cout << i << endl;
}
10. 迭代器是一种访问容器元素的通用机制,与指针类型类似,迭代器也提供了对象的间接访问。使用迭代器可以访问某个元素,迭代器也能够从一个元素移动到另外一个元素。
11. int型的最大整数:MAX_INT;
12. 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
while(n != 0){
count++;
n = n & (n-1);
}
return count;
}
};
13. 指针操作时,需要千万注意不能越界,容易发生段错误!
14. 输入一个链表,反转链表后,输出新链表的表头。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* current = pHead;
ListNode* pre = NULL; //很机智的一步!
ListNode* temp = NULL;
while(current != NULL)
{
temp = current->next;
current->next = pre;
if(temp == NULL)
pHead = current;
pre = current;
current = temp;
}
return pHead;
}
};
15. 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
//正数的源码,反码,补码都一样
//负数的反码是符号位不变,其他为取反;补码反码加一
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
while(n != 0){
count++;
n = n & (n-1);
}
return count;
}
};
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
unsigned int flag = 1;
while(flag){
if(n&flag)
count++;
flag = flag << 1;
}
return count;
}
};
16. 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
<!--按圈打印-->
//先计算要打印的圈数,每圈都按照 左->右;上->下;右->左;下->上 || 如此循环打出。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
if(matrix.empty()) return matrix[0];
int row = matrix.size();
int col = matrix[0].size();
int circle=((row<col?row:col)-1)/2+1;//圈数;
vector<int> result;
for(int i = 0; i < circle; i++){
for(int j = i; j < col - i; j++)
result.push_back(matrix[i][j]);
for(int k = i+1;k < row - i;k++)
result.push_back(matrix[k][col-1-i]);
for(int m = col-i-2; (m>=i)&&(row-i-1!=i);m--)
result.push_back(matrix[row-i-1][m]);
for(int n = row-i-2; (n>i)&&(col-i-1!=i);n--)
result.push_back(matrix[n][i]);
}
return result;
}
};
17. 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
//递归方法
void PermutationHelp(vector<string> &ans, int k, string str) //遍历第k位的所有可能
{
if(k == str.size() - 1)
ans.push_back(str);
//note: if it's first time to excute the for-loop i = k else i was increasing with 1 step.
for(int i = k; i < str.size(); i++)
{
// verify if it is the first time to excute the for-loop
if(i != k && str[k] == str[i])
continue;
swap(str[i], str[k]);
PermutationHelp(ans, k + 1, str);
}
}
18. int 转化为string; string转化为int;string转化为char*
1. std::to_string(42);(C++11特性)
2.
int a = 10;
stringstream ss;
ss << a;
string str = ss.str();
3.
int a = 10;
char *intStr = itoa(a);
string str = string(intStr);
------------------------------
1. std::stoi( str )
2.
std::istringstream ss(thestring); //头文件是 #include<sstring>
ss >> thevalue;
3.
string a = "25";
int b = atoi(a.c_str());
---------------------------------
1.
string a = "Hello";
const char* ch; //要指向常量字符串 ch 需要为指向常量的指针
ch = a._str(); //返回字符串常量的首地址
19. 枚举enum
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band; // band a variable of type spectrum
band = blue; // valid, blue is an enumerator
band = 2000; // invalid, 2000 not an enumerator
20. explicit关键字
参考Stack Overflow上的答案:
explicit是为了防止类构造函数隐式转换的发生。
class Foo
{
public:
// single parameter constructor, can be used as an implicit conversion
//explicit Foo(int foo): m_foo(foo) --> Compile error!
Foo(int foo): m_foo(foo)
{
cout << "Call constructor" << "\n";
}
int GetFoo()
{
cout << "Call getFoo func." << "\n";
return m_foo;
}
private:
int m_foo;
};
void DoBar(Foo foo)
{
int i = foo.GetFoo();
}
int main()
{
//由于以int为参数的Foo构造函数没有加关键字explicit限定,那么调用DoBar函数时,会将整型int变量
//隐式转换为Foo的对象
DoBar(12);
}
```
14. 两个元素值递增的链表合并成一个递增的链表(两种实现方式,一种递归,一种O(n)遍历)
struct ListNode{
int val;
ListNode* next;
ListNode(int v){
val = v;
next = NULL;
}
};
------递归形式----
//pHead1, pHead2 are the first node of the list1 and list2
ListNode* merge(ListNode* phead1, ListNode* pHead2)
{
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
ListNode* pMerge = NULL;
if(pHead1->val < pHead2){
pMerge = pHead1;
pMerge->next = merge(pHead1->next, pHead2);
}
else{
pMerge = pHead2;
pMerge->next = merge(pHead1, pHead2->next);
}
return pMerge;
}
------遍历形式----
//思路:重新定义一个新的merge后的指针 pMergeHead;以及定义一个一直指向新链表尾结点的指针 pMergeTail
//pHead1, pHead2 are the first node of the list1 and list2
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
ListNode* l;
//int init_flag = 0;
ListNode* h1 = pHead1;
ListNode* h2 = pHead2;
ListNode* h3 = NULL; //Merge list
ListNode* tail3 = NULL;
while(h1!=NULL && h2!=NULL)
{
//init_flag++;
if(h1->val < h2->val){
if(h3 == NULL)
tail3 = h3 = h1;
else{
tail3->next = h1;
tail3 = tail3->next;
}
h1 = h1->next;
}
else{
if(h3 == NULL)
tail3 = h3 = h2;
else{
tail3->next = h2;
tail3 = tail3->next;
}
h2 = h2->next;
}
if(h1 == NULL){
tail3->next = h2;
}
if(h2 == NULL){
tail3->next = h1;
}
}
return h3;
}
};
###15. 密码强度测试
```
分四类:
1. 强度0:密码长度小于8|用户名与密码相同|用户名与密码相反
2. 强度1:密码长度大于8&密码只包含数字与小写字母组合或数字与大写字母组合
3. 强度2:密码长度大于8&密码除只包含数字与小写字母组合或数字与大写字母组合
4. 强度3:密码长度大于8&密码组合有超过三类
密码可由:数字、小写字母、大写字母、特殊字符组成。
```
16. 输入一个整数Sum,输入一个数组其中元素>=0,从一个数组中取出任意个数,他们的和为Sum,有多少种方法?
```
int main()
{
int m;
int sum;
vector<int> vect1;
cin >> sum;
int temp;
while (cin >> temp) {
vect1.push_back(temp);
}
int count = 0;
if (vect1.empty()) return 0;
sort(vect1.begin(), vect1.end());
for (auto i = vect1.begin(); i != vect1.end() ; i++) {
int sum_temp = *i;
if (sum == 0 && *i == 0) {
count++;
continue;
}
if (*i == sum) count++;
int flag = 0;
for (auto j = i + 1; j != vect1.end(); j++) {
sum_temp += *j;
if (flag == 1) {
if (*i + *j == sum) count++;
}
flag = 1;
if (sum_temp == sum) count++;
//if (sum_temp > sum) continue;
}
}
cout << count << endl;
}
17. 函数重载(overload),函数覆盖(override)以及隐藏的的区别:
函数重载:是C++才有的特性,当存在两个作用域相同的同名函数,编译器根据函数参数不同,在内存中会有不同命名规则,在运行是,根据输入的参数,自动匹配相应的函数,这是在编译时期就绑定好的。
函数覆盖(override):如果基类存在虚函数,那么子类声明一个与父类虚函数同名的函数(要求参数也一样),这时候会发生覆盖,也就是实现多态,可以通过父类指针指向子类对象实现子类的同名函数,即接口服用。
隐藏:如果子类继承父类的同名函数,而该函数没有声明为virtual时,这时候父类的同名函数会被隐藏起来,不管两个函数有没有参数区别,都不能直接访问对方函数,需要加::显式调用对应类的函数。
举个例子:
-----------overload-------
void func(int a, int b){}
void booc(int a){}
/*函数在编译阶段绑定好了*/
-----------override------
class Base{
public:
virtual int area(){}
};
class derive: public Base{
public:
int area(){} //多态的实现是基于虚函数的,设计多态时是为了接口复用,因此要求函数参数一致
};
/*属于晚绑定,即函数运行时才绑定*/
-----------hide--------
class Base{
public:
int area(){}
};
class derive: public Base{
public:
int area(int a){}
};
由于derive继承自Base,所以area()和area(int a)的作用域不同,前者是Base::或者是derive::,因此函数虽参数不一样,但是不能重载,编译器在解析derive中的area只会去匹配area(int ),如果参数不一致则编译错误,而Base::area则隐藏了起来
为了在继承的环境下,仍然可以发生函数重载,我们需要在子类中说明基类同名函数的作用域即:
class derive: public Base{
public:
using namespace Base::area;
int area(int a){}
};
这样,子类调用area函数时,根据参数会去执行area()或者area(int)
###18. 类的多态理解,即底层实现机制
###19. 类的继承、虚继承、多重继承
###20. 常用的排序算法有哪些?它们的时间复杂度各是多少?
In C++, in STL algorithm, you can use
std::sort,
std::stable_sort,
std::partial_sort
排序算法内嵌了许多CS的思想:
1. 比较 vs 不比较策略
2. 迭代 vs 递归实现
3. 分而治之(Divide-and-Conquer)
4. 最好/最差/平均例子的时间复杂度分析
5. 随机性算法等
排好序数组A的应用:
1. 在数组A中查找特定元素v
2. 寻找数组A中第k个最大或最小元素
3. 测试数组A中是有某个独立元素或者删除重复的元素
4. 对特定元素v在数组A中出现的个数进行计算
5. 在数组A和另一个排好序的数组B之间插入或联合
6. 在A中找x和y,使得x+y等于目标z
排序算法分类:
1. 基于比较的排序算法:
a. 冒泡排序
b. 选择排序
c. 插入排序
d. 归并排序(递归实现)
归并排序,时间复杂度是nlog(n),是一种稳定的排序算法,适合对元素多的数组进行排序。
缺点(不那么好的地方):在合并步骤要求一个辅助数组,占用内存空间。
e. 快速排序(递归实现)
f. 随机快速排序(递归实现)
以上,基于比较的排序时间复杂度至少是 $$$nlog(n)$$$
2. 不是基于比较的排序算法
a. 计数排序
b. 基数排序
---
0. 冒泡排序,左边和右边比较,如果左边比右边大则左右交换,循环n次,这样最大的数字会放在最后
bubbleSort(A[0...n-1])
for i = 0 to n-1 to do
for j = 0 to n - i -1 do
if A[j] > A[j+1]
temp = a[j]
a[j] = a[j+1]
a[j+1]=temp
1. 插入排序(减治思想):
虽然插入排序的时间复杂度为O(n^2),归并排序的时间复杂度为O(nlogn),但插入排序中的常数因子使得它在n较小时,运行得要更快一些。因此,在归并排序算法中,当子问题足够小时,采用插入排序算法就比较合适了。
“递归的思想,自底向上实现。”(迭代)
在n-1已经排好序的基础上插入第n个数。
InsertSort(A[0..n-1])
//用插入排序对给定数组排序
//输入:n个可排序元素构成的一个数组A[0..n-1]
//输出:非降序排列的数组A[0..n-1]
for i <- 1 to n-1 do
v <- A[i]
j <- i-1
while j >= 0 and A[j] > v do
A[j+1] <- A[j]
j <= j-1
A[j+1] <- v
//2. 拓扑排序:
3. 选择排序
首先从未排序的数组中找到最下的元素放在起始位,然后从剩余未排序的数组中继续找最小的数放在已经排好序数组的末尾
repeat (numOfElements - 1) times
set the first unsorted element as the minimum
for each of the unsorted elements
if element < currentMinimum
set element as new minimum
swap minimum with first unsorted position
4. 快速排序
快排和归并是两个相反的套路,但是思想都是分而治之。归并是先将大问题不断分解成小问题,比如把一个数组分割到一个个元素,然后在合并部分再从基于比较相邻元素组合成一个数组;而快速排序是先找到一个基准点,把基准点放在数组中间,小于基准的元素放在前面,大于基准的元素放在后面,这样就分成了两个子数组,然后在子数组分别继续寻找基准点,继续划分,直到划分到元素,这时候元素已经排好序了,只要合并起来就好了。所以快排和归并思想是一样的,但是实现确实完全相反的,快排是按元素大小拆分然后合并,归并是先拆分然后再基于比较合并。归并排序最好和最坏的时间复杂度都是O(nlog(n));而普通的快排最坏情况的时间复杂度是O(n^2),平均时间复杂度是O(nlog(n),如果采用随机快排(Random quick sort),则最好和最坏的时间复杂度都是O(nlog(n))。
ok,taik is cheap, let's look the code!
int partion(int a[], int low, int high)
{
int p = a[low];
int m = low;
for(int k = low+1; k<high+1; k++){ // explore the unknown region
if(a[k]<p){
m++;
std::swap(a[m],a[k]);
}
}
swap(a[low], a[m]);
return m; // return the pivot
}
void quickSort(int a[], int low, int high)
{
if(low < high){
int pivot = partion(a, low, high);
quickSort(a, low, pivot);
quickSort(a, pivot+1, high);
}
}
5. 归(合)并排序(分治思想)
设归并排序的当前区间是R[low..high]
分三步走:
1)拆分:将当前区间一分为二,即求分裂点
2)求解:递归地对两个子区间R[low..mid]和R[mid+1..high]进行归并排序
3)合并:将已排序的两个子区间R[low..mid]和R[mid+1..high]归并为一个有序的区间R[low..high]
其中,递归终止条件为子区间长度为1.
写合并排序算法的关键点在于对已经排好序的数组进行合并。复杂度是O(nlog(n))。
//Assuming that the array a[low..mid] and a[mid+1..high] has been sorted
void merge(int a[], int low, int mid, int high)
{
int left = low;
int right = mid+1;
int N = high-low+1;
int bIdx = 0;
int *b = new int[N]; //Store the merged element
while(left <= mid && right <= high)
b[bIdx++] = (a[left]<a[right]) ? a[left++]:a[right++];
while(left<=mid) b[bidx++] = a[left++]; //The right array has merged over
while(right<=high) b[bIdx] = a[right++]; //The left array has merged over
for(int k = 0; k<N; k++)
a[low+k] = b[k];
}
//mergeSort
void mergeSort(int a[], int low, int high)
{
// the array to be sorted is a[low..high]
if(low < high){
int mid = (high+low)/2; // base case: low >= high (0 or 1 item)
mergeSort(a, low, mid);
mergeSort(a, mid+1, high);
merge(a, low, mid, high);
}
}
###21. 你了解哪些数据结构?数构的应用场景?
###22. STL专题:容器,迭代器,算法
容器:
0. array
底层实现机制:
1. vector
vector是一个动态数组,可以用push_back()往vector里添加元素,当空间达到最大容量时,push_back会重新申请一段内存,这个内存是之前的1.5倍(有的编译器是2倍),然后将之前的元素以复制移动的形式存进新的内存空间。一般来说,vector里面的空间只增不减,所以当用erase()或者pop_back()时,只是把元素抹去,空间是不变的。
底层实现机制:
2. forward_list(a Single Linked List)
底层实现机制:
3. stack
底层实现机制:
4. queue
底层实现机制:
5. list(a Double Linked List)
底层实现机制:
6. deque(actually not Doubly Linked List but another technique)
底层实现机制:
###22.2 Hash Table
哈希表是一种用键值匹配的数据结构。它使用哈希函数来将大的keys映射到一个小范围的表中。
哈希表需要解决冲突问题。
哈希是一种算法,借助哈希函数来将大的数据集,通过keys映射到小的数据集。
哈希表是一种数据结构,使用哈希函数快速用keys去匹配值,实现高效搜索、恢复、插入或者移除。
哈希表广泛应用于计算机软件中,实现关联数组(assoviative array),数据库索引,缓存和集合。
哈希表和二叉搜索树一样都适合实现搜索,插入和移除。
好的哈希函数是实现一对一(one-to-one)映射。并且没有冲突。
###23. 汉诺塔(递归)
如果n=1,直接将物块从A移动到C上
如果n>1时,移动过程分解为一下几个步骤:
(1)将A上的n-1物块借助C移动到B上;
(2)把A上剩下的一个物块从A移动到C上;
(3)把B上的n-1物块,借助A从B移动到C;
void hanoi(int n, char a, char b, char c)
if(n==1) cout << "a -> c\n"; //递归终止条件
else{
hanoi(n-1,a,c,b);
cout << "a -> c\n";
hanoi(n-1,b,a,c);
}
###24. n个不重复的数全排列并输出
###25. 递归的三种形式
recursion(大规模) {
if (end_condition) {
end;
} else {
//先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
recursion(小规模); //go;
solve; //back;
}
}
-----------
recursion(大规模) {
if (end_condition) {
end;
} else {
//在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
solve; //back;
recursion(小规模); //go;
}
}
--------
recursion() {
if (end_condition) {
solve;
} else {
//在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
for () {
recursion(); //go;
}
}
}
###26. 字符串处理总结
总结一下STL里面string的常用操作
1. 定义和初始化
2. 读写字符串
3. 字符串操作
4. 处理字符串中的每个字符
####1. 定义和初始化
string是STL里的一个序列容器类型,和vector一样,可以有多种初始化形式:
```
string s1; // 默认初始化;s1为空字符串
string s2 = s1; // 调用copy构造函数初始化;s2是s1的副本
string s3 = "hiya"; // s3 是常量字符的副本(属于复制初始化)
string s4(10, 'c'); // s4为cccccccccc,调用相应函数直接初始化
string s5("hiya"); //调用构造函数直接初始化
```
####2. 读写字符串
通常使用iostream来读写内置数据类型int, double等,同样也可以用IO操作符(>>, <<)来读写``string``
```
int main()
{
string s; //空字符串
cin >> s; //读取一个不包含空格的字符串(cin遇到空格会停止)
cout << s << endl; //将s输出
return 0;
}
```
给两个字符串赋值:
```
string s1, s2;
cin >> s1 >> s2; //读取第一个字符放在s1,第二个字符放在s2(终端输入时,两个字符串中间空格隔开)
```
利用while循环读取多个字符串:
```
int main()
{
string word;
while(cin >> word){ //读取字符串直到遇到文件结束符; window平台是:Ctrl+z, Linux平台是:Ctrl+d
cout << word << edndl; //
}
return 0;
}
cin的作用是将键盘的输入放在缓存区,不管是数字还是字符串,都是根据空格对数据进行分割,并以enter键作为本次输入的终止条件。如果cin在while里面,那么只有输入文件结束符或者类型不匹配,才会终止循环,否则一直等待下一次输入。
input: Hello world!
output:
Hello
world!
(等待下一次输入)
```
按行读取
```
cin读取时,遇到空格会认为一个字符串结束,如果想对字符串进行整行输入可以使用getline(),这样不会跳过空格
int main()
{
string line;
//一次读取一整行,遇到文件结束符时终止
while(getline(cin, line))
cout << line << endl;
}
```
####3. 字符串常用操作
```
auto n = str.size(); // 字符串中有多少个字符,n的类型是string::size_type
str[n]; // 和vector一样是序列容器,支持下标访问
newStr = str1 + str2; // 字符串拼接,返回一个字符串对象
string s5 = "Hello "+ str1; // error!
str1 == str2; // 判断两个字符串是不是含有相同字符
```
####4.处理字符串中的字符
#####处理字符串中的每一个字符
经常需要对字符串里的每一个字符进行处理,比如说要判断一个字符串里面有没有空格,或者改变字符的大小写,判断某个字符是否出现等。
利用Range-Based for
for(declaration : expression)
statement;
string str("Some string");
//将str里面的每一个字符按行输出
for(auto c : str)
cout << c << end; // print the current character followed by a newline
```
cctype 函数:
isalnum(c) 如果字符c是字母或数字返回true
isalpha(c) 如果字符c是字符返回true
iscntrl(c) 如果字符c是一个控制字符(control character)
isdigit(c) 如果字符c是数字返回true
isgraph(c) 如果字符c不是一个空格但可打印(printable)返回ture
islower(c) 如果字符c是一个小写字母返回true
isprint(c) 如果字符c是一个可打印的字符(空格或者不可见的字符表示),返回ture
ispunct(c) 如果字符c是一个标点符号返回true
isspace(c) 如果字符c是空格(空格,tap,v tab,换行符等)返回true
isupper(c) 如果字符c是一个大写字母返回true
isxdigit(c) 如果字符c是一个16进制数字,返回true
tolower(c) 如果字符c是大写字母转化为小写字母,如果c是小写字母则啥也不做
toupper(c) 如果字符c是小写字母转化为大写字母,如果c是大写字母则不改变
```
#####处理字符串中的某个或者某几个字符
1. 使用下标
只要str不是const的,就可以通过下标赋予新的值,如:
s[0] = toupper(s[0]);
2. 使用迭代
#####STL中常用的string操作
string s("Hello");
auto i = s.begin(); //返回一个迭代器类型的引用
string ss(s, pos); //复制s中的字符,从s中的索引pos开始。如果pos>s.size()则出错。
string sss(s, pos, len); //从s中索引pos开始复制len位字符到sss;需要满足pos<s.size();复制的字符个数最多为s.size()-pos个
######子字符串操作
string s("hello world!");
string s2 = s.substr(0, 5); //s2 = "hello"
string s3 = s.substr(6); //s3 = world
string s4 = s.substr(6, 11); s3 = world
string s5 = s.substr(12); //throws an out_of_range exception
string和其他序列容器一样(vector, dequeue)一样,支持assign, insert, and erase操作。
append和replace函数能改变字符串里面的内容。
string搜索操作:
提供6中不同的搜索函数,每一种都有四个重载方式:
返回string::size_type 如果找到元素了 返回相应元素的下标,如果没找到返回string::npos(npos的值的-1)
string name("AnnaBelle");
auto pos1 = name.find("Anna"); //pos1 == 0
string lowercase("annabelle");
pos1 = lowercase.find("Anna); // pos == npos;
s.find(args) 在s中找到第一个和args匹配的index
s.rfind() 在s中找最后一个和args匹配的index
s.find_first_of(args) 在s中找到第一个和args中含有的字符的index
s.find_last_of(args) 在s中找到一个和args中匹配的字符(越往后越好)
s.find_first_not_of(args)在s中找到args中的一个字符
s.find_last_not_of(args)找到最后一个在args中的字符
以上的args 必须为:
(c, pos)
(s2, pos)
(cp, pos)
(cp, pos, n)
数字转换函数:
to_string(val); //将整数型或者浮点型数字转化为字符串
stoi(s, p, b);
stod(str) //将字符串转化为浮点型数字方;
####27. STL中通用算法
####28. C/C++中的数据存储区(data segment, code segment, stack segment)
每个segment在内存中的不同区域。
计算机程序内存可以分成两部分:只读(read-only)和读写(read-write)
代码段(code segment),也称为文本段,是存在于目标文件中的一部分,是只读的。
data segment(数据段):包括(已经定义)初始化静态变量,全局变量,和静态局部变量。数据段是可读可写(read)。
BSS:如果全局变量,和静态变量没有初始化,则放在BSS区域;
堆(heap):堆主要用于用户内存申请(malloc,calloc,realloc,free等),堆的区域是由线程共享的。
栈(stack):栈是一种后进先出的结构,通常在函数调用的时候,会将局部变量和函数的返回地址放在栈里面,栈和堆,一个在顶部,一个在底部,两个放相反方向生长。可能会相遇。
![](E:\Personal\myNote\Interview\memory structure.png)
tips: 类中的静态成员不占用内存空间!!
class 和 static 组合的时候, class 就只是一个 namespace 的作用了
####29. exit(), abort(), return区别
return:
终止当前执行的函数,并返回到函数调用的地方,return有两种形式,一种是没有返回值,一种是有返回值。
return;//函数返回类型是void,函数最后一个语句如果没有显示调用return,编译器自动隐式调用,如果在函数中间调用return,则在中间就结束。
return时,会释放函数调用过程中用到的栈内存空间即,调用析构函数,释放局部变量。
exit()不会调用任意一个局部变量的析构函数,但是全局变量,静态变量仍能释放
abort()不会释放全局变量,局部变量和静态变量。
如果是在多线程里面,exit()是退出一个线程,退出时会将缓冲数据清除干净,文件写进磁盘,而abort()是发出一个异常终止信号(SIGABRT signal),强行将这个线程关闭,但是并不会讲缓冲数据清除掉。
return expression;//返回函数结果,返回的值要和函数返回类型一致
####30. 在类成员函数后面加const
说明这是一个只读函数,不能修改任何成员变量
####31. 函数返回类型是引用
如果返回值是一个类的对象,那么返回的是这个对象的副本。
如果返回类型是一个引用,那么返回的是这个对象的别名,并且可以通过左值运算来改变对象。
tips: 引用
```
//返回长度短的字符串的引用
string &shortString(string &str1, string &str2) {
return (str1.size() < str2.size()) ? str1 : str2;
}
int main()
{
string s1 = "Yanbin";
string s2 = "Lin";
string s3 = shortString(s1, s2);
shortString(s1, s2) = ("Lillian"); //返回s2的别名,并通过赋值运算直接改变s2的值
cout << s3 << endl;//输出Lin
cout << s2 << endl;//输出Lillian
}
```