栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++】专项练习(2)(牛客)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++】专项练习(2)(牛客)

【C++】专项练习(2)(牛客)
  • 1、按位与运算符(&)
    • 按位与运算符(&)
    • 按位或运算符(|)
      • 拓展函数:
  • 2、指针是否偏移
  • 3、二维数组与指针
    • 知识点
  • 4、面向对象特点
    • 封装
    • 继承
    • 多态
  • 5、ASCII 码、BCD码、内部码、十进制码
  • 6、递归
  • 7、带默认形参值的函数
    • 知识点
  • 8、指针数组和数组指针
    • 指针数组
    • 数组指针
    • 区分
  • 9、常对象(const)与常函数
    • 常对象
  • 10、构造函数与析构函数的调用顺序
  • 11、非零即为真
  • 12、**%d在遇到非数字字符时scanf认为输入结束**
  • 13、vector
    • vector::at()
      • 代码示例
    • vector::operator[]
  • 14、迭代器 遍历
  • 15、fork()遇到for循环
    • fork
  • 16、父类子类继承
  • 17、合法的浮点型数据
  • 18、*p++ 运算符优先级
    • **运算符优先级表**
  • 19、if、else匹配
  • 20、gcc与g++

1、按位与运算符(&)

题目
下面程序中, 输出是什么?

int fun(int x){
    int count = 0;
    while(x){
        count++;
        x = x &(x-1);
    }
    return count;
}
int main(){
    cout << "fun(2015)=" << fun(2015)< 

思路
该函数的功能:用于查找数值的二进制中有多少个1
x = x &(x-1);是什么意思:按位与运算将x用二进制表示时最右边的一个1变为0

按位与运算符(&)

参加运算的两个数,按二进制位进行“与”运算。

运算规则:只有两个数的二进制同时为1,结果才为1,否则为0。(负数按补码形式参加按位与运算)

按位或运算符(|)

参加运算的两个数,按二进制位进行“或”运算。
运算规则:参加运算的两个数只要两个数中的一个为1,结果就为1。

拓展函数:

用于查找数值的二进制中有多少个0
每一次或运算,x的二进制从低位到高位,遇到bit为0依次少一个0

while(x){
        count++;
        x = x | (x+1);
    }//统计x的二进制中有多少个0

来源:牛客网
解析:
每执行一次x = x&(x-1),会将x用二进制表示时最右边的一个1变为0,因为x-1将会将该位(x用二进制表示时最右边的一个1)变为0。
2015 二进制 11111011111
2014 二进制 11111011110
按位与运算(有0则0)得:11111011110
该函数旨在计算二进制有多少个1----10个

2、指针是否偏移

题目
以下程序的运行结果是()

#include 
using namespace std;

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

void fun(int* pa, int n);

int main() {
    int m = 10;
    fun(a, m);
    cout << a[6] << endl;
}

void fun(int* pa, int n) {
    for (int i = n - 1; i > 0; i--)
        *(pa + 6) += pa[i];
}

思路

*(pa+6)=pa[6]
第一次循环:
pa[6]=pa[6]+pa[9]=7
2:pa[3]=pa[3]+pa[8]=13
指针一直向前偏移6位,会从头开始吧。
3:8
4:13
5:8
6:13
7:8
8:3
9:8
10:3
错因:以为 *(pa+6)=pa[6]是指针偏移并赋值的意思,其实一直都是指向数组的第7个元素。
解析:
关键点: *(pa + 6) += pa[i];是什么意思? *(pa + 6):数组中的第七个元素
i=9: a[6]=a[6]+a[9]=7+0=7
i=8:a[6]=a[6]+a[8]=7+9=16
i=7:a[6]=a[6]+a[7]=16+8=24
i=6:a[6]=a[6]+a[6]=24+24=48
i=5:a[6]=a[6]+a[5]=48+6=54
i=4:a[6]=a[6]+a[4]=54+5=59
i=3:a[6]=a[6]+a[3]=59+4=63
i=2:a[6]=a[6]+a[2]=63+3=66
i=1:a[6]=a[6]+a[1]=66+2=68
此时i=1>0,i++,i不满足循环条件,退出循环。

3、二维数组与指针

题目

知识点

解析

1、&a+1跨整个变量(二维数组)。
2、a+1跨一维数组。
3、a[0]+1跨一个int。

a是数组首地址,也就是a[0]的地址,
&a是对象(数组)首地址,
a+1是数组下一元素的地址,即a[1],
&a+1是下一个对象的地址。

所以int*(&a+1)意思是ptr指向下个数组变量的地址,ptr-3表示指针向前偏移三个元素的位置,也就是指向a[1][2]的地址,加*意思是取该地址存储的内容数据,也就是9.

4、面向对象特点

题目
面向对象方法的多态性是指()
一个类可以派生出多个特殊类
一个对象在不同的运行环境中可以有不同的变体
针对一消息,不同的对象可以以适合自身的方式加以响应
一个对象可以是由多个其他对象组合而成的
知识点
C++ 面向对象三大特征(封装、继承、多态)—BC菜鸟

封装

封装(encapsulation)即信息隐蔽。它是指在确定系统的某一部分内容时,应考虑到其它部分的信息及联系都在这一部分的内部进行,外部各部分之间的信息联系应尽可能的少。

public:对外公开,访问级别最高
protected:只对同一个包中的类或者子类公开
默认:只对同一个包中的类公开
private:不对外公开,只能在对象内部访问,访问级别最低

struct 和 class 区别:struct默认权限是public ;class默认权限是private

继承

我们发现,定义这些类时,下级别的成员除了拥有上一 级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码
继承:让某个类型的对象获得另一个类型的对象的属性和方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1)继承父类成员变量/方法
2)重写父类方法
3)重载父类方法
重载方法必须满足以下条件:
1、方法名相同
2、方法的参数类型、个数、顺序至少有一项不同
3、方法的返回类型可以不同
4、方法的修饰符可以不相同

多态

多态:对于同一个行为,不同的子类对象具有不同的表现形式。
多态存在的3个条件:
1)继承 2)重写 3)父类引用指向子类对象。
多态的实现方式分析:
覆盖:是指子类重新定义父类的虚函数的做法。
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
参考:什么是面向对象,它的三个基本特征:封装、继承、多态—冰棍hfv
解析

5、ASCII 码、BCD码、内部码、十进制码

题目
字符型常量在内存中存放的是
ASCII码
BCD码
内部码
十进制码

解析

√ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。
BCD码(Binary-Coded Decimal‎)亦称二进码十进数或二十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个数码。是一种二进制的数字编码形式。
内码是指汉字系统中使用的二进制字符编码。
十进制码面向的是开发人员,经过转化对应的编码给计算机使用。

6、递归

题目
根据下面递归函数:调用函数Fun(2),返回值是多少()

int Fun(int n)          
{ 
    if(n==5)     
        return 2;        
    else        
        return 2*Fun(n+1);           
}

解析
Fun(2)=2Fun(3)=22Fun(4)=222Fun(5)=222*2=16

7、带默认形参值的函数

题目
考虑函数原型void test(int a,int b=7,std::string z=“*”),下面的函数调用中,属于不合法调用的是( )
test(5)
test(5,8)
test(6,“#”)
test(0,0,“#”)

知识点

1、函数定义或者声明的时候,给形参赋默认值
调用函数时,如果没有给出实参,按指定的默认值进行工作

2、可能产生二义性:编译器不能确认调用的是哪个函数
3、需要从右向左为参数赋默认值,不然会报错
4、调用的时候,实参给的顺序:从左向右
5、函数声明时指定默认值后,定义时就不能再赋默认值

  • 有默认值的参数必须在放在形参列表的最右边
  • 形参的默认值必须从右往左写。这点条件来自于函数调用堆栈,形参在入栈的时候是从右往左入栈的

解析
此题可能无解
传递实参不能跳过

C选项的意图是希望跳过第二个参数,传递第三个参数,但是实际上会发生类型转换把#转换为int,所以编译起来其实是通过的

8、指针数组和数组指针

题目
对于int *pa[5];的描述,正确的是()

pa是一个具有5个元素的指针数组,每个元素是一个int类型的指针;
pa[5]表示某个数组的第5个元素的值;
pa是一个指向数组的指针,所指向的数组是5个int类型的元素;
pa是一个指向某个数组中第5个元素的指针,该元素是int类型的变量;

指针数组

定义:多个同类型指针的集合,指针类型的数组
int *ptr[5];指针数据类型 *指针数组名[指针元素个数]

数组指针

int (*ptr)[5];将变量名与*括起来,表示这是一个指针,指向一个数组的首地址

区分

区分int *p[n]; 和int (*p)[n]; 就要看运算符的优先级了。
int *p[n]; 中,运算符[ ]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组。
int (*p)[n]; 中( )优先级高,首先说明p是一个指针,指向一个整型的一维数组。
解析:
正确答案: A 你的答案: C (错误)
int *pa[5];为指针数组。

9、常对象(const)与常函数 常对象

常对象就是创建初始化之后不能改变的对象
常对象只能调用常函数,就是说函数声明是有const关键字的成员函数。

一旦将对象定义为常对象之后,不管是哪种形式,该对象就只能访问被 const 修饰的成员了(包括 const 成员变量和 const
成员函数),因为非 const 成员可能会修改对象的数据(编译器也会这样假设),C++禁止这样做。

10、构造函数与析构函数的调用顺序

题目:

class A;
class B;
int main() {
    A a;  
    B b;  
    return 0;
}

在 main 函数中,变量 a 和 b 的构造函数和析构函数的调用顺序是()
答案:a构造 - b构造 - b析构 - a析构
解析:

在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反,即先构造的后析构,后构造的先析构。它相当于一个栈,先进后出。

11、非零即为真

题目
对于条件表达式(k)?(i++):(i–)来说,其中的表达式k等价于()
k0
k
1
k!=0
k!=1
解析:
k为真(非零)i++,k为假(0)i–

12、%d在遇到非数字字符时scanf认为输入结束

有如下程序,输入数据:12345M678<CR>后(表示回车),x的值是()

#include
int main(){
    int x;
    float y;
    scanf("%3d%f", &x, &y);
    return 0;
}

12345
123
45
345
解析:
%3d表示输入的整数,其宽度为3,因此,只取123
输出结果123 45.000000(因为M对于单精度来说是非法数字,遇到它就结束)
%d在遇到非数字字符时scanf认为输入结束

如果遇到非数字字符,scanf(“%d”)是会自动忽略的,且认为读入已经结束了,如果是给数组赋值,非数字字符之后的空间其实都没有被成功赋值,

13、vector

w3school-vector
顺序容器:向量、双端队列、链表
1、向量
特点:地址是连续的,方便高效的访问单个元素,动态结构,大小不固定,可以在程序运行时增加或减少
 内存用尽时,向量自动分配更大(成倍)的连续内存区,将原先的元素复制到新的内存区,并释放旧的内存区。永远不会存在越界的情况
怎么用:
a.加头文件
b.实例化一个类模板:初始化:vector<数据类型> vec;
vector<数据类型> 对象名;创建向量对象的时候会走构造函数
c.查看容器大小vec.size() //元素个数

d.插入数据:vec.insert(vec.begin(),200);
vec[0] 第一个元素

e.删除:
e1.删除第三个:从头开始偏移2位
e2.删除第三个以及后面的

f.清空

g.访问向量中所有的元素
g1.for循环:

g2.迭代器访问
容器的end在最后一个元素的后面,所以迭代器不能为end
迭代器定义:
for循环

end为最后一个元素的下一个

vector::at()

但at()是我们的首选,因为at()进行了边界检查,如果访问超过了vector的范围,将抛出一个例外

代码示例
#include 
#include 
#include
using namespace std;
int main()
{
vectortest;//建立一个vector
test.push_back(1);
test.push_back(2);//把1和2压入vector,这样test[0]就是1,test[1]就是2
printf("%dn",test.at(0));
return 0;
}

vector::operator[]

operator[]主要是为了与C语言进行兼容。它可以像C语言数组一样操作。由于operator[]容易造成一些错误,所有我们很少用它。
题目:

T是一个数据类型,关于std::vector::at 和 std::vector::operator[] 描述正确的是:

at总是做边界检查, operator[] 不做边界检查.
at 不做边界检查, operator[] 做边界检查.
at和operator[] 是一样的

解析:

正确答案: A 你的答案: B (错误)
结论:at()总是做边界检查,下标运算符(operator[] )不做边界检查。
解析(详情参看 《C++ Primer》(第5版) P310 ”下标操作和安全的随机访问“):
提供快速随机访问的容器(如:string、vector、deque 和 array)也都提供下标运算符(operator [ ])。
一、下标运算符接受一个下标参数,返回容器中该位置的元素的引用。给定下标必须保证”在范围内“(即,大于等于0,且小于容器的大小)。保证下标有效是程序员的责任,下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误,而且编译器并不检查这种错误。
c[n] : 返回c中下标为n的元素的引用,n是一个无符号整数。若n>=c.size(),则函数行为未定义。
二、如果希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常。
c.at(n): 返回下标为n的元素的引用。如果下标越界,则抛出一个out_of_range异常。

14、迭代器 遍历

【c++STL——第十一讲】iterator系列 (常用知识点总结)—lyshark 粉丝

C ++迭代器用于对数据结构中的元素进行顺序访问或随机访问。因此,对于根据定义不允许顺序或随机访问的数据结构,迭代器没有任何意义。这就是堆栈和队列没有迭代器的原因。

15、fork()遇到for循环

题目:

for (int i = 0; i < 2; i++)
{
    fork();
    printf("-n");
}

会打印出多少个"-"?

fork

一次调用,两次返回。
返回子进程id的为父进程。
返回0的为子进程。
在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。
思路:
原先:我以为是执行到fork就会复制出一模一样的代码,再执行到各自的fork再复制一直循环。
实际:其实确实也是这样,但是第一次fork生成子进程B后,现在有两个一模一样的进程了,且都运行到了fork语句后面,开始打印printf了。此时打印了两次-。然后两个进程都i++,开始第二次循环。两个进程运行到fork又各自生成了进程,且都运行到了fork语句后面,开始打印printf。
此时要注意看,print语句中是否有换行符n。
若没有n,子进程就会把父进程缓存区里的-,一起复制进去。也就是第二次fork的子进程会将父进程缓冲区打印的-,一起复制了。导致第二次循环后,除了两个父进程和两个子进程打印的-之外,还有复制的两个缓冲区的-。也就是一共2(第一次fork的父与子)+2(第二次fork的两个父)+2(第二次fork的两个子)+2(第二次fork的两个子复制的两个父的缓冲区)=8
若有n,就会把数据刷出缓冲区。也就没有那两个缓冲区的-了。一共打印6个-。
参考:for循环中的fork()—Lee_01

16、父类子类继承

题目:
有如下程序段:

#include "stdio.h"
 
class A
{
public:
    int _a;
    A()
    {
        _a = 1;
    }
    void print()
    {
        printf("%d", _a);
    }
};
class B: public A
{
public:
    int _a;
    B()
    {
        _a = 2;
    }
};
int main()
{
    B b;
    b.print();
    printf("%d", b._a);
}

请问程序输出:

22
11
21
12

解析:
因为在继承的时候,允许子类存在与父类同名的成员变量,但是并不覆盖父类的成员变量,他们同时存在。 因为给孩子类中没有定义print函数,所以会按照就近原则去寻找父类中是否有print函数。恰好父类中有这个函数,于是调用父类的print函数b.print(),而这个函数会调用父类的a变量。
正确答案: D 你的答案: A (错误)

17、合法的浮点型数据

题目:
在下列选项中,全部都合法的浮点型数据的选项为()
正确答案: B 你的答案: B (正确)
1e3.5 15. 2e4
12.34 1e+5 0.1E12
0.2e2 12345. e5
5.0e(1+4) 0.1 8e+2
知识点
合法的浮点数有两种表示形式:
十进制小数形式。由数字和小数点组成,必须有小数点。例如(123.)(123.0)(.123)。比如 1.和.1也是合法的表达。
指数形式。如123e3。字母e(或E)之前必须有数字,e后面的指数必须为整数。
规范化的指数形式里面,小数点前面有且只有一位非零的数字。如1.2345e8
解析
A。1e3.。e后面指数不能是小数,必须是整数。
C:e5 。e之前必须有数字
D:5.0e(1+4)。 e后面只能是整型常量,不能是表达式。

18、*p++ 运算符优先级

题目:
*p++ 自增 p 还是 p 所指向的变量?

自增 p
自增 p 所指向的变量
和机器相关

运算符优先级表


解析:
++优先级比*高,因此先进性指针指向对象的变化,再取取指针指向的内容。

正确答案: A 你的答案: B (错误)
后缀++ 和-- 操作符本质上比前缀一目操作的优先级高, 因此*p++ 和*(p++) 等价, 它自增p 并返回p 自增之前所指向的值。
要自增p 指向的值, 使用(*p)++, 如果副作用的顺序无关紧要也可以使用++*p。

19、if、else匹配

题目:
C++语言的if语句嵌套时,if与else的配对关系是 每个else总是与它上面的最近的if配对。说法是否正确?

正确答案: B 你的答案: A (错误)
正确
错误
解析:
else与它前面最近 且没有匹配的在同一复合语句中的if匹配。

if(a>b)
{
    if(b>c)
    cout<<"b>c"<=a"< 
20、gcc与g++ 

题目:
以下说法正确的是()

gcc编译.c文件,__cplusplus没有定义,编译器按照c编译代码
gcc编译.cpp文件,__cplusplus有定义,编译器按照c++编译代码
g++编译.c文件, __cplusplus没有定义,编译器按照c编译代码
g++编译.cpp文件,__cplusplus有定义,编译器按照c++编译代码
解析:
gcc和g++都是GNU组织的编译器。 在编译**.cpp**,两者都是将其视为C++来搞; 但是,两者区别地方在于,编译.c文件时,gcc将其视为C程序,而**g++将其视为C++**程序。
正确答案: A B D 你的答案: 空 (错误)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1022663.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号