面试结束复盘查漏补缺
每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。
以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~
重要的事说三遍,关注+关注+关注!
更多笔记分享
addData(3.0,2); //正确–3.0会强制转换为int类型
addData(“321”);//错误–传递的类型不正确
addData(2,3,1);//错误–传递的参数数量不对
**
每一个形参之间必须用逗号隔开,并注明类型
可以不定义形式参数
形式参数可以是任何基本类型或者本身定义的类型,可以是指针,引用,值
**
int fun();//正确–隐式的定义无形式参数
int fun(void);//正确–显示的定义无形式参数
int fun(int a,b);//错误–每一个形式参数前都要有一个变量类型
int fun(int a int b);//错误–不同的形式参数之间没有用逗号隔开
int fun(int a,int b);//正确–值传递
int fun(int *a,int *b);//正确–指针传递
int fun(int &a,int &b);//正确–引用传递
**
直接调用
直接调用即在代码中调用函数,比如说addData()
嵌套调用
嵌套调用指的是,举一个例子,在a函数中会调用b函数,而在b函数中又调用了a函数
**
C++函数的传递个人感觉是一个十分骚气的操作,他的值传递不像Java这么简单,如果不了解c++的传值类型,可能有一些方法的调用得到的结果往往不是我们所想的那样
比如设计一个函数
**
//cout<<a<<endl; ==>> 1
//cout<<&a<<endl;==>> 008FF9BC
//& 为取地址值符号
#include
using namespace std;
int main()
{
int a = 1;
int b = 2;
cout << "交换前 a = " << &a << "\tb = " << &b << endl;
cout << “打印出交换函数内部的a,b地址” << endl;
void swap(int, int);
swap(a, b);
cout << "交换后 a = " << &a << "\tb = " << &b << endl;
return 0;
}
void swap(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
cout << "交换函数swap内部 a = " << &x << "\tb = " << &y << endl;
}
我们可以发现单独的交换函数的参数地址其实并不会改变,交换前交换后的地址都是一样的,唯一不一样的是交换时的地址
你写了一个函数,能够交换两个变量的值,于是在main函数中测试这个函数,却发现输出的结果往往不是我们想象的那样,发现值并没有交换,于是焦头烂额,我这思路没错啊…,到底错在哪里呢,其实是因为c++函数的传值的差异,如果读者有兴趣的话,不妨听我来讲一讲这其中的原因!
多次运行的你可以发现没错的地址值都是随机的,并不是唯一的
int main(){
int a,b;
cin>>a>>b;
cout<<“a的值为:”<<a<<endl;
cout<<“b的值为:”<<b<<endl;
swaps(a,b);
cout<<“a的值为:”<<a<<endl;
cout<<“b的值为:”<<b<<endl;
return 0;
}
**
**
可以知道,每一个变量的定义都会在内存中对应一个地址,每一个地址是对应着特定的编号的,值即这个对应内存地址里面存储的数据,将值作为形式参数传入数组,比如上面main函数的swab(a,b),你以为swap里面用到的a,b就是main函数里面定义的a,b,但是实际不是这样的,可以对函数做一些修改,打印出他的内存地址
#include
using namespace std;
void swaps(int a,int b){
cout<<“Function Swap address_a is”<<&a<<endl;
cout<<“Function Swap address_b is”<<&b<<endl;
int temp = a;
a = b;
b = temp;
}
int main(){
int a,b;
cin>>a>>b;
cout<<“Address_a”<<&a<<endl;
cout<<“Address_b”<<&b<<endl;
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
swaps(a,b);
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
return 0;
}
可以看到,运行结果根本没有交换a,b的值,因为在swap里面的a和b与在mian函数里面输入的a和b地址都不一样,根本就不是同一个变量!所以这样的交换数据的函数根本不是一个可以成功交换的函数,他其实是重新定义了两个int类型变量,只是值与a和b相等,所以值传递并不能达到修改变量的作用,
可以看到当我们在调用函数时传入参数,但是不想改变(非本愿)传入的变量的值时,我们可以采用值传递的形参定义方式
**
**
相比较于值传递,指针传递又是另外一个方法,指针变量指向的是这个变量存储的内存位置,即指针变量指向内存地址,这个指针变量存储在另一个内存中,通过这个指针,我们可以修改这个指针指向的地址位置的变量数值,对于上面的两数交换的数据,我们就可以用指针传递的形式达到交换两个数据的效果
修改代码如下
#include
using namespace std;
void swaps(int *p,int *q){
cout<<“Function swaps Point p Address:”<<&p<<endl;
cout<<“Function swaps Point q Address:”<<&q<<endl;
int temp = *p;
*p = *q;
*q = temp;
}
int main(){
int a,b;
cin>>a>>b;
int *p=&a,*q=&b;
cout<<“Point p Address:”<<&p<<endl;
cout<<“Point q Address:”<<&q<<endl;
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
swaps(p,q);
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
}
从运行结果可以看到,达到了一个交换的效果,以指针的形式,但是可以看到传入的指针跟mian函数里面定义的指针也是不一样的!其实指针传递可以理解为另类的值传递,传递的指针参数也是一个变量类型,在这种swap函数的修改其实是对指针变量指向的内存地址里面的值进行修改,即main函数里面变量a和b的地址里面的值进行修改,也就可以理解为修改了a和b里面的值,这里可以用一张图表明
**
**
引用传递其实是跟地址传递具有相同的效果,但是,唯一的区别是引用变量一旦定义,里面的指向的地址位置是不能修改的,相当于*const p
代码实现
#include
using namespace std;
void swaps(int &p,int &q){
cout<<“Function swaps a Address:”<<&p<<endl;
cout<<“Function swaps b Address:”<<&q<<endl;
int temp = *p;
*p = *q;
*q = temp;
}
int main(){
int a,b;
cin>>a>>b;
cout<<“a Address:”<<&a<<endl;
cout<<“b Address:”<<&b<<endl;
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
swaps(a,b);
cout<<“a:”<<a<<endl;
cout<<“b:”<<b<<endl;
可以看到引用变量传入的值就是mian函数里面的a和b,地址是一样的
一维数组
数组的传递其实就是指针的传递,当然在C++的STL中有一个可以代替数组的更安全的类型array(这里不考虑),对于c++中的数组类型,其实就是一个指针类型,比如说定义一个数组int a[],这里a就是指向a[0]的指针,要获得a[1],只需要a++即可
//打印数组
void prints1(int a[],int len){
for(int i=0,i<len;i++){
printf(“%d\t”,a[i]);
}
printf(“\n”);
}
void prints2(int *a,int len){
for(int i=0,i<len;i++){
printf(“%d\t”,*a);
a++;
}
printf(“\n”);
}
//这两个方法感觉都是指针传递,因为他们都能修改传入的原数组的值
int main(){
int a[3] = {1,2,3};
prints1(a,3);
prints2(a,3);
return 0;
}
可以看到***a和a[]**其实没有区别
二维数组
二位数组其实也没啥,也就是按照一维数组的思想,但是人为的规定了一些限制,因为二维数组数据在内存中是顺序条状存储的,即从1到n个连续的内存位置,存储的是二位数组,计算机才不会管你的几行几列,它只知道只有顺序存储和链式存储之所以有二位数组,是人为的规定了,比如1到多少个内存位置是第一行…,既然这样,同样可以用一个*p来访问二位数组 ,于此同时也可以用双指针(或者说行指针)来访问二位数组
采用一维指针的方式访问
void PrintDoubleArray(int *p,int row,int lie){
for(int i = 0;i<row;i++){
for(int j = 0;j<lie;j++){
printf(“%d\t”,*p);
p++;
}
printf(“\n”);
}
}
采用二维数组的形式访问
void PrintDoubleArray1(int p[][3],int row,int lie){
for(int i=0;i<row;i++){
for(int j=0;j<lie;j++){
printf(“%d\t”,p[i][j]);
}
printf(“\n”);
}
}
采用行指针的形式访问
void PrintDoubleArray2(int (*p)[3],int row,int lie){
for(int i=0;i<row;i++){
for(int j=0;j<lie;j++){
printf(“%d\t”,*((*p+i)+j));
}
printf(“\n”);
}
}
这三个方法都能够打印出二维数组,但是他们的定义放方式不同
一个是采用以为数组,即把二维数组当作一个长一点的一维数组,用指针往后自增来遍历二维数组中的每一个元素
另一个是采用索引的方法a[i][j]的形式,来访问二位数组中的每一个元素,但是形式参数的第二个[]里面必须有一个常数,即你传入的二维数组的列数必须是跟形式参数一样的,否则无法传递
第三个是以行指针的形式,他的访问方式如下
分类 表示方法 备注
行指针 p+i, *p[i] 下标为第i行的指针
元素指针 *(p+i)+j, p[i]+j 第i行第j列的元素指针
元素 ((p+i)+j), *(p[i]+j) 第i行第j列的元素
测试代码,分别用上面三种传值方法输出二维数组
int main(){
int a[][3]={{1,2,3},{2,3,4},{3,2,5},{1,5,1}};
int *p = &a[0][0];
PrintDoubleArray(p,4, 3);
cout<<endl;
PrintDoubleArray1(a,4,3);
cout<<endl;
PrintDoubleArray2(a,4,3);
return 0;
}
可以看到得到的结果是一样的
**
**
在函数中,除了传递变量之外,还可以为设置的形式参数设置默认值,当一个参数具有默认值时,调用其时可以不需要传入该形式参数
比如一个函数中的某些参数,我们没有传入时,他是他执行默认的方法,比如一个时间显示器,我们需要传入的参数为时区,当我们没有传入时区时,默认的为北京时间,就显示一个北京时间
void showTime(int opt = 1){
switch(opt){
case 1:{
cout<<“BeJing time:”<<“xxxx.xx.xx.xx\n”;
break;
}
case 2:{
cout<<“NewYork time:”<<“xxxx.xx.xx.xx\n”;
break;
}
}
}
然后测试一下
int main(){
int des = 2;
showTime();
showTime(des);
return 0;
}
可以看到,当没有传入参数时,这个方法将调用形式参数opt的默认值1进行处理,否则按照传入参数进行处理
**
**
函数的重载的意思即为相同的函数名实现不同的功能,一个相同的函数名他的参数数量,返回类型又可以不同,用来实现其他的功能
比如我们设计一个比较两个变量的大小的函数,可看到
bool compare(int a,int b){
return a>b;
}
bool compare(double a,double b){
return a>b;
}
bool compare(float a,float b){
return a>b;
}
可以看到,函数名是相同的,但是参数的类型是不同的,他们都实现了一个比较的函数,为了简化上面的代码,我们可以用函数模板来写这个函数,函数模板会在后面介绍,再打个比方,一个物体是有面积的,但是对于不同的物体,传入的参数和求面积的方式是不同的
void area(int r){//求圆的面积
cout<<“The area of Circle is:”<<3.14rr;
}
void area(int a,int b){//求矩形的面积
cout<<“The area of Rectangle is:”<<a*b;
}
可以看到上面的函数定义中,一个area函数能有不同的求面积的方法,参数的数量和类型可以不同,即相同的函数名可以实现很多的方法
**
**
对于上面的compare函数,要实现不同的比较方法,需要重新写很多份代码,这样大大加大了代码的冗余度,为了解决这个问题,其实c++是有一个函数模板编程方法的,即起始我们不知道这个形式参数的具体类型是什么,而是在程序运行的时候将特定的变量代替这个模型,定义的格式为
template //T是一个起初未知的模板类型
bool compare(T a,T b){
return a>b;
}
我们并不知道T的具体类型是什么样子的,但是这样的话可以大大地减少代码的冗余度,这个变量类型T只有在实际的编译运行过程中根据用户的输入变量类型来替代,但是T不能是所有的变量类型,它也是有限制的,是根据代码来确定适配范围的,如果一个类型没有重载>这个运算符,这种类型传入无法比较大小,这是程序运行的过程中是会报错的
**
**
内联函数
在程序的运行中,一次函数的调用远非我们想象的只是执行几行语句那么简单,假设有这么一个函数
string& shortString(const string &st1,const string &st2){
return st1.size()>st2.size()?st2:st1;
}
//这个函数的意思即为返回两个字符串中长度较小的字符串的引用类型
在shortString函数执行的过程中,并不是简单的return st1.size()>st2.size()?st2:st1;在执行这句话之前,还有着一系列的操作,比如将方法入口拷贝到寄存器,并且在返回时恢复,或者需要拷贝实参,返回返回值…,需要经历一系列的操作之后,上面那一句话只是这一系列操作的一部分,那么对于这些十分简单的函数语句描述,可以用内联函数来实现,内联函数能够避免函数调用的额外开销,将函数指定为内联函数,通常是在每个调用点的内联地址展开,而无需前面的这么多步操作
,定义内联函数十分简单,只需要在返回值前面加一个inline即可,上面的方法改为内联为
inline string& shortString(const string &st1,const string &st2){
return st1.size()>st2.size()?st2:st1;
}
cout<<shortString(st1,st2)<<endl;
最后
权威指南-第一本Docker书
引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。
总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。
关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!
ortString(const string &st1,const string &st2){
return st1.size()>st2.size()?st2:st1;
}
cout<<shortString(st1,st2)<<endl;
最后
权威指南-第一本Docker书
引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。
总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。
[外链图片转存中…(img-JDE0ZC73-1715816820170)]
[外链图片转存中…(img-4gTB9Dsj-1715816820170)]
[外链图片转存中…(img-4chElXVy-1715816820171)]
[外链图片转存中…(img-AHIb3tN7-1715816820171)]
关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!