C++ 八股(2)

1. 函数调用的参数是以什么顺序压栈的,为什么?

从右向左压栈的。因为C, C++支持可变参函数。

可变参函数就是参数个数可变的函数,如printf()就是可变参函数

void func(int a,...){}

2. 有一个函数

在main函数中通过:string s = fun(s1, s2);调用,按照代码执行顺序分析一下调用了什么构造函数和顺序,以及析构函数的调用顺序。

由于fun函数定义中没有使用&,故调用时进行拷贝构造。

  1. 拷贝构造s2
  2. 拷贝构造s1
  3. 执行string tmp = s1+s2; 拷贝构造tmp
  4. 使用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):
这意味着在参数传递时,不会创建副本,而是传递对象的引用(指针)。
传递引用时不涉及副本创建,因此不会调用拷贝构造函数。

拷贝构造函数的递归问题:拷贝构造函数的主要作用是复制一个对象。当我们定义一个按值传递的拷贝构造函数时,编译器必须在调用时创建该参数的副本,这意味着再次调用拷贝构造函数。这样就进入了一个无限递归的调用过程,最终导致栈溢出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值