C++笔试强训第八天

Posted 不 良

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++笔试强训第八天相关的知识,希望对你有一定的参考价值。

选择题

解析:函数重载:在相同作用域中,函数名字相同,参数列表不同的一系列函数称之为函数重载。

参数列表的不同具体表现在:参数个数不同、参数类型不同、参数类型次序不同。与函数类型返回值没有关系。

解析:A 引用是给变量取别名,需要知道是给谁取的别名,所以必须初始化,指针不强制要求;

B 引用初始化之后就不能被二次引用,但指针指向的对象可以改变;

C 引用不存在空,指针可以指向空;

D 引用就是变量别名;

E 引用底层使用是使用指针来实现的,引用的本质就是一个指针,所以在传参的时候也是传实参的地址;

F 函数参数可以声明为引用或指针类型。

解析:在类域中,public修饰的成员是公有的,可以在类外被访问;private或protected不可以在类外访问。

解析:在main函数中Sample S(5)创建对象的时候需要调用构造函数,在析构函数中delete说明需要使用new申请新空间,而p指向新空间的地址,new int(x)表明要给新申请的空间赋值。

解析:拷贝构造函数是一个特殊的构造函数,是单参的,参数的类型必须是类类型&,如果使用传值传参会一直递归,一般情况下会使用const修饰,eg:const A& a

当用已经存在的对象构造新对象时,编译器会自动调用拷贝构造函数;

A:当创建一个新的对象时要调用构造函数,但是编译器会做优化,如下图所示,构造+拷贝构造优化为拷贝构造;

B:将类的一个对象赋值给该类的另一个对象时,调用的是赋值运算符,如下图

C:函数的形参对象,调用函数进行形参和实参结合时,即对类对象进行函数传参时也会调用拷贝构造函数

D:函数的返回值是类的对象,函数执行返回调用时;函数执行返回调用时会先进行拷贝构造,将返回值放在一个临时对象中,因为函数执行完栈帧会销毁,所以返回的时候返回的是拷贝构造产生的临时对象。

解析:从main函数中第一条指令开始分析,Widget x调用构造函数;Widget y=f(f(x));先执行里面的f(x),传参的时候会调用一次拷贝构造,函数内部Widget v(u)创建新对象的时候也会调用一次拷贝构造,Widget w=v的时候也调用一次拷贝构造;return w的时候会调用拷贝构造创建一个临时变量用来返回;到现在调用了4次拷贝构造了,接着执行外面一层函数调用,又是4次拷贝构造;函数调用完成之后,用一个已经存在的对象去创建新对象时,也会调用拷贝构造,所以一共调用了9次拷贝构造。

注意:编译器在调用拷贝对象时会进行一些优化,在返回值的时候我们需要创建临时对象,编译器可能会直接拿着临时对象使用,这样子可以少调用一次,所以可以认为是调用了7次拷贝构造。

解析:什么时候需要运算符重载?当用户定义了一个类之后,然后想要通过该类的对象直接使用某种运算符时编译器不支持,原因就是类对象中可能有多个成员,在用该类的对象进行相应的运算符操作时,编译器不知道该如何处理。

函数重载运算符,函数形参个数要根据运算符的操作数决定。

运算符重载成类的成员函数时,因为在类里面函数有隐藏的this指针,所以重载运算符时形参数目看起来比该运算符的操作数少1;

重载成类的友元函数时,必须要有一个参数是类类型的对象。

D中没有任何参数明显错误,而且运算符必须要有操作数。

解析:因为先创建A类型的变量,所以先调用A的构造函数;再创建B类型的变量,调用B的构造函数;B先调用析构函数然后是A,可以理解为进栈和出栈:A先进栈,B才进栈,所以要B先出栈,A才能出栈。

解析:在main函数中创建了一个cla类型的对象,然后deletecla类中的成员变量是静态变量,在类外定义n = 0new的时候要调用构造函数,delete的时候要调用析构函数,所以最后n还是为0。

