1. 函数调用的参数是以什么顺序压栈的,为什么?
从右向左压栈的。因为C, C++支持可变参函数。
可变参函数就是参数个数可变的函数,如printf()就是可变参函数
void func(int a,...){}
2. 有一个函数
在main函数中通过:string s = fun(s1, s2);调用,按照代码执行顺序分析一下调用了什么构造函数和顺序,以及析构函数的调用顺序。
由于fun函数定义中没有使用&,故调用时进行拷贝构造。
- 拷贝构造s2
- 拷贝构造s1
- 执行string tmp = s1+s2; 拷贝构造tmp
- 使用tmp拷贝构造string s(如果使用临时对象拷贝构造新对象,那么临时对象就不产生了,直接构造新对象即可。
但如果是
string s;
s = fun(s1, s2);
那么临时对象就存在,并且s的构造函数是赋值构造。
析构时正好相反,先析构tmp,然后s1,然后s2,最后main函数结束析构s
3. 空结构体有多大?
struct Data1{
};
对于C语言:
windows下,vs 2013 15 17 20是不允许定义空的struct结构体
linux/unix系统下,sizeof(Data)=0,因为在C语言中,struct是结构体变量,定义变量只需要内存即可,空的结构体什么也没有,无需内存。
对于C++:
windows/linux/unix系统下均为1,在C++中,struct不叫变量,而是对象,需要内存+构造,什么也没有也需要分配一块最小的内存,有了这块内存才能给构造函数的this指针传东西,才能构造,才能创建对象,这块最小的内存就是1.
struct Data3:public Data1{
};
sizeof(Data3)是多大? 也是1
struct Data2{
char a1;
};
sizeof(Data2)是多大? 也是1 (此时不再按照空结构体占一个字节算,char是1个字节)
4. vector和map存储一对数据的区别
1. 定义上有区别 vector要借助pair才能存储一对数据
vector<pair<int, int> s;
unordered_map<int, int> map;
2. 遍历上的区别
vector遍历方式:如果是pair型的一对数据 vector用.second
map的三种遍历方式 注意:mp.begin()搭配->second ->first
只有方式二那种遍历for(auto it:mp)用.second .first
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<string, int> mp;
mp["张三"] = 20;
mp["李四"] = 18;
mp["王五"] = 30;
// 方式一、普通for循环
cout << "方式一、普通for循环" << endl;
for (auto it = mp.begin(); it != mp.end(); it++) {
cout << it->first << " " << it->second << endl;
}
// 方式二、基于范围的for循环 C++ 11版本及以上
cout << "\n方法二、 range for" << endl;
for (auto it : mp) {
cout << it.first << " " << it.second << endl;
}
// 方法三、while循环
cout << "\n方法三、while循环" << endl;
unordered_map<string, int>::iterator iter = mp.begin();//开头那里用的是map类型!!不是变量名!!!
while(iter != mp.end()){
cout<<"key:"<<iter->first<<" value:"<<iter->second<<endl;//不能用iter.first!!!!
++iter;
}
// 方法四、原理同方法三 只是用的是for循环
unordered_map<string, int>::iterator iter;
for(iter = mp.begin(); iter!=mp.end(); iter++){ // 这里边界条件是!=mp.end() 不能写成<mp.end()!!!!
cout<<"key:"<<iter->first<<" value:"<<iter->second<<endl;
}
}
3.添加数据的区别
vector用
s.push_back(pair<int, int>(...,...));
map中添加数据有很多种办法,不过我比较喜欢用两种,一个是数组下标,另外一个是用insert插入pair数据。具体如下:
map<string,int>m;//定义m
1:使用insert添加元素
m.insert(pair<string,int>("sd",19));
2:直接使用数组下标
m["sd"]=19;
map[key] = value;
4.sort()函数
a.对第一个数的大小进行排序,因此vector<pair<int, int>>时,会比较第一个数然后sort从小到大
b.对字母按字典排序
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
string str;
cin>>str;
sort(str.begin(),str.end());
cout<<str;
return 0;
}
c.结构体的按字母排序排序(结构体排序是最难的,没有学过结构体的先去看看结构体,不然看不懂)
#include <iostream>
#include <cstring>
#include <memory.h>
#include <algorithm>
using namespace std;
struct student
{
char name[20];
char gender;
unsigned long birthday;
double height;
double weight;
};
int cmp(student a,student b)
{
return strcmp(b.name,a.name)>0;//strcmp函数,让首字母按字典排序,升序降序改变符号即可
}
student room[4] = {
{"Lixin ", 'M', 19980301, 1.82, 65.0},
{"Zhangmeng", 'M', 19980902, 1.75, 58.0},
{"Helei ", 'M', 19981203, 1.83, 67.1},
{"Geyujian ", 'M', 19980101, 1.70, 59.0}
};
int main()
{
sort(room,room+4,cmp); //引用cmp函数
for (int i = 0; i < 4; i ++)
{
cout<< room[i].name << "\t"
<< room[i].gender << "\t"
<< room[i].birthday << "\t"
<< room[i].height << "\t"
<< room[i].weight << "\n";
}
return 0;
}
我们看到同学的第一个字母按字典排序了
5. size_t类型(就像int型 char型 float型一样)
size_t 是一种无符号整数类型(即非负整数),通常在C++种表示对象的大小或数组的索引,用于与内存和数据结构相关的操作。
size_t 的大小(即占用多少字节)是平台相关的,它可以根据编译器和操作系统的不同而变化。通常情况下,size_t 被设计成足够大,可以容纳你的系统上最大可能的对象大小。
在C++中经常见到size_t,定义size_t这种类型别名,只是为了让应用程序代码的可移植性高一些。如果代码中采用size_t来定义变量,则不用考虑平台变化带来的影响!
主要特点和用途:
无符号整数类型: size_t 是无符号整数类型,它只能表示非负整数,包括0。它不存储负数值。
平台相关性: size_t 的大小在不同的编译器和平台下可以不同。通常情况下,它被定义为足够大以容纳目标平台上最大对象的大小。例如,在大多数32位系统上,size_t 是4字节(32位),而在64位系统上通常是8字节(64位)。在不同架构上进行编译时需要注意这个问题。而int在不同架构下都是4字节,与size_t不同;且int为带符号数,size_t为无符号数。
用于数组索引: size_t 常用于循环中,特别是用于遍历数组或容器的元素。它可以保证足够大以表示数组的索引范围,从而避免溢出问题。
用于内存分配: size_t 通常用于表示内存分配的大小,例如 new 和 malloc 函数的参数。这有助于确保分配足够的内存以容纳数据。
标准库中的使用: C++标准库中的许多函数和容器(如std::vector、std::string等)使用size_t 来表示容器的大小、元素数量等。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 size_t 来遍历容器
for (size_t i = 0; i < numbers.size(); ++i) {
cout << numbers[i] << " ";
}
cout << endl;
return 0;
}
6. 多if语句和if...else if...语句
多if语句:所有的if都会进行判断
if(条件1)
语句1;
if(条件2)
语句2;
if(条件3)
语句3;
分析一下多个if:当满足条件1时,执行语句1.当不满足条件1时,往下走,判断条件2,如果还是不满足,再判断条件3.
if else if else语句:只要有一个条件满足,则其余条件不用判断,直接跳过即可
else if可有可没有,多少个都无所谓
,
else 可以没有,但是只要有,就只能有一个
,就一定是在判断结构的最后
if(条件1)
语句1;
else if(条件2)
语句2;
else if(条件3)
语句3;
.
.
.
else
语句4;
1.使用场景:
这种形式的if语句,最终只有一个出口.即当满足条件1时,执行语句1.当不满足条件1时,往下走,判断是否满足条件2,如果还是不满足,则继续往下走,一直走到else语句,说明之前的条件都不满足,最终执行语句4;
2.if elseif else 与if 嵌套的区别:
if套if,是当外面条件不满足时,里面的if不会执行
而if else if else,是当第一个条件不满足时,判断第二个条件
多个if型:
int a=0;
if(a==0){
a++;
printf("aaa");
}
if(a==1)
printf("bbb");
if(a==2)
printf("ccc");
最终输出:aaabbb,在多个if语句中,所有的if都会进行判断,
无论是否满足情况.所以在满足a==0时,执行了a++,a就变成了1,
当进行a==1判断时,也符合,故也执行了printf("bbb")
-------------------------------------------------
if else if else型:
int a=0;
if(a==0){
a++;
printf("aaa");
}
else if(a==1)
printf("bbb");
else
printf("ccc");
最终只输出了aaa.在if else if else中,只要有一个条件满足,
则其余条件不用判断,直接跳过即可,所以a++,
a=1之后不会去判断else if中的a==1,所以也就不会执行printf("bbb")
7. stoi、stol、stoll函数的用法
参考C++stoi、stol、stoll 函数用法-CSDN博客
stoi() 函数
#include <string>
int stoi(string& str, size_t* pos = 0, int base = 10);
功能:将字符串 str 转成 int 整数
参数:
str:字符串
pos:存储将字符串 str 转成 int 整数,处理了 str 中字符的个数的地址,默认为 NULL
base:进制,10:十进制,8:八进制,16:十六进制,0:则自动检测数值进制,str 是 0 开头为八进制,str 是 0x 或 0X 开头是十六进制,默认为十进制
stol() 函数
#include <string>
long stol(string& str, size_t* pos = 0, int base = 10);
功能:将字符串str转成 long 整数
参数:
str:字符串
pos:存储将字符串str转成 long 整数,处理了 str 中字符的个数的地址,默认为 NULL
base:进制,10:十进制,8:八进制,16:十六进制,0:则自动检测数值进制,str 是 0 开头为八进制,str 是 0x 或 0X 开头是十六进制,默认为十进制
stoll() 函数
#include <string>
long long stoll(string& str, size_t* pos = 0, int base = 10);
功能:将字符串str转成 long long 整数
参数:
str:字符串
pos:存储将字符串 str 转成 long long 整数,处理了 str 中字符的个数的地址,默认为 NULL
base:进制,10:十进制,8:八进制,16:十六进制,0:则自动检测数值进制,str 是 0 开头为八进制,str 是 0x 或 0X 开头是十六进制,默认为十进制
8. 说一下深拷贝、浅拷贝和移动构造的区别,拷贝构造是哪一种拷贝?
浅拷贝:仅仅复制对象的数据成员的值,如果数据成员是指针,那么它会复制指针的值,而不是指针所指向的内容。这意味着两个对象会共享同一块内存区域。当其中一个对象修改了这块内存,另一个对象也会受到影响。如果没有特别定义拷贝构造函数或赋值操作符,编译器会自动生成默认的浅拷贝。
深拷贝:深拷贝则不仅复制对象的数据成员,还会复制指针所指向的内容,即在堆上分配新的内存空间来存储被指针指向的数据。这样,即使两个对象有相同的指针成员,它们也会指向不同的内存区域,修改其中一个对象的指针所指向的数据不会影响另一个对象。深拷贝通常需要显式地实现拷贝构造函数和赋值操作符。
移动构造:可以看出深拷贝和浅拷贝一般都发生在拷贝构造和赋值操作符中,虽然浅拷贝比深拷贝开销更小,但是其存在资源多次释放的问题。而移动构造可以避免这个问题,其实现方式通常是将源对象指针指向的资源转移到目标对象中,并将源对象的指针置为nullptr,以避免资源的重复释放。
9. C++拷贝构造函数:为什么必须按引用传递参数?
在C++中,有两种主要的参数传递方式:
a. 按值传递(Pass by Value):
这意味着在参数传递时,会创建参数对象的副本。
对于内置类型,这种拷贝操作非常高效。
对于用户定义的类型,副本的创建需要调用拷贝构造函数。
b. 按引用传递(Pass by Reference):
这意味着在参数传递时,不会创建副本,而是传递对象的引用(指针)。
传递引用时不涉及副本创建,因此不会调用拷贝构造函数。
拷贝构造函数的递归问题:拷贝构造函数的主要作用是复制一个对象。当我们定义一个按值传递的拷贝构造函数时,编译器必须在调用时创建该参数的副本,这意味着再次调用拷贝构造函数。这样就进入了一个无限递归的调用过程,最终导致栈溢出。