1、 Const:
(1)const修饰的是一个只读变量
(2)节省空间,避免不必要的内存分配,提高效率
编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使
得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
例如:
#define M 3 //宏常量
const int N=5; //此时并未将N放入内存中
......
int i=N; //此时为N分配内存,以后不再分配!
int I=M; //预处理期间进行宏替换,分配内存
int j=N; //没有内存分配
int J=M; //再进行宏替换,又一次分配内存!
const定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define
一样给出的是立即数,所以,const定义的只读变量在程序运行过程中只有一份拷贝(因为
它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝。
#define宏是在预处理阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。
#define宏没有类型,而const修饰的只读变量具有特定的类型。
(3)修饰一般变量,数组,指针,函数参数,函数返回值
2、 Static
(1) 修饰全局变量,称为静态全局变量。由于全局变量本身存储在静态区,因此本身就是静态的,对全局变量使用静态是告诉编译器这个变量只能在本文件中被使用,不能被extern
(2) 修饰局部变量,称为静态局部变量。存储在静态区,即使函数下次调用也不改变其值。
(3) 修饰函数。表示这个函数的作用域仅限于本文件。
3、 如果一个函数没有显式地声明返回值,那返回值就是Int型的
在c语言中,如果一个函数没有显式地说明参数是void,那么是可以使用参数的,如下所示:
#include<stdio.h>
void test(){
printf("ok\n");
}
int main(){
//test(3);
return0;
}
在c++中不可以
4、 按照ANSI(AmericanNational Standards Institute)标准,不能对void指针进行算法操作,
即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指
向数据类型大小的。也就是说必须知道内存目的地址的确切值。
例如:
int *pint;
pint++; //ANSI:正确
但是大名鼎鼎的GNU(GNU's Not Unix的递归缩写)则不这么认定,它指定void *的算法
操作与char *一致。因此下列语句在GNU编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
在实际的程序设计中,为符合ANSI标准,并提高程序的可移植性,我们可以这样编写
实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法
的支持。但是我们在真实设计时,还是应该尽可能地符合ANSI标准。
【规则1-36】如果函数的参数可以是任意类型指针,那么应声明其参数为void *。
典型的如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src,size_t len);
void * memset ( void * buffer, int c,size_t num );
这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作
函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy
和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy和memset
明显不是一个“纯粹的,脱离低级趣味的”函数!
下面的代码执行正确:
例子:memset接受任意类型指针
int IntArray_a[100];
memset (IntArray_a, 0, 100*sizeof(int) );//将IntArray_a清0
例子:memcpy接受任意类型指针
int destIntArray_a[100],srcintarray_a[100];
//将srcintarray_a拷贝给destIntArray_a
memcpy (destIntArray_a, srcintarray_a,100*sizeof(int) );
有趣的是,memcpy和memset函数返回的也是void *类型,标准库函数的编写者都不是一
般人。
5、 void不能代表一个真实的变量。
因为定义变量时必须分配内存空间,定义void类型变量,编译器到底分配多大的内存呢。
下面代码都企图让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void体现了一种抽象
6、一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automaticvariables)
3). 多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:
1 2 3 4 |
intsquare(volatileint*ptr) { return*ptr**ptr; } |
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
3). 这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
1 2 3 4 5 6 7 |
intsquare(volatileint*ptr) { inta,b; a=*ptr; b=*ptr; returna*b; } |
由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:
1 2 3 4 5 6 |
longsquare(volatileint*ptr) { inta; a=*ptr; returna*a; } |
7、 大小端问题值得注意:跟处理器有关,可以使用程序判定。
8、 enum
在编译阶段确定其值
9、 const修饰的只读变量不能用来作为定义数组的维数,
也不能放在case关键字后面。
1、 Strlen和sizeof的区别
Strlen是一个函数,sizeof是一个运算符。
1、 sizeof(...)是运算符,在头文件中typedef为unsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。
它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。
由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
数组——编译时分配的数组空间大小;
指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
类型——该类型所占的空间大小;
对象——对象的实际占用空间大小;
函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
2、 strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。
它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL。
3、 实际例子:
char arr[10] = "What?";
int len_one = strlen(arr);
int len_two = sizeof(arr);
cout << len_one << " and " <<len_two << endl;
返回值为5 and 10,因为strlen计算的是字符串已经用掉的长度,因此应该为5,而sizeof返回的是获得保证能容纳实现所建立的最大对象的字节大小,这就表示的是这个数组的长度为10。
char*t1[20];
char (*t2)[20];
printf("%d%d\n0",sizeof(t1),sizeof(t2));
返回值为80和4。*t1[20]是一个指针数组,本质上是一个数组,他表示一个长为20的数组,数组的每一位是一个指针,因此sizeof(t1)相当于在求一个数组的长度,而这个数组每一位所占的空间是一个指针的大小为4,因此总大小为80;(*t2)[20]是一个数组指针,本质上是一个指针,每个指针下面有20个空间大小,因此sizeof(t2)相当于t2[0]是一个指针,因此所占的大小为4。
2、 如何计算结构体的大小
首先需要明确,在c中,空结构体
structpoint{};
所占的大小为0,在c++中所占的大小为1。
structpoint{
int num;
char k;
int c;
};
structpoint p;
printf("%d\n",sizeof(p));
返回结果为12。这里我们先要明确一点:如何计算结构体的大小。运算符sizeof可以计算出给定类型的大小,对于32位系统来说,
sizeof(char) = 1; sizeof(int) = 4。
基本数据类型的大小很好计算,我们来看一下如何计算构造数据类型的大小。
C语言中的构造数据类型有三种:数组、结构体和共用体。
数组是相同类型的元素的集合,只要会计算单个元素的大小,整个数组所占空间等于基础元素大小乘上元素的个数。
结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题。看下面这样的一个结构体:
structstu1
{
int i;
char c;
int j;
};
先介绍一个相关的概念——偏移量。偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。因此,第一个成员i的偏移量为0。第二个成员c的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为4;第三个成员j的偏移量是第二个成员的偏移量加上第二个成员的大小(4+1),其值为5。
实际上,由于存储变量时地址对齐的要求,编译器在编译程序时会遵循两条原则:
一、结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
二、结构体大小必须是所有成员大小的整数倍。
对照第一条,上面的例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上3个空字节,使得第三个成员的偏移量变成8。
对照第二条,结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为12,满足要求。
再看一个满足第一条,不满足第二条的情况
struct stu2
{
int k;
short t;
};
成员k的偏移量为0;成员t的偏移量为4,都不需要调整。但计算出来的大小为6,显然不是成员k大小的整数倍。因此,编译器会在成员t后面补上2个字节,使得结构体的大小变成8从而满足第二个要求。
3、 算术运算符 > 关系运算符 > 赋值运算符
因此:
X > y+2 也意味着x>(y+2)
X=y>2 也意味着 x=(y>2)
4、 在c语言中所有的输入实际上是一个输入流,可以用getchar来接收。
5、 内存分配方式:
内存分配方式有三种:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(在函数中不要返回栈内存,但可以返回动态分配的内存)。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。