文章目录
一.sizeof和strlen的区别
这已经算是一个老生常谈的问题,算是面试题的经典题目,相信初学C语言的小伙伴们,都对sizeof和strlen做过一定的总结和区分。
1.sizeof语句
sizeof()是个运算符,其作用是返回一个对象或者类型在内存中所占用的字节数。它的参数可以是数组,指针等;
主要功能是计算数组或指针最大能够容纳的字符数。
值得注意的是:1.sizeof语句所计算出来的值是在编译过程中已经计算出来的,而不是运行时计算的,这一点和strlen函数有很大的区别。
2.sizeof不能返回动态地被分配的数组的大小(毕竟人家的值在编译过程中已经计算出来了呗)。
2.strlen函数
首先strlen()是一个C库中的函数,它的参数必须是一个字符型的指针,返回值是在运行时计算出来的,其次,它计算的是一个对象或者类型的实际有效的内存长度。
strlen()函数遇到字符串末尾的‘\0’时,会停止计算,并且不会将‘\0’计算在字符串长度内。
3.练习
我们来看一下其中一年的面试题:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc[],char *argv[]){
int a[3][2] = {2,0,1,8};
char *str = (char*)malloc(sizeof(char)*20);
strcpy(str,"\0101\\xb2");
printf("%zu\n",sizeof(a));
printf("%zu %d\n",sizeof(a[1][1] = 0),a[1][1]);
printf("%zu %zu\n",sizeof(str),strlen(str));
return 0;
}
%zu 输出无符号整型,当无法使用时,可以用 %z
首先的第一个小考点是对字符串中字符数目的判断:
str指向的字符串可以这样分开来看:\010 , 1 , \ , \x , b , 2 显然,一共有六个字符。
明白这个后我们来看,输出结果:
24
4 8
8 6
第一行的输出不出所料,是24,计算的是a数组内存的长度。
第二行的第一个输出,由于a数组是一个int类型的数组,所以输出为4。
这里有个小问题,a[1][1]的值是否被重新赋值?
前面已经提到过了,sizeof()是一个运算符,所以在编译阶段已经计算出了sizeof的结果,而赋值是在运行过程中进行的,所以并不会执行 a[1][1] = 0 这个语句。
第三行是一个小重点,对str的内存计算:
由于str是一个指针变量,所以sizeof()语句计算的是 str 这个指针变量的长度,即返回计算机系统的地址字节数,故其结果为8字节(博主为64位机,如果是32位机,则返回4)。
而strlen()函数计算的则是str指向字符串的有效长度。
二.结构体存储
我们先看这个题目:
#include<stdio.h>
struct icd{
int a;
char b;
double c;
};
struct cdi {
char a;
double b;
int c;
};
int main(int argc,char *argv[]){
printf("%zu %zu\n",sizeof(struct icd),sizeof(struct cdi));
return 0;
}
输出结果:
16 24
1.同样在结构体中定义了同样的三个类型的变量为什么所占内存大小不同呢?
在 经过一番刻苦研究研究 求助度娘后得到了如此分配内存的原因:
结构体的大小不是结构体元素单纯相加就行的,因为我们主流的计算机使用的都是32bit字长的CPU,对这类型的CPU取4个字节的数要比取一个字节要高效,也更方便。所以在结构体中每个成员的首地址都是4的整数倍的话,取数据元素时就会相对更高效,这就是内存对齐的由来。每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
简而言之就是:
通过将内存“对齐”这一方式让变量的地址拥有一定的特性,使CPU对内存的访问效率得到了提高。
2.对齐规则(砰砰砰,敲黑板,划重点)
1.起始地址为该变量类型所占内存的整数倍,若不足则不足部分用数据填充至所占内存的整数倍。
2.该结构体所占总内存为结构体成员变量中最大数据类型的整数倍。
下面我们来看一下上面两个结构体的存储方式:
struct icd{
int a;
char b;
double c;
};
我们可以看到上面char类型的c虽然只有一个字节长度,但是为了满足地址对齐,后面三位地址为空。
struct cdi {
char a;
double b;
int c;
};
我们可以看到上面char类型的a虽然只有一个字节长度,但是为了满足地址对齐,后面7位地址为空,而int类型的c原本只有四个字节,但是这样算下来,长度为20,不满足规则2中的总内存为结构体成员变量中最大数据类型(此中为double)的整数倍,所以后面又补了4位。
三.面试题中指针和数组的部分相关操作
1.定义指针时 const 关键字的用法
const double PI = 3.1415926
我们虽然可以用#define指令创建类似功能的符号常量,但是 const 的用法更加灵活,可以创建 const 数组,const 指针和 指向const 的指针等。
(1)指向 const 的指针
double int days[3] = {1,2,3};
const double *p = days; //p指向数组首元素
如此定义,这里的指针p无法改变指向地址的值,实例:
*p = 3; //不允许
p[0] = 3; //不允许
days[0] = 3; //允许,因为days数组没有const 限定
p++; //允许,指向dats[1]
通过上面例子可以看出,p不能改变自己指向地址所存储的量,但是这并不影响它指向其他地址。
顺带一提:只能把非const的值赋给普通指针,否则语句无效。
(2)const指针
注意和上一题定义的区别:
double int days[3] = {1,2,3};
double const *p = days; //此指针只能指向days数组首地址这一地址,无法指向其他地址
p = &days[1] //不允许
*p = 10 //允许,可以改变指向的值
const int *const q = days; //该指针不能改变所指向的地址,也不能改变地址上存储的值
2.指针和多维数组
我们可以先看这个程序,思考一下输出会是怎样的:
#include<stdio.h>
int main(){
int a[4][2] = {{2,4},{6,8},{1,3},{5,7}};
printf(" a = %p, a + 1 = %p\n",a,a+1);
printf(" a[0] = %p, a[0] + 1 = %p\n",a[0],a[0]+1);
printf(" *a = %p, * a+1 = %p\n",*a,*a+1);
printf(" a[0][0] = %d\n",a[0][0]);
printf(" *a[0] = %d\n",*a[0]);
printf(" **a = %d\n",**a);
printf(" a[2][1] = %d\n",a[2][1]);
printf("*(*(a+2)+1) = %d\n",*(*(a+2)+1));
return 0;
}
在此程序中,数组名 a 是数组元素的首地址,在本例中, a 的首元素是一个内含两个int值的数组,即 {2,4} . 接下来,我们分析一下:
1.首先,a是数组首元素的地址,所以a的值和 &a[0]的值是相同的,而 a[0] 本身表示的是一个内含两个int值的数组({2,4}),所以 a[0] 的值和 &a[0][0] 是相同的。
2.给指针或者地址加1,其值会增加对应类型大小的值,在这里 a 和 a[0] 不同,因为 a 指向的是一个含有两个 int 值的数组,所以a+1指向a[1],同时地址加8个字节,而a[0]指向的是一个 int 值,所以呢,a[0]+1就是a[0][1],地址加4个字节。
3.解引用,简而言之就是:
*( *(a+x)+y) 所指向的值就是 a[x][y] 。
以上内容清楚后,看懂输出就比较简单了
输出:
a = 0x7fff19a496f0, a + 1 = 0x7fff19a496f8
a[0] = 0x7fff19a496f0, a[0] + 1 = 0x7fff19a496f4
*a = 0x7fff19a496f0, * a+1 = 0x7fff19a496f4
a[0][0] = 2
*a[0] = 2
**a = 2
a[2][1] = 3
*(*(a+2)+1) = 3