C++笔试强训第五天

Posted 不 良

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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++笔试强训第二天

选择题

解析:考查printf%后面-表示输出左对齐,输出左对齐30个字符格式为%-30f.后面表示精度。%e字符以指数形势输出,可以认为是double类型(也就是小数点后保留6位)的指数。为%f字符表示输出格式为double类型。所以上面题目要求的格式为%-30.4f

C语言中要求我们掌握的各种输出如下:

整形输出:%d整型输出,%ld长整型输出,%u以十进制数输出unsigned型数据(无符号数);

进制输出:%o以八进制数形式输出整数,%x以十六进制数形式输出整数;

字符输出:%c用来输出一个字符,%s用来输出一个字符串;

浮点数输出:%f用来输出实数,以小数形式输出,%e以指数形式输出实数,%g根据大小自动选f格式或e格式,且不输出无意义的零。

解析:本题考查const修饰及指针相关知识。

指针常量:指针是个常量,即指针的指向不能改变,指针指向空间的内容可以改变;

常量指针:指针指向的空间是个常量,指针指向空间的值不能改变,指针的指向可以改变。

我们判断是指针常量还是常量指针通常是看const*所在的位置,如int *const p3 = &iconst后面紧跟的就是指针变量p3,这就是指针常量,指针的指向不能改变,指针指向空间的内容可以改变;而int const *p2 = &iconst后面紧跟的是*p2,此时就是常量指针,指针的指向可以改变指针,指向空间的值不能改变。

(1)中const修饰的是*p1,是常量指针,指针的值不能改变,指针的指向可以改变。只不过该指针没有给初始值,正确;

(2)中const修饰的是*p2,常量指针,指针的值不能改变,指针的指向可以改变,正确;

(3)中p2 = &j改变了指针p2的指向,正确;

(4)中const修饰的是p3,是指针常量即指针的指向不能修改,指向空间的内容可以修改,正确;

(5)中修改了p3指向空间的内容,没有修改指针指向,正确;

(6)中想修改p2指向空间的内容,但是p2是常量指针,不能够修改,错误;

(7)中修改了p3指针的指向,但是p3是指针常量,不能修改指针指向,错误。

所以本题中错误的就是(6)、(7)。

解析:本题主要考查常量字符串。acXacY都是存在栈区的字符数组,acXacY的区别在于acX存在\\0,而acY中不存在\\0,所以acX的空间比acY的空间大。而szXszY字符指针指向的常量字符串是存在常量区(静态区)的,szXszY这两个字符指针指向的是同一个地址,常量区的内容只能读不能被修改,所以D错误。

解析:数组名表示数组首元素的地址, int *b = a意思就是指针b指向b的地址,而b表示的数组首元素的地址;*b得到的就是数组中第一个元素即1, *b += 2就是将数组中第一个元素修改为3;b + 2表示的是数组中第3个元素即数组元素3的地址, *(b + 2) = 2表示的是将数组中第3个元素修改为2;b++则表示b此时指向的地址是数组中第2个元素的地址。经过上面的修改之后数组就变为[3,2,2,4]则输出语句中*b表示的就是数组中第2个元素,即2;*(b+2)表示的是第4个元素,即4。所以最后输出结果为2,4。

解析:宏定义在程序预处理的时候是在需要的地方直接展开的,所以不会检查参数的正确性,但可以减少函数调用所需的栈帧和压栈的消耗,可以提高运算效率;宏的嵌套过多可能会出现优先级的问题,容易出错且可读性差。我们平常使用要尽量避免宏定义常量,可以使用const修饰的常量,有类型的检查。所以选B。

解析:本题主要考查数组传参。数组传参形参可以是数组,也可以是指针。如下面例子:
int arr[10]传参,他的参数用数组接收可以是 int arr[]或者int arr[10];用指针接收是int* arr。
int* arr[10]传参,他的参数用数组接收是int* arr[10],用指针接收是int** arr

解析:本题考查指针数组、函数指针及函数指针数组。

指针数组:存放指针的数组。

函数指针:int(*pf)(int,int)中,int是函数返回类型,pf是函数指针变量,(int,int)是函数的参数类型,在调用函数的时候可以通过函数指针int ret = (*pf)(a,b),使用函数指针时,也可以省略*int ret = pf(a,b)

函数指针数组:可以存放多个返回类型相同和参数相同的函数的地址。可以写成int(*pf[5])(int, int)形式。

