1. 大小和长度竟然不是一个意思
sizeof()
和strlen()
有什么异同之处?他们对于不同参数的结果有什么不同?请试举例子说明。
int main(void) {
char s[] = "I love Linux\0\0\0";
int a = sizeof(s);
int b = strlen(s);
printf("%d %d\n", a, b);
}
- 程序输出为
16 12
- sizeof会统计字符串结尾的\0;而strlen不会,它只统计\0之前的字符串个数,遇到\0停止
- 因此sizeof的结果比strlen的多能看见的三个\0,以及在字符串末尾自动加上的再一个\0,即一共多四个\0。So,16和12相差4
举个栗子ヾ(•ω•`)o
int main(){
char arr1[]="abc";
char arr2[]={'a','b','c'};
printf("%d\n"sizeof(arr1));
printf("%d\n"sizeof(arr2));
printf("%d\n"strlen(arr1));
printf("%d\n"strlen(arr2));
}
- 根据以上的说明,可知该程序的输出为
4 3 3 26(随机值)
(由于arr2没有结尾的\0,所以计算完‘c’后,继续往后计算,直到遇见随机的\0) - 嗯哼,没看够?点这里
注意:
- sizeof是运算符,计算对应类型所占的空间数,不是函数;而strlen是在#include<string.h>头文件下的一个计算字符串长度的函数,且只能(char*)字符串作为参数
2. 箱子的大小和装入物品的顺序有关
test1
和test2
都含有:1个short
、1个int
、1个double
,那么sizeof(t1)
和sizeof(t2)
是否相等呢?这是为什么呢?
struct test1 {
int a;
short b;
double c;
};
struct test2 {
short b;
int a;
double c;
};
int main(void) {
struct test1 t1;
struct test2 t2;
printf("sizeof (t1) : %d\n", sizeof(t1));
printf("sizeof(t2): %d\n", sizeof(t2));
}
- 相等,均为
16
- 了解一下结构体的内部成员存放和结构体的大小:
1.结构体内部的成员不是连续紧挨着存放的
2.结构体可以分成N份,每份的大小是最大成员类型的长度
3.结构体的大小为N * 最大成员类型的长度
再看这题:
- 举该题t1的例子,由于double是占了8个字节,为结构体成员中的最大长度,因此
每份大小为8
; - 第一份中,int占了4个字节,还剩8-4=4个字节,
∵4>2(shout)
,∴
接下来的short可以继续放在第一份中,此时第一份里只剩下8-4-2=2个字节,∵2<8(double)
,∴
double无法再放入第一份,得重开新的第二份; - 第二份刚好放下double;
- 在这个结构体中
一共用了两份
,所以结构体大小为8(每份大小)× 2(份数)=16
3. 哦,又是函数
想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个
func
函数,用来输出二维数组arr
中每个元素的值。
/*在这里补全func函数的定义*/
int main(void) {
int arr[10][13];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 13; j++) {
arr[i][j] = rand();
}
}
func(arr);
}
- 上代码
void fun( int a[][13] ){
int i ,j;
for( i = 0 ; i < 10 ; i++ ){
for( j = 0 ; j < 13 ; j++ ){
printf( "%d" , a[i][j] );
}
}
}
- over
4.就不能换个变量名吗?
- 请结合下面的程序,简要谈谈
传值
和传址
的区别。- 简要谈谈你对C语言中变量的生命周期的认识。
int ver = 123;
void func1(int ver) {
ver++;
printf("ver = %d\n", ver);
}
void func2(int *pr) {
*pr = 1234;
printf("*pr = %d\n", *pr);
pr = 5678;
printf("ver = %d\n", ver);
}
int main() {
int a = 0;
int ver = 1025;
for (int a = 3; a < 4; a++) {
static int a = 5;
printf("a = %d\n", a);
a = ver;
func1(ver);
int ver = 7;
printf("ver = %d\n", ver);
func2(&ver);
}
printf("a = %d\tver = %d\n", a, ver);
}
-
看结果:
-
传值:把A的数值传到B,改变B,A不会跟着变,B存的是跟A一样的值
-
传址:把A的地址传到B,改变B,A同时跟着变,B存的是A的地址
-
生命周期:将一对大括号称为“块”
1.本地变量定义在块内,且不会被默认初始化(因此第一个的输出结果为随机值)
2.程序运行到“块”之前,其中的变量不存在;离开“块”,其中的变量消失了
3.“块”外面定义的变量其一下到处有效
4.“块”里面定义了和外面同名的变量,则会掩盖外面的 -
据以上信息,便可得其结果
5. 套娃真好玩!
请说明下面的程序是如何完成求和的?
unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }
-
结果为
5050
=100+99+…+1 -
本题了解条件运算符的执行方法就能解决
6. 算不对的算术
void func(void) {
short a = -2;
unsigned int b = 1;
b += a;
int c = -1;
unsigned short d = c * 256;
c <<= 4;
int e = 2;
e = ~e | 6;
d = (d & 0xff) + 0x2022;
printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);
printf("c=Ox%hhx\t\n", (signed char)c);
}
又双叒叕是位运算
注:图片来自位运算全面总结,关于位运算看这篇就够了
%hx
代表以16进制输出short类型的整数,因此a,b显而易见,不解释- 关于d,d为无符号的short,c*256=-256,所以此时d=216+(-256);接着d和0xff进行与运算,结果为0,再加上0x2022,所以d最终=
0x2022
- 关于e,先进行 ~(取反),取反结果和6进行或运算,结果为
0xffffffff
- 关于c,将c左移4位后,并强制类型转换成signed char,得
0xfff0
7. 指针和数组的恩怨情仇
int main(void) {
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int(*b)[3] = a;
++b;
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}
- 结果:
10 4 9
int main(void) {
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int(*b)[3] = a;//b和*结合,构成一个名为b指针,int修饰数组的内容(数组的每个元素),因此b指针指向该数组的首地址,
//而这个数组有3个单元,每个单元都是int类型,且该数组没有名字>>指向含有3个int数组的指针
//将a赋值给他,使b指针指向a数组第一个单元{1,2,3}中的首元素1
++b;//指针b指向a数组的第二个单元{4,5,6}中的首元素4
b[1][1] = 10;//经过++b后,b[0][0]为4,所以这里的b[1][1]为8,并将其赋值为10(即a[2][1]=10)
int *ptr = (int *)(&a + 1);//(&a+1)是指针ptr指向a数组的下一个数组(在a数组最后一个元素的后面)
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}
- a[2][1]据分析可得为10
- **(a + 1)可理解为 * (*(a+1)+0)=a[1][0]=4
- *(ptr - 1))指整个数组的下一个数组减去一个元素地址,得到上一个数组a的最后一个元素9
8. 移形换位之术
下面有
a
、b
、c
三个变量和4个相似的函数。
- 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
- 请找出下面的代码中的错误。
const int
和int const
是否有区别?如果有区别,请谈谈他们的区别。const int *
和int const *
是否有区别?如果有区别,请谈谈他们的区别。
int a = 1;
int const b = 2;
const int c = 3;
void funco(int n) {
n += 1;
n = a;
}
void func1(int *n) {
*n += 1;
n = &a;
}
void func2(const int *n) {
*n += 1;
n = &a;
}
void func3(int *const n) {
*n += 1;
n = &a;
}
void func4(const int *const n) {
*n += 1;
n = &a;
}
void func2(const int *n) {//const修饰指针*n,表示*n指向的值不能被改变
*n += 1;//因此这里是错误的
n = &a;
}
void func3(int *const n) {//const修饰地址,表示指针的地址不能被改变
*n += 1;
n = &a;//因此这里是错误的
}
void func4(const int *const n) {//const修饰指针和地址,两者均不能改变
*n += 1;//因此这里是错误的
n = &a;//因此这里是错误的
}
- const int和 int const都表示int类型的变量不能被修改
const int *a
、int const *a
:(*和a连在一起)表示指针a指向一个const int,即 *a的值为const,不能被修改- 而对于
int *const a
:(* 和a分开来了)表示地址固定的指针a不能被修改地址,但能修改*a的值 - 更多见:here here
9. 听说翻转字母大小写不影响英文的阅读?
请编写
convert
函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。
char *convert(const char *s);
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
- 上代码
char *convert(const char *s)
{
int len=strlen(s);
char *a=(char*)malloc(len);
strcpy(a,s);
char *q=a;
for(;*a!='\0';a++){
if(*a>='a'&&*a <= 'z')
*a -= 32;
else if(*a >= 'A'&&*a <= 'Z')
*a += 32;
}
return q;
}
10. 交换礼物的方式
- 请判断下面的三种
Swap
的正误,分别分析他们的优缺点。- 你知道这里的
do {...} while(0)
的作用吗?- 你还有其他的方式实现
Swap
功能吗?
#define Swap1(a, b, t)
do {
t = a;
a = b;
b = t;
} while (0)
#define Swap2(a, b)
do {
int t = a;
a = b;
b = t;
} while (0)
void Swap3(int a, int b) {
int t = a;
a = b;
b = t;
}
- swap1、2正确,swap3错误
swap1:
直接从原函数中传递参数,方便快捷swap2:
在参数中创建交换变量t,作用时间短,且占用内存空间小swap3:
只传递了a,b变量的值而并没有传递地址,所以仅在函数内部改变了a,b的值而函数作用完后就释放掉函数中变量的内存,a,b的值还是没有改变- do {…} while(0)的作用:实现局部作用域(为了在宏定义中使用多个语句块而不会受大括号和分号的影响)
- 用指针啦
11. 据说有个东西叫参数
你知道
argc
和argv
的含义吗?请解释下面的程序。你能在不使用argc
的前提下,完成对argv
的遍历吗?
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i]);
}
- argc:外部输入的参数个数
- argv:参数的字符串数组,用来存放指向字符串参数的指针数组
- (argc=1时,表示只有一个程序名称,存储在argv[0]中;argv[0]指向程序运行的全部路径名;argv[1]指向程序在DOS命令中执行程序名的第一个字符串,后面以此类推)
- 见代码
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);
for ( int i = 0; argv[i]!='\0'; i++)
printf("%s\n", argv[i]);
}
一样的结果:
12. 人去楼空
这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。
int *func1(void) {
static int n = 0;
n = 1;
return &n;
}
int *func2(void) {
int *p = (int *)malloc(sizeof(int));
*p = 3;
return p;
}
int *func3(void) {
int n = 4;
return &n;
}
int main(void) {
*func1() = 4;
*func2() = 5;
*func3() = 6;
}
- 解答见下
int *func3(void) {
int n = 4;
return &n;//不可返回局部变量的地址(&n),作用完就被释放了
}
- static int 与 int 的区别:
static int
不管在函数内还是函数外,都作为一个全局变量可以保存它被修改以后的值。而int
则没有这一功能,只有作为全局变量时能保存修改。 - c语言中static关键字用法详解
13. 奇怪的输出
int main(void) {
int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}
- 这里涉及到一个知识点:大端法,小端法
- 在了解这个之前,先了解一个专业词:字节序。字节序,顾名思义就是字节的排列顺序,而计算机中既可以从高位到低位进行排列,也能从低位到高位进行排列
- 现在再来看大端法、小端法:假设有数据0x12345678,左边为高字节,右边为低字节。将高字节的数据放在低地址,便是大端法;反之,为小端法
- 下图,上面一排为字节的存放地址,从左到右,地址由低到高;下面一排为字节,分别存放在不同的地址中
大端法
小端法
- 一般家庭计算机所使用的为小端法,因此根据以上知识并查阅阿斯克码表可得,该程序的输出为
Welcome to xiyou Linux group 2021
14. 请谈谈对从「C语言文件到可执行文件」的过程的理解
全题终。。。。。。
点个赞再走呗ヾ(•ω•`)o