解析:C/C++没有内存回收机制,需要考虑内存管理的问题,为了避免内存泄漏,所有在堆上申请的内存都需要用户手动释放;

悬挂引用(悬挂指针):指向已经释放的地址;首先必须明白空悬指针(悬挂指针)是针对动态内存来说明的:

eg:
	p=new int ;
	delete p;

当我们delete一个指针后,指针值就变为无效了。虽然指针已经无效,但在栈上的指针仍保留着(已经释放了的)动态内存的地址。在delete之后,指针就变成了人们所说的空悬指针,即,指向一块曾经保存数据对象但现在已经无效的内存的指针。

未初始化指针的所有缺点空悬指针也都有。有一种方法可以避免空悬指针的问题:在指针即将要理考其作用域之前释放掉它所关联的内存。这样,在指针关联的内存被释放后,就没有机会使用指针了。如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就可以清楚地指出指针不指向任何对象。

程序的动态性越强,内存管理就越重要,内存分配程序的选择也就更重要;

编程题

1.两种排序方法

解析:创建string类型的数组,存储数据元素,对数组中每个元素进行长度比较和字符大小比较。需要注意的是当数组中两个字符串中首个字符相同的情况下要继续比较这两个字符串中下一个字符,这样才能确定是不是根据字典序排序。

#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() 
    int n;
    cin >> n;
    vector<string> s;//定义一个字符指针数组
    s.resize(n);//开辟空间
    for (int i = 0; i < s.size(); i++) 
        cin >> s[i];//使用getline输入的时候会少输入一个字符串,所以当输入的字符串没有空格的时候使用cin
    
    int i = 0;
    int flag1 = 1;//标记次数,从1开始后面能够和
    int flag2 = 1;
    while (i < n - 1)  //n个元素只需要比较n-1次
        if (s[i][0] < s[i + 1][0]) //等
        
            flag1++;
        
        //注意当两个字符串中首个字符相同的时候要继续比较该字符串中下一个位置
        else if(s[i][0] == s[i + 1][0]) 
            int j = 1;
            while (s[i][j] == s[i + 1][j])  //当不相同时退出循环
                j++;
            
            if(s[i][j] < s[i + 1][j]) //再次进行比较
                flag1++;
            
        
        if (s[i].length() < s[i + 1].length()) //比较长度
            flag2++;
        i++;//每次增加
    
    
    //注意下面ifelse条件的顺序不能改变
    if (flag1 == s.size() && flag2 == s.size())   //先看两者是否都符合
        cout << "both";
        return  0;
     
    else if (flag1 == s.size() && flag2 != s.size()) 
        cout << "lexicographically";
        return 0;
    
    else if (flag2 == s.size() && flag1 != s.size()) 
        cout << "lengths";
        return 0;
    
    else 
        cout << "none";
    
    return 0;

答案解析:思路很简单,将接受的字符串都放到vector容器中,利用string的operator>=运算符重载来按ascii比较字符串,利用string的size来比较字符串的长度

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()

    int n;
    cin>>n;
    vector<string> v;
    v.resize(n);
    for(auto& str : v)
        cin>>str;
    bool lenSym = true, lexSym = true;
    // 这里要注意从i=1开始遍历,前后比较,比较长度
    for(size_t i = 1; i < v.size(); ++i)
    
        if(v[i-1].size() >= v[i].size())
        
            lenSym = false;
            break;
        
    
    //比较ASCII码
    for(size_t i = 1; i < v.size(); ++i)
    
        if(v[i-1] >= v[i])
        
            lexSym = false;
            break;
        
    
    if (lenSym&& lexSym)
        cout<<"both"<<endl;
    else if (!lenSym && lexSym)
        cout<<"lexicographically"<<endl;
    else if (lenSym && !lexSym)
        cout<<"lengths"<<endl;
    else if (!lenSym&&!lexSym)
        cout<<"none"<<endl;
    return 0;