根据题目描述,可以理解为该变量是一个数组,里面有10个元素,每个元素都是一个函数指针,该函数指针返回类型是整型,参数是整数。所以该数组为函数指针数组,所以选D。A表示的是一个指针数组,数组元素类型为int*;B表示一个数组指针,指向数组的指针,存放数组的地址,a是数组指针变量;C表示的是一个返回类型为int,函数参数为int的函数指针变量。

解析:可以假设数组a中元素为"ABZ",则当i = 0时,即第一个元素A时,A选项中,元素下标为负数,排除;当i = 1即元素为B时,B中数组下标也为负数,排除;C选项中count数组下标随着i变化,当数组中元素相同时,并不会累加。如数组元素为"AAA"时,并不能统计出A的个数是多少。D中当a数组元素为"ABZ"时,count数组元素下标是逆着来的,也就是字符A在数组count中的下标表示为26,所以打印的时候先输出的是Z的个数,D正确。

解析:本题考查结构体内存对齐和位段。题目中unsigned就是指unsigned int,位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。因为是unsigned int类型,所以每次开辟4个字节。刚开始存储a时开辟4个字节即32个bit位,使用19个还剩13个,大于b所要使用的11个bit位,b继续使用,使用之后还剩2个不够c使用,需要再开辟4个字节的空间,c使用4个bit之后还剩28个,不够d使用,再开辟4个字节用来存储d。此时已经使用12个字节;而char类型变量index需要1个字节,所以总共使用了13个字节,但是结构体必须是最大对齐数的整数倍,即必须是4的倍数,所以是16。

解析:&数组名表示取的是整个数组的地址。所以&a取的就是整个数组的地址,数组a中有4个元素,所以数组a的大小就是16个字节,(&a + 1)表示跳过整个数组,指向数组后面的那个地址,ptr - 1表示的是指向数组中最后一个元素。所以结果为4。

编程题

1.排序子序列

解析:暴力解法,一个一个遍历和比较,如1 2 3 3 2 1,最开始先拿1和2比较,再比较2和3,依次向后进行比较;当非递增非递减序列停止的时候count++。注意为了避免a[i+1]的越界,我们在数组后加一个零,因为由题干可知,所有的数据都比0大且0在第n个位置,所以不会对结果有影响。

从这道题可以抽象出来一个模型:在一个数组中截取一段连续的性质相同的序列,可以采用while–if–while模型。

while(A)

if(B)

   while(A&&B)
   
     ++i;
   

if(C)

   while(A&&C)
   
     ++i;
   

//.......


该模型的意思是,找到同时满足A和B的序列,其中A为i前进条件,B为元素所满足的条件。当序列中下一个元素不满足B的时候,同时退出while与if语句。方便查找满足另一个条件C的序列。

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

    int n = 0;
    cin >> n;
    vector<int> arr;
    //多开辟一个空间,防止越界情况的发生
    arr.resize(n+1);
    int i = 0;
    int count = 0;
    for(int i = 0; i < n; i++)
    
        cin >> arr[i];
    
    i = 0;
    while(i < n)
    
        //非递增序列
        if(arr[i] > arr[i+1])
        
            while(i < n && arr[i] >= arr[i+1])  
            
                i++;
            
            count++;
            i++;
        
        else if(arr[i] == arr[i+1]) //相等
        
            i++;
        
        else
            //非递减序列
            while(i < n && arr[i] <= arr[i+1])
            
                i++;
            
            count++;
            i++;
        
    
    cout << count;
    return 0;

答案解析:基本思路和上面一样

  1. 本题依次比较整个数组

  2. a[i+1]>a[i] ,则进入非递减序列判断,直到遍历到下一个值不大于等于为止count++,然后进行下一位
    置的判断

  3. a[i+1]<a[i],则进入非递增序列判断,直到遍历到下一个值不小于等于为止count++,然后进行下一位
    置的判断

  4. a[i+1] == a[i]不进行操作,++i进行下一位置遍历,因为相等既可以属于非递增序列,也可以属于非递减
    序列。
    本题注意点:本题开始比较a[i+1]与a[i]进行比较,为了避免越界,数组定义为n+1个,同时给a[n] = 0;
    a[n] = 0带来的影响,我们分为三种情况讨论:

  5. 若到a[n-1] 的最后一组是非递减序列,当i=n-1,a[i] >a[i+1],因为前面的数都是大于0的,这个输入
    条件已经说明了(去看看题目输入条件描述),里面的循环结束,i++,count++,i==n,外面的循环结
    束。

  6. 若到a[n-1] 的最后一组是非递增序列,当i=n-1,a[i] >a[i+1],因为前面的数都是大于0的,这个输入
    条件已经说明了(去看看题目输入条件描述),循环再走一次,i++, i== n,里面的循环结束,i++,
    count++,i==n+1,外面的循环结束。

  7. 第三种情况 1 2 1 2 1最后一个数是单独的情况,后面补个0,序列变成1 2 1 2 1 0,当走完全面的序列
    i==n-1时,a[i] > a[i+1],进入判断出一个非递增序列,count++,i++,循环结束。

  8. 也就是说数组最后一个位置多增加一个0,不会影响第1、2情况的判断,主要是帮助第3情况的正确判
    断。

