目录
1.关于static
首先我们得先明白一个东西,什么是static ?static是静态变量,存储在静态缓存区中,静态静态,并不是说不能它的改变值,不能改变值的量叫常量。 其拥有的值是可变的 ,只是它的值一直都是最新的。说其静态,是因为它不会随着函数的调用和退出而发生变化。即上次调用函数的时候,如果我们给静态变量赋予某个值的话,下次函数调用时,这个值保持不变。举个例子:
#include <stdio.h>
void print(int n);
main()
{
int n,i;
printf("次数:\n");
scanf("%d",&n);
for(i=0;i<n;i++)
print(n);
}
void print(int n)
{
static a=1;
a++;
printf("%d\n",a);
}
正常情况下我们重复引用print()函数,a初始化为1,应该输出的都是2,但实际上,a的值在第一次调用完函数后就更新了,a=2,依次,所以假设n=5,其输出依次为2,3,4,5,6.
static int 不管在函数内还是函数外,都作为一个全局变量可以保存它被修改以后的值。
而 int 则没有这一功能,只有作为全局变量时能保存修改。放在函数内部时,每次调用都用的是一个新的数。
上2019题:
3.有如下代码段所示的函数 f,当我们执行该函数时,会产生什么样的输出结果?在同一程序中
多次执行该函数,输出结果是否一致?
void f() {
static int a = 0;
int b = 0;
printf("%d, %d\n", ++a, ++b);
}
回到问题就迎刃而解了,每次调用后a的值会更新即保留下来,而b每次调用时又被重新赋为1.
2.)关于printf()返回值
上题目:
4.下面程序段的输出是什么?请解释该现象并说出与之相关尽可能多的知识。
int main(void) {
printf("%d\n", printf("Xiyou Linux Group2%d", printf("")));
}
先说答案吧,因为是2019年,所以答案是Xiyou Linux Group2019.
言归正传,我们首先要搞懂printf()返回的是什么东西。其返回一个int值,表示被打印的字符数,比如说:
#include<stdio.h>
main()
{
int a=123;
printf("%d\n",printf("%d",a));
}
从内层往外展开,首先看到的是printf("%d",a),打印出123,再把printf("%d",a)看成整体,其返回的是打印的字符数,即123的字符数,为3,输出结果为1233,你可能想打个空格方便看:
#include<stdio.h>
main()
{
int a=123;
printf("%d\n",printf("%d ",a));
}
可你会发现其输出变成了123 4,为什么呢?因为在打印里层的的时候你打印的是123 ,多打印了一个空格,printf()的返回值变成了4。
Ok,回到原题,首先打印最里层的print("")注意没有空格,printf没有打印内容,printf()返回0,再到
printf("Xiyou Linux Group2%d"),先打印Xiyou Linux Group20,为什么是0,因为上一次printf()返回的是0,同理,Xiyou Linux Group20返回字符数即19.最后整理输出的为Xiyou Linux Group2019.
3.sizeof 和strlen()
sizeof 和strlne()算是小组比较常考的题型了。他两有啥区别呢?
首先,sizeof 是运算符,而strlen 是一个在<string.h>里的函数。
其次,sizeof 可以用类型做参数,它可以计算int,float,char等等类型,而strlen 只能用 char* 做参数,且必须是以 \0 结尾的。说白了strlen只能用于字符串。
再一,strlen 是一个函数,它用来计算指定字符串 str 的长度,但不包括结束字符(即 ‘'\0’符),而sizeof计算输入的总长度。
啥意思,比如2021的面试题:
sizeof(s)时,其计算所有的字符,并不会在遇到\0时就停止,在题目中,其值为16(不是15!)
而strlen(s)时,strlen在遇到第一个\0时就结束进程,其返回12;
另外sizeof还有个用途:其可以计算相应类型所占的字节数, 比如sizeof(int)=4;
4.数据的溢出
5.执行下面的代码段,会输出什么?请试着解释其原因,并叙述相关知识。
int main(int argc, char *argv[]) {
char ch = 255;
int d = ch + 1;
printf("%d %d", ch, d);
}
结果是-1和0,这里需要注意的是char 的范围【-128,,127】,超出范围就会溢出,上溢,从-128往回,下溢从128往回。
取值范围
数据类型的取值范围有一个公式:-2(n-1)~2(n-1)-1
n为整型的内存占用位数,所以int类型32位那么就是-(231)~231 -1即-2147483648~2147483647数据溢出是怎么回事呢?计算机内部以补码形式进行运算的,所以运算结果为补码,补码转原码的过程为:先减一,然后符号位不变其他位取反。
5.有关运算符
1.)|和||
||:或运算,有真必真,例如:t=3>2||4<1; 3>2为真=1,4<1为假=0, 1||0 =1,t=1;
|:按位或,按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。当参与运算的是负数时,参与两个数均以补码出现。
例如:char x = -2, y = 3;
char t = (++x) | (y++);
x=-1=1001;
y=4=0100;
x|y=1101;
取补码后得1001=-1;
2.)<<和>>
移位运算符。
<<:左移运算符 格式:需要移位的数字<<移位的次数n;
1.无符号:
就是把数值转化为二进制,再进行左移,高位舍去,低位补零。相当于乘上2的N次方。
比如6<<2 就等于6*2的2次方=24;
2有符号:
不确定,因为符号位在左移的过程中丢失了。
<<:右移运算符 格式:需要移位的数字<<移位的次数n;
1.无符号
按二进制形式把所有数字向右移动相应的位数,低位移出(舍弃),高位的空位补0。相当于除以2的n次方
2.有符号
格式:需要移位的数字>>移位的次数n
按二进制形式把所有数字向右移动相应的位数,低位移出(舍弃),正数,高位的空位补0。负数,高位的空位补1.
6.关于const
const 是 constant 的缩写,本意是不变的,不易改变的意思
const修饰普通的变量时,比如 const int a=3; a就变成了一个不可改变的值,但是可以把a的值赋给其他变量。
当const 修饰指针变量时,要注意:
当const 修饰指针变量时,指针的值不可修改,例如char* const p;
当const 修饰指针所指的内容时,指针的所指的内容不可修改,例如 char const *p;
当const 既修饰指针所指的内容又修饰指针时,两者都不可以修改,例如 const char *const p;
21年第八题:
要注意,其实const 会先寻找最靠近它右边的修饰,当右边没东西时,才会看左边。
所以int const 和const int 没有区别,const int *,不能修改的是指针所指向的内容,而int* const ,不能修改的是指针。 const int *const n是两者都不能修改
7.define 宏定义嵌套规律
上题:
#define X a+b
int main(int argc, char *argv[]) {
int a = 1, b = 1;
printf("%d\n", X*X);
}
是不是看起来很简单,x=1+1=2,2*2=4;但实际上答案为3;为什么呢?
这是 C/C++ 里面的宏定义,在编译时,编译器会将其原样展开。
什么是原样展开?
比如说 在上例中define X 1+1; X*X=1+1*1;
先讲一些宏嵌套的展开规则:
- 一般的展开规律像函数的参数一样:先展开参数,再分析函数,即由内向外展开
- 当宏中有#的时候,不展开参数
- 当宏中有##的时候,先展开函数,再分析参数
- ##运算符用于将参数连接到一起,预处理过程把出现在##运算符两侧的参数合并成一个符号,注意不是字符串。
来再做一题:
#define A 2 + 2
#define B 3 + 3
#define C A * B
int main(int argc, char *argv[])
{
printf("%d\n", C);
return 0;
C=(2+2)*(3+3)=2+2*3+3=11;
为了避免上诉问题可以添加上括号,如下:
#define s(x) ((x)*(x))
具体宏定义相关可查看:C++宏定义详解 - Boblim - 博客园 (cnblogs.com)
8.数组,指针,指针数组,数组指针。
1)数组。
其倾向于二维数组(字符串数组是重点),接下来看下20年的第九题:
int main(int argc, char *argv[])
{
int a[2][3] = {{5, 7}, {5, 2}};
int b[2][3] = {5, 7, 5, 2};
int c[2][2] = {{5, 7}, {5, 2}};
int d[2][2] = {5, 7, 5};
printf("%d %d\n", a[1][1], b[1][1]);
printf("%d %d\n", c[1][1], d[1][1]);
return 0;
}
考察的一个知识点就是未定义的数组元素赋初值为0;
2.)指针
就开始有点晕了,其大概有以下几个基础用法:
p=&a;
&a 表示变量a的地址,&a=100
p 指针变量p的值,p=100=&a
*p 表示指针变量p指向的变量,*p=a=8
&*p 相当于&(*p),&(*p)=&a=100
*&a 相当于*(&a),*(&a)=*p=a=8
&p 表示指针变量p的地址,&p=100
*&p 相当于*(&p),*(&p)=100
看个题:9.执行下列程序段,并输入“Xiyou Linux”(不含引号),那么程序的输出结果是什么?
int main(int argc, char *argv[]) {
char *p = (char *)malloc(sizeof(char) * 20),
*q = p;
scanf("%s %s", p, q);
printf("%s %s\n", p, q);
是不是理所当然的以为是Xiyou Linux,可实际上输出的结果是Linux Linux,为什么?
第二行代码为20个char类型的值请求内存空间,并设置p指向该位置.
第三行又使得p和q指向了同一个地址.
p先获取Xiyou,当q再获取Linux时,,由于p和q指向相同的地址,p的值也被改变。
3.) 指针数组和数组指针。
什么是指针数组? 形如 int*a[5],其是一个包含五个int*类型的数组,经常用于二维数组当中,在稍后的21年例题中会讲解。
什么是数组指针呢?形如(int*a)[5],其是一个指向含有5个int的数组的指针。
我们很容易把数组指针和指针数组搞混,那么我们如何能够将其正确分辨呢?
记住 “[]”的优先级比“*”要高。当int*没有括号时,a首先是个数组,其包含5个指针,当int*有括号时,(int*)的优先级高,它首先是个指针,指向数组。
当数组和指针相结合的时候就让人头大了。
看看21年的恩怨情仇~:
(*b)[3]是一个数组指针,【3】是列数,int(*b)[3]=a,使得b指向了a[0][0],
++b; 使得b指向了a[1][0];
这个时候的b[1][1]可不是a[1][1]了,而是在把b[0][0]看成a[1][0],再进行寻找,也就是说b[1][1]此时为a[2][1];
int *ptr=(int*)(&a+1);
这句话是不是很奇怪,什么意思呢?
(&a+1)先取变量a的地址,并根据a的地址获得下一个与a同类型的相邻地址。
可以看图体会一下。
因此在原题中&a+1指向的地址为下一个相邻地址的首地址,也就是a[3][3]后的第一个地址,ptr-1,使得其指向了a[3][3]的最后一个元素的地址,也就是9的地址。
关于指针和数组的具体内容可浏览:指针,C语言指针完全攻略 (biancheng.net)
9.)关于排序
几年来的面试题排序的身影经常出现在上,我们应该掌握一些排序算法。
1.)冒泡排序(经典基础)
普通的冒泡就不再多赘述,让我们看下冒泡的优化——鸡尾酒排序。
鸡尾酒排序(cocktail sort)对冒泡排序进行了优化,使得外层循环一次能找出两个已排序的数(最大和最小),可以理解为”双向“的冒泡排序。
注:因为鸡尾酒排序外层循环一次能找出两个排序数,故其外层循环次数折半,而内层循环则为两个并列的for循环(分别控制正向和反向)。总的来说,鸡尾酒排序大多数情况下要比冒泡排序效率高。
#include <stdio.h>
// can be any swap function. This swap is optimized for numbers.
void swap(int *x, int *y) {
if(x == y)
return;
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
void cocktailsort(int *a, size_t n) {
while(1) {
// packing two similar loops into one
char flag;
size_t start[2] = {1, n - 1},
end[2] = {n, 0},
inc[2] = {1, -1};
for(int it = 0; it < 2; ++it) {
flag = 1;
for(int i = start[it]; i != end[it]; i += inc[it])
if(a[i - 1] > a[i]) {
swap(a + i - 1, a + i);
flag = 0;
}
if(flag)
return;
}
}
}
int main(void) {
int a[] = { 5, -1, 101, -4, 0, 1, 8, 6, 2, 3 };
size_t n = sizeof(a)/sizeof(a[0]);
cocktailsort(a, n);
for (size_t i = 0; i < n; ++i)
printf("%d ", a[i]);
return 0;
}
2.插入排序
你可以理解为打扑克牌时的理顺牌的过程 ,上个图理解
//直接插入排序函数
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。
int j= i-1;
int x = a[i];
while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间
a[j+1] = a[j];
j--;
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i);//打印每次排序后的结果
}
}
、
3.)快速排序。
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
2、快速排序的三个步骤:
(1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 "基准"(pivot)
(2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大
(3)递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。
void insertsort(int*a,int begin,int end)
{
int i,j,t;
for(i=begin;i<=end;i++)
for(j=i+1;j<=end;j++)
if(a[i]>a[j])
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
void quick_sort (int *a,int begin,int end)
{ if(begin>end)
return ;
if(end-begin+1<10)
{
insertsort(a,begin,end);
return ;
}
int i=begin;
int j=end;
int t;
int tmp=a[begin];
while(i!=j)
{
while(a[j]>=tmp&&j>i)
j--;
while(a[i]<=tmp&&j>i)
i++;
if(j>i)
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[begin]=a[i];
a[i]=tmp;
quick_sort(a,begin,i-1);
quick_sort(a,i+1,end);
}
】
这是核心代码,注意到其中有个优化,当n<10时,使用了插入排序,为什么呢,当N的数量小时,快排的速度没有其他的快,但是当N的数值很大时,快排的优势就大大地体现出来了;其他的优化如三数取中法等大家可以自行了解下。
快排可以在快排库中调用(侧面体现了它的效率之高)
怎样调用系统提供的快排库函数:qsort,它包含在<stdlib.h>头文件里,函数一共四个参数,在函数头部加上#include<stdlib.h>,就可以直接调用,并且无需声明。一个典型的qsort的写法如下:
qsort(s,n,sizeof(s[0]),cmp);
其中第一个参数s是参与排序的数组名(或者也可以理解成开始排序的地址,因为可以写成&s[i]这样的表达式,这个问题下面有说明);第二个参数n是参与排序的元素个数; 第三个参数是单个元素的大小,必须sizeof(s[0])这样的表达式,下面也有说明;第四个参数cmp其实是个函数名字,它是为指导qsort如何进行排序而专门写的一个函数,我们称之为比较函数,其目的是为了告诉qsort要以什么样的方式进行排序(降序?升序?或者按照某个关键字进行排序等)(注:写成cmp只是一个名字,可以随便怎么写),cmp这个函数有形参,和返回值(int型),但是在调用时却不需要给它传递实参进去,直接调用其名字即可,这个函数是专门为qsort开发的一种函数形式,这个函数的典型定义是:
intcmp(const void *a,const void*b);(红色字体是其固定模式,必须这样写,黑色的则是自己随便起的名字)(其函数体详见后面),并且规定这个函数只能返回int型值。
最后欢迎大家加入西邮Linux兴趣小组!