2.最小公倍数

解析:

本题可以使用上图中的第三种方法,扩大法。就是将叫较大数每次扩大1倍,再去%较小数,当取模为0的时候说明该数就是最小公倍数。

#include <iostream>
using namespace std;

int main() 
    int A ,B;
    cin >> A >> B;
    //先找出较大的那一个
    int max = A;
    int min = B;
    if(max < B)
    
        max = B;
        min = A;
    
    //从2倍开始比较,找较大数的倍数,然后%较小数,当==0时n就是最小公倍数
    int i = 2;
    int n = max;
    while(n % min)
    
        n = max*i;
        i++;
    
    cout << n;
    return 0;

// 64 位输出请用 printf("%lld")

答案解析:最小公倍数 = 两数之积除以最大公约数,这里使用碾转相除法进行最大公约数的求解:即a与b的最大公约数可以转化为a、b之间的余数为两者之间最小的数之间的公约数。所以对于输入的两个数进行连续求余,直到余数为0,求余的分母即为结果。

#include<iostream>
using namespace std;
int gcd(int a, int b)

    int r;
    while(r = a%b)
        a = b;
        b = r;
    
    return b;

int main()

    int a,b;
    while(cin >> a >> b)
        cout << a*b/gcd(a,b) <<endl;
    
    return 0;

C++笔试强训第五天

选择题

解析:本题考查do while循环及后置++。刚开始x = 1,先进入循环中,后置++是先使用再++,所以printf输出结果为 1。++后x的值变为2,经while(x–)后又变为1,再次进入循环…… 所以该程序将会陷入死循环。

解析: sizeof 是一个操作符,用于计算变量或类型的大小,一般单位为字节,通常用于计算内存大小。 strlen 是函数,用于计算字符串的长度,只统计字符串中字符的数量,不包括结尾的空字符。sizeof是用于计算内存占用,strlen主要是用于计算字符串的长度。sizeof(dog)计算的是整个字符数组所占内存大小,字符串中的’\\0’算是一个字符,计算字符串大小的时候也要加上末尾的结束字符’\\0’;strlen遇见’\\0’就停止。所以选A。

解析:strcpy ( char * destination, const char * source )函数是覆盖拷贝,将source全覆盖拷贝到destination,会把’\\0’也拷过去,且必须考虑destination的空间够不够(destination的空间必须>=source的空间)。

strcat( char * destination, const char * source ) 为追加拷贝函数,将source追加到目标空间后面,目标空间必须足够大,能容纳下源字符串的内容。

strcat是字符串追加的库函数,第一个参数为被追加字符的首地址,从首地址开始字符串的结尾,第二个参数是追加字符的首地址,从此位置向被追加的位置开始进行追加。成功就返回被追加字符的首地址。题目strcat(p1+2,p2+1)中,p2+1表示指向字符串"ABCD"中B的地址,p1+2表示指向"abcd"中c的地址即被追加字符首地址,所以这句代码的意思就是将"BCD"追加到"cd"的后面,然后返回被追加字符的首地址,追加后的结果为:cdBCD,返回的是c的地址。然后进行拷贝,从str+2的位置进行拷贝。最后选D。

解析:

n数组可表示为上图所示,int (*p)[3]表示这是一个数组指针,存放的是数组的地址。p指向数组的第0行,p[0][0]等价于n[0][0],即是第1行第1个元素,即10;*(p[0]+1)也可以用p[0][1]*(*(p+0)+1)表示,就是第1行第2个元素,即20;(*p)[2]可以表示为*(p+0)[2],即第1行第3个元素30,所以答案选B。

解析:C/C++程序的入口函数是main函数;main函数可以放在程序的任意位置;要调用的函数可以不在main函数中,也可以在别的函数中调用。