#include<iostream>
#include<vector>
using namespace std;
// 本题牛客测试用例不全,至少应该增加以下两组测试用例
// 输入:
// 4
// 1 3 2 3
// 输出:2
// 输入:
// 6
// 3 2 1 1 2 3
// 输出:2
int main()

    int n;
    cin >> n;
    // 注意这里多给了一个值,是处理越界的情况的比较,具体参考上面的解题思路
    vector<int> a;
    a.resize(n + 1);//这里有个坑,这个题越界了牛客测不出来,给n,并且不写a[n] = 0;不会报错,但是最好写上
    a[n] = 0;
    //读入数组
    int i = 0;
    for (i = 0; i < n; ++i)
        cin >> a[i];
    i = 0;
    int count = 0;
    while (i < n)
    
        // 非递减子序列
        if (a[i] < a[i + 1])
        
            while (i < n && a[i] <= a[i + 1])
                i++;
            
            count++;
            i++;
        
        else if (a[i] == a[i + 1])
        
            i++;
        
        else // 非递增子序列
        
            while (i < n && a[i] >= a[i + 1])
                i++;
        	
            count++;
            i++;
        
    
    cout << count << endl;
    return 0;

2.倒置字符串

解析:整体思路可以先将整个字符串逆置,再从前往后依次将每个单词逆置。使用string类中的getline函数输入字符串,再使用reverse函数将整个字符串逆置,这个函数有两个参数,参数为迭代器这里可以将迭代器理解为指针,``reverse函数要带上头文件algorithm。如上面示例,整个字符串逆置之后为.gnijieb ekil I`,再依次将每个单词逆置。注意不能越界。

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main() 
    string s1;
    getline(cin,s1);//输入字符串
    //s1.begin()代表字符串开始位置,s1.end()代表字符串结束位置,这里可以将迭代器理解为指针,那么在字符串.gnijieb ekil I中s1.begin()指向开始位置'.',s1.end()指向字符串结尾即指向'\\0'位置。
    reverse(s1.begin(),s1.end());//迭代器为左闭右开,即[s1.begin(),s1.end()),所以逆置的时候不包括'\\0'
    for(int i = 0; i < s1.size(); i++)
    
        int tmp = i;//tmp记录每次逆置的开始位置
        while(i < s1.size() && s1[i] != ' ')//当s1[i] == ' '时或者当i < s1.size(),结束循环,进行逆置
        
            i++;
        
        
        //逆置,字符串.gnijieb ekil I 
        //第一次逆置tmp = 0,i = 8,即s1[i] == ' ',左闭右开,即对[0,7]之间的字符串逆置,逆置之后字符串为beijing. ekil I;
        //第二次逆置,tmp = 9,i = 13,即s1[i] == ' ',左闭右开,即对[9,12]之间的字符串逆置,逆置之后字符串为beijing. like I;
        //第三次逆置,tmp = 14,i = 15,即i < s1.size()不成立,对[14]这个字符串进行处理,逆置之后字符串为beijing. like I;
        reverse(s1.begin() + tmp,s1.begin() + i); //左闭右开
    
    cout << s1;

答案解析:

思路一:先将整个字符串逆置过来,再遍历字符串,找出每个单词,对单词逆置。这里我们使用了stl算法中的reverse,所以这里使用迭代器遍历string。和上面一样。

思路二:直接利用cin>>s接收输入,遇到空格就结束了,自然就分割开了每个单词,其次将每次接收到的单词拼接到之前串的前面就逆置过来了。代码如下:

#include <iostream>
#include <string>
using namespace std;
// cin读取string时自动会被空格分隔开,用另一个字符串存储进行逆序输出
int main()

    string s1, s2;
    cin >> s2;
    while (cin >> s1)
        s2 = s1 + " " + s2;
    cout << s2 << endl;
    return 0;

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

C++笔试强训第三十天

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

C++笔试强训第八天

C++笔试强训第六天

C++笔试强训第十一天

笔试强训之每日一题