21-0002 牛客网刷题(第二天)

1.数组初始化问题

在这里插入图片描述
需要注意的点:

  • 数组只能够省略第一维
  • char类型的数组,最后有一个’\0’,size是需要+1的
char a[]="hello";
char a[6]="hello";
char a[5]="hello";//error
char a[][5]={"this","is"};
char a[][]={"this","is"};//error

2.二进制的运算

Q:设 char 型变量 x 中的值为10100111,则表达式(2+x)^(~3)的值是多少?

-00000000  //对齐操作
---------
 00000010  //2
+10100111  //原二进制数据
=10101001  //结果1
 
~00000011  //对3进行取反操作
=11111100  //结果2
 
 10101001  //result 1
^11111100  //result 2,异或操作,相同为0,不同为1
=01010101  //结果3,D is right.

备注:当时将’^'当成了幂运算,实际上这些运算应该是统一的,如果系统能够确定是二进制运算,就不会再掺进其他类型的运算。

3.变量的内存分配【·】

Q:只有在使用时才为该类型变量分配内存的存储类说明是 autoregister

解释:

  • auto 根据数据类型分配内存,
  • register 在使用到时分配CPU寄存器地址,
  • static 声明的时候就会分配内存,不初始化系统会自动初始化,
  • extern 外部变量在程序被编译时(程序运行之前)分配存储。

相关链接:
1.C++ - 外部变量(extern)
2.extern和const和#ifndef和内存分配和C++的.h和.cpp
3.extern声明变量详解
4.C++中的static及内存分配

4.字符常量

'\007'//八进制转义字符,表示响铃
'\b'//ASCII码,普通转义字符,退格
'a'//字符a
"\09"//这是两个字符,八进制中最多出现到7

5.静态成员&非静态成员

  • 静态成员存在于内存,非静态成员需要实例化才会分配内存
  • 非静态成员可以直接访问类中静态的成员
  • 静态成员不能访问非静态的成员
  • 非静态成员的生存期决定于该类的生存期,而静态成员生存期则与程序生命期相同

解释:静态成员存在于内存,非静态成员需要实例化才会分配内存,所以静态成员函数不能访问非静态的成员。因为静态成员存在于内存,所以非静态成员函数可以直接访问类中静态的成员。[至少你要访问的东西是在内存中的]

6.运算符重载

下面是C++运算符的优先级顺序,【】内是不能够重载的运算符

作用域 【::】
增量、对象、数组 -> 【.】 ++(后) - -(后) [ ]
增量、内存、取反 ~ ++(前) - -(前) +(正) -(负) *(指针) &(取址) new delete 【sizeof】
类成员指针 ->* 【.*】
算术 * / %
算术 + -
<< >>
关系 < <= >= >
关系 == !=
&
^
|
&& 逻辑
逻辑 ||
条件 【?:】
赋值 = += -= *= /= %= &= ^= |=
逗号 ,
  • .和.*是为保护访问成员功能而不允许被重载;
  • sizeof运算对象是类型而非变量,不具备重载特征。

我觉得对于可以重载的运算符进行重载,需要单独写一篇文档。

7.二进制表示中1的个数【·】

#include <stdio.h>
 
int fun(int i) 
{ 
int cnt = 0; 
while(i) 
{ 
cnt++; 
i = i&(i-1); //循环下来计算的是从右往左数1的个数。
} 
return cnt; 
} 
 
int main()
{ 
printf( "%d\n", fun(2017) );
return 0; 
}

//output:7

解释:2017的二进制是11111100001,有7个1。

8.等概率输出数字【·】

下面的程序可以从0…n-1中随机等概率的输出m个不重复的数。这里我们假设n远大于m,直接看代码吧:

knuth(int n, int m)
{ 
    srand((unsigned int)time(0)); 
    for (int i = 0; i < n; i++) { 
        if (rand()%(n-i)<m) { //原题目中需要填写
            cout << i << endl;
            m--;//原题目中需要填写
        }
     }
}

原理解释:

由这个for循环循环n次,且在满足条件时才输出 i 可知,输出m个不同值的要求已满足,因为每次输出的都是 i 值,而 i 值每次都是不一样的,m-- 保证了程序在输出了m个值后就停止循环。
.
在i=0时,rand()%(n-i)的取值范围为0到n-1,共n个数,此时要输出0只需要rand()%(n-i)小于m,故i=0被输出的概率为m/n;
.
在i=1时,rand()%(n-i)的取值范围为0到n-2,共n-1个数,

若i=0没有被输出,则m–未被执行,此时i=1被输出的概率为m/(n-1),
.
若i=0已经被输出了,则m变为m-1,此时i=1被输出的概率为(m-1)/(n-1);

由概率论的知识,可知此时i=1被输出的概率为
P=(1-m/n)(m/(n-1))+m/n((m-1)/(n-1))=m/n;
以此类推,可知每个数被输出的概率都为m/n.

备注:

  • 这里输出的是 i,m只是用来判断个数的,并不会输出的数字逗比m小,i 是逐渐递增的,随机数是用来判断的。
  • 使用随机数作为判断的条件,来输出随机数,做法比较高明

9.进制转换

源码:

auto fn = [](unsigned char a){
    cout << std::hex << (int)a << endl;
};
fn(-1);
//output:ff

题中形式为C++11标准里引入的lambda表达式,一个lambda表达式表示一个可调用的代码单元,也可将其理解为一个未命名的内联函数

其基本形式如下:

  • [capture list ] ( parameter list ) -> return type { function body }
  • [捕获列表] ( 参数列表 ) -> 返回类型 { 函数体 }
  • capture list (捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空,写为 [] ),空捕获列表即表明此lambda不使用它所在函数中的任何局部变量;
  • " -> " 表明lambda使用了尾置返回类型;
  • 可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

比如:

auto f = [ ] { return 42; }; // 定义了一个可调用对象f,它不接受参数,返回42。
即常见形式如: 
[ ] { 函数体 }
[ ] ( int n ) { 函数体 } 

回到上面的问题:

 1. int型-1的存储方式为补码(32位,4字节),1111 1111 1111 1111 1111 1111 1111 1111
 2. 转换为unsigned char(8位,1字节)发生字节截断,取最后八位为1111 1111
 3. 再转换为int后为0000 0000 0000 0000 0000 0000 1111 1111
 4. 输出为ff [hex为16进制,四个四个的看]

这个题目中我需要知道:

  • 原码、反码、补码
  • 计算机为什么使用补码存储数据
  • C++11的lambda表达式有什么好处
  • 正数的原码、反码和补码都相同。
  • 负数原码2反码:符号位不变,数值位按位取反。
  • 负数原码2补码:符号位不变,数值位按位取反,末位再加1。

相关链接:
1.c++ 补码与整数互相转换
2.计算机为什么使用补码来存储数据
3.计算机中带符号的整数为何采用二进制的补码进行存储?
4.C++原码、反码、补码详解
5.原码反码补码的相互转换
6.C++ 11 Lambda表达式 通俗易懂

与此类似的还有一道题目:

short int i=32769printf("%d\n",i)//output:-32767

解释:

计算机中整数以补码的形式进行存储,第一位是符号位
shout int 只有2字节
but 32769 is 1000 0000 0000 0001,由于最高位是1,表示复数
由上述补码,求原码
1000 0000 0000 0001//补码
1111 1111 1111 1110//按位取反,临时反码
1111 1111 1111 1111//末位加1,原码
=-32767

10.对象的创建

Q1:假定CSomething是一个类,执行下面这些语句之后,内存里创建了 6 个CSomething对象。

CSomething a();//函数声明
CSomething b(2);//构造函数调用,+1
CSomething c[3];//含有三个对象的数组,+3
CSomething &ra = b;//引用
CSomething d=b;//拷贝构造函数,+1
CSomething *pA = c;//单纯是个指针
CSomething *p = new CSomething(4);//有对象生成,+1

Q2:在C++中,为了让某个类只能通过new来创建(即如果直接创建对象,编译器将报错),应该将析构函数设为私有。

解释: 编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。 因此, 将析构函数设为私有,类对象就无法建立在栈(静态)上了,只能在堆上(动态new)分配类对象 。

11.struct、class

不同点:

struct class
默认继承权限 public private
默认数据访问控制 public private
模板参数 不能定义 可以用于定义模板参数

相同点:
可以有数据成员,方法,构造函数等。

相关链接:
1.C++中Struct与Class的区别与比较

12.指针数组,数组指针

这个概念有点像是马原里面的唯物辩证法&辩证唯物论,看谁是定语,谁是主语,相关解释如下:

int *p[n];	[]优先级高,与p先结合成数组,是个指针数组,有n个指针类型的元素
int(*p)[n];	()优先级高,与*先结合成指针,是个数组指针,指向一个整形一维数组

同样的,还有指针常量,常量指针:

int * const p;//指针常量,重点是个常量,这个指针是常量,指针指向的位置不能改变,也就是说不能乱指,可以乱碰指向区域的东西
const int * p;//常量指针,重点是个指针,指向常量的指针,指向的数据区内容不能变,也就是说可以乱指,但别乱碰指向区域的东西
int const * p;//同上,一个道理,const在*的前面

相关链接:
1.数组指针和指针数组的区别
2.指针常量和常量指针

13.结尾~~~~

总结一下,这些题目是C++比较基础的知识,但是由于学习C++时记住了,但后来太多的依靠各种搜索引擎,以至于只是掌握的不牢固,该背的内容还是要背的,毕竟写代码的时候不能总靠搜索,靠API,虽说是在使用C++进行开发,但是重点在使用,而不是查!!!!
【2020年3月19日01:36:39】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章