解析:在定义的时候,由于a是int类型,字符’1’会发生隐式类型转换,转换成整形,b、c也同样会发生类型抓换;传参的时候也会发生隐式类型转换,转化成对应的字符。fun(a,b)返回的是’1’,fun(b,c)返回的是’1’,最后就是fun(‘1’,‘1’),所以最后输出的就是字符’1’。

解析:int* pa[5]是一个指针数组,具有5个元素,每个元素都是int类型的指针。

解析:pragma pack()设置默认对齐数。

结构体的内存对齐规则:
(1)结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
(2)从第二个成员开始,要对齐到某个对齐数的整数倍的偏移处。(对齐数:结构体成员自身大小和默认对齐数中的较小值,VS下默认对齐数是8个字节;Linux环境下默认不设对齐数,对齐数是结构体成员的自身大小)
(3)结构体的总大小必须是最大对齐数的整数倍。
(4)如果嵌套了结构体的情况,嵌套的结构体对齐到最大对齐数的整数倍处,结构体的整体大小就是所有对齐数(含嵌套结构体的对齐数)中最大对齐数的整数倍。

(1)对齐数为4的情况下:

struct One中成员变量d的类型为double,占8个字节;char类型成员变量c占1个字节;int类型成员变量i占4个字节,但是必须从对齐数的整数倍开始,所以从12开始向后偏移4个字节,所以总共占了16个字节;

struct Two中char类型成员变量c占1个字节;成员变量d的类型为double,占8个字节,但是由结构体内存对齐规则的(2)可知,从第二个成员变量开始,要对齐到某个对齐数的整数倍的偏移处,对齐数为成员变量自身大小(8)和默认对齐数(4)中的较小值,即4。所以从4开始向后偏移8个字节;int类型成员变量i占4个字节,也要从对齐数的整数倍开始,所以从12开始向后偏移4个字节,所以总共占了16个字节。

(2)对齐数为8的情况下:

struct One中成员变量d的类型为double,占8个字节;char类型成员变量c占1个字节;int类型成员变量i占4个字节,但是必须从4的整数倍开始,所以从12开始向后偏移4个字节,所以总共占了16个字节;

struct Two中char类型成员变量c占1个字节;成员变量d的类型为double,占8个字节,但是由结构体内存对齐规则的(2)可知,从第二个成员变量开始,要对齐到某个对齐数的整数倍的偏移处,对齐数为成员变量自身大小(8)和默认对齐数(8)中的较小值,即8。所以从8开始向后偏移8个字节;int类型成员变量i占4个字节,也要从对齐数的整数倍开始,即从4的整数倍开始,所以从16开始向后偏移4个字节,所以总共占了24个字节。

解析:a[i]可以表示为*(a + i)a[i][j]可以表示为*(*(a + i) + j)以此类推……,需要一层一层的解引用。

解析:

预处理是将宏定义展开,将被包含的文件插入到该编译指令的位置等;

编译就是把预处理完的文件,进行语法分析、词法分析、语义分析及优化后生成相应的汇编代码文件,这个过程是整个程序构建的核心过程,也是最复杂的部分;

汇编是将汇编代码文件转变成机器可以执行的指令文件,即目标文件

链接:将这些模块组装起来的过程就是链接,链接的时候去找对应函数,未定义的函数就会报错——该函数没有定义。

编程题

1.统计回文

解析:保存刚开始的s1,从头到尾将s2插入到s1中,然后翻转比较。

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

int main() 
    string s1;
    string s2;
    getline(cin, s1);
    getline(cin, s2);
    string s3;
    string s4;
    s3 = s1;//刚开始保存s1,防止后续s1插入之后被改变
    int count = 0;//用来记录相同次数
    int i = 0;
    while (i < s1.size()+1)  //i的值要比size大1,因为在字符串的最后也要插入一次
        //s1 = s3;//不能在这里写
        s1.insert(i, s2); //插入
        s4 = s1;//插入之后赋值给s4,方便后续翻转
        
        //翻转比较是否相同
        reverse(s1.begin(), s1.end()); 
        if (s1 == s4)
            count++;
        s1 = s3;
        i++;
    
    cout << count;

答案解析:什么是回文字符串,题目里面说就是一个正读和反读都一样的字符串 ,回文串也就是前后对称的字符串。本
题是判断是否是回文串的变形题。字符串本身不一定是回文,把第二个字符串插入进去看是否是回文。

本题使用暴力求解方式计算即可,遍历str1,将str2 insert进入str1的每个位置,判断是否是回文,是就
++count;需要注意的是这里不能 str1.insert(i, str2),这样的话str1改变了,判断下一个位置就不对了。所
以每次使用str1拷贝构造一个str,然后str.insert(i, str2),再判断。

#include<iostream>
#include<string>
using namespace std;
// 判断是否是回文
bool IsCircleText(const string& s)

    size_t begin = 0;
    size_t end = s.size()-1;
    while(begin < end)
    
        if(s[begin] != s[end])
            return false;
        ++begin;
        --end;
    
    return true;

int main()

    std::string str1, str2;
    getline(cin, str1);
    getline(cin, str2);
    size_t count = 0;
    for(size_t i = 0; i <= str1.size(); ++i)
    
        // 将字符串2插入到字符串1的每个位置,再判断是否是回文
        string str = str1;
        str.insert(i, str2);
        if(IsCircleText(str))
    
    cout<<count<<endl;
    return 0;

2.连续最大和

答案解析:本题是一个经典的动规问题,简称dp问题,但是不要害怕,这个问题是非常简单的dp问题,而且经常会考察,所以大家一定要把这个题做会。本题题意很简单,就是求哪一段的子数组的和最大。

【解题思路】:状态方程式: max( dp[ i ] ) = getMax( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )
dp[i] 就是以数组下标为 i 的数做为结尾的最大子序列和,注意是以 i 为结尾,比如说现在有一个数组6,-3,-2,7,-15,1,2,2,dp[2]就是以-2为结尾的,那么显然dp[2]的最大值就是1(6,-3,-2),dp[3]要以7结尾那么以7结尾的子序列最大和就是8(6,-3,-2,7)。现在我们开始细细品一下上面这个递推式,求dp[i]的时候是不是有两种可能,要么就是像上面的dp[3]一样,dp[2]求出来是1了,再加上自己array[3]是最大的,那么还有一种可能就是说如果dp[2]我求出来是-100,那如果我也是dp[2]+array[3]的话是-93, 这时候dp[2]反而是累赘,最大就是自己(因为前面定义了必须以i为结尾,也就说必须以7结尾)

状态方程式: max( dp[ i ] ) = getMax( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )

#include <iostream>
#include<vector>
using namespace std;
int GetMax(int a, int b) //得到两个数的最大值

	return (a) > (b) ? (a) : (b);

int main()
    int size;
    cin >> size;
    vector<int> nums(size);
    for(size_t i = 0; i < size; ++i)
        cin >> nums[i];
    int Sum = nums[0]; //临时最大值
    int MAX = nums[0]; //比较之后的最大值
    for (int i = 1; i < size; i++)
    
        Sum = GetMax(Sum + nums[i], nums[i]); //状态方程
        if (Sum >= MAX)
            MAX = Sum;
        //下面的代码具有同等的效果
       /*
       //相当于当Sum + a[i] < a[i]时,就从i处开始重新计算字串了。
       if(Sum + a[i] > a[i])
             Sum = Sum + a[i];
        else
            Sum = a[i];
        if (Sum >= MAX)
            MAX = Sum;
       */
    
    cout << MAX << endl;
    return 0;


以上是关于C++笔试强训第八天的主要内容,如果未能解决你的问题,请参考以下文章

C++笔试强训第三十一天

C++笔试强训第二天

C++笔试强训第五天

C++笔试强训第六天

C++笔试强训第十一天

实训第八天