牛客网刷题第二天
- 1.数组初始化问题
- 2.二进制的运算
- 3.变量的内存分配【·】
- 4.字符常量
- 5.静态成员&非静态成员
- 6.运算符重载
- 7.二进制表示中1的个数【·】
- 8.等概率输出数字【·】
- 9.进制转换
- 10.对象的创建
- 11.struct、class
- 12.指针数组,数组指针
- 13.结尾~~~~
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 //结果210101001 //result 1
^11111100 //result 2,异或操作,相同为0,不同为1
=01010101 //结果3,D is right.
备注:当时将’^'当成了幂运算,实际上这些运算应该是统一的,如果系统能够确定是二进制运算,就不会再掺进其他类型的运算。
3.变量的内存分配【·】
Q:只有在使用时才为该类型变量分配内存的存储类说明是 auto 和 register。
解释:
- 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 11112. 转换为unsigned char(8位,1字节)发生字节截断,取最后八位为1111 11113. 再转换为int后为0000 0000 0000 0000 0000 0000 1111 11114. 输出为ff [hex为16进制,四个四个的看]
这个题目中我需要知道:
- 原码、反码、补码
- 计算机为什么使用补码存储数据
- C++11的lambda表达式有什么好处
- 正数的原码、反码和补码都相同。
- 负数原码2反码:符号位不变,数值位按位取反。
- 负数原码2补码:符号位不变,数值位按位取反,末位再加1。
相关链接:
1.c++ 补码与整数互相转换
2.计算机为什么使用补码来存储数据
3.计算机中带符号的整数为何采用二进制的补码进行存储?
4.C++原码、反码、补码详解
5.原码反码补码的相互转换
6.C++ 11 Lambda表达式 通俗易懂
与此类似的还有一道题目:
short int i=32769;
printf("%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】