C语言
一、.h头文件的作用
? .h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。
? 1)h文件作用:
? 1.方便开发:包含一些文件需要的共同的常量,结构,类型定义,函数,变量的声明;
? 2.使函数的作用域从函数声明的位置开始,而不是函数定义的位置(实践总结);
3 .提供接口:对一个软件包来说可以提供一个给外界的接口(例如: stdio.h)。
? 2)h文件里应该有什么:常量,结构,类型定义,函数,变量申明。
? 3)h文件不应该有什么:变量定义, 函数定义。
? 4)extern问题: 1.对于变量需要extern;
2.对于函数不需要因为函数的缺省状态是extern的.如果一个函数要改变为只在文件内可见,加static。
? 5)include包含问题:虽然申明和类型定义可以重复,不过推荐使用条件编译。
#ifndef _FILENAME_H,
#define _FILENAME_H
……
#endif
1、.h头文件中的ifndef/define/endif 的作用?
? 防止该头文件被重复引用。
2、#include<file.h> 与 #include "file.h"的区别?
? 前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
二、不能做switch()参数的类型
? C/C++中: ? 支持byte,char,short,int,long,bool整数类型和枚举类型。 ? 不支持float,double,string。
? (不支持小数和字符串)
三、结构体与联合的区别
1、结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2、对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
四、const常量与宏定义的区别
(1) 编译器处理方式不同
? define宏是在预处理阶段展开。
? const常量是编译运行阶段使用。
(2) 类型和安全检查不同
? define宏没有类型,不做任何类型检查,仅仅是展开,只进行字符替换。
? const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。
(4)const 可以节省空间,避免不必要的内存分配。
? const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。
(5) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
五、函数返回值
int* fun(void)
{
static int str[]={1,2,3,4,5};
int*q=str;
return q;
}
虽然str是在动态变量区(栈),而该动态变量是局部的,函数结束时不保留的。不能正确返回。
char * fun(void)
{
char *str="hello";
return str;
}
字符串"hello"不是变量,而是一个常量,编译程序在处理这种常量时,通常把它放在了常量区中。而常量区则是始终存在的。可以正确返回。动态变量str不存在了,但常量区中的字符串"hello"还存在。主程序根据返回的地址就可以找到该字符串。
int fun(int a,int b)
{
int max;
if(a>b)
max=a;
else
max=b;
return max;
}
返回的不是变量max的地址,返回的是它的值。主程序是从“返回值寄存器”得到这个5的(此时max变量已经不存在了)。可以正确返回。
char* fun(void)
{
char str[]={'a','b','c','d','e','f','\0'};
char str[]="hello";
printf("%x\n",str);
return str;
}
char str[]=“hello”; 是在动态变量区中开辟了可以容纳6个字符的数组,数组名叫str。同时将字符串"hello"(原存放于常数空间)拷贝到这个数组空间中去作为数组的初始化值。不能正确返回。
char * fun(void)
{
char *str=(char*)malloc(sizeof(char)*6);
str = "hello";
return str;
}
可以正确返回。
六、函数重入
1、可重入函数
? 可重入函数:可以被中断的函数,即这个函数执行时,可以中断其执行,可以由一个或多个任务并发使用,而不必担心数据错误。
? 在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。
? 写出可重入的函数,在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断。
2、不可重入函数
满足下列条件的函数多数是不可重入的: (1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。标准io函数中有全局变量。
不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是不能运行在多任务环境下的,除非能保证互斥(使用信号量/代码的关键部分禁用中断)。
3、把一个不可重入函数变成可重入的方法
第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。
第二,在和硬件发生交互的时候,切记执行关闭硬件中断的操作。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。//这是临界区保护
第三,不能调用任何不可重入的函数。
第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。
七、分别写出bool、int、float、指针类型变量a与“零”的比较语句
bool: if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
? if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
八、可变参数宏
九、函数指针
? 如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
int(*p)(int, int);
? 需要注意的是,指向函数的指针变量没有 ++ 和 – 运算。
1、函数指针的使用
int Func(int x);
int (*p) (int x);
p = Func;
赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。
# include <stdio.h>
int Max(int, int);
int main(void)
{
int(*p)(int, int);
int a, b, c;
p = Max;
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b);
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y)
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
2、函数指针的作用
(1)函数指针作为参数传递给另一个函数;
? 回调函数:将一个函数指针作为参数传递给其它函数。后者将“回调”用户函数。
? 实例:实现一个与类型无关的查找函数(单链表)
? 思考:单链表的元素类型可能是字符串或者数组,数组和字符串无法作为参数传递给函数,但是指向他们的指针却可以。
要求:需要查找函数作用于任何类型的值。
? 解决办法:把参数类型声明为 void * , 表示“一个指向未知类型的指针”。
回调函数如下:
int compare(void const *a, void const *b){
if ( *(int *)a == *(int *)b )return 0;
else return 1;
}
单链表查找函数如下:
Node *search_list(Node *node, void const *value, int(*compare)(void const *, void const *)){
while (node != NULL){
if ( compare( &node->value, value) == 0) break;
node = node->next;
}
return node;
}
(2)作为转换表;
包括两步:
step1:声明状态转移函数、声明初始化函数指针数组;
? 声明 状态转移函数
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double xiebianchang(double, double);
? 声明并初始化一个函数指针数组
double(*pfunc[])(double, double) = { add, sub, mul, div, xiebianchang };
step2:调用函数指针数组
double result= pfunc[i](a,b);
测试代码:
#include<iostream>
#include<cmath>
using namespace std;
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double xiebianchang(double, double);
double(*pfunc[])(double, double) = { add, sub, mul, div, xiebianchang };
double add(double a, double b){ return a + b;}
double sub(double a, double b){ return a - b; }
double mul(double a, double b){ return a*b; }
double div(double a, double b){ return a / b; }
double xiebianchang(double a, double b){ return sqrt(pow(a, 2) + pow(b, 2)); }
void test(){
int n = sizeof(pfunc) / sizeof(pfunc[0]);
for (int i = 0; i < n; ++i){
cout << pfunc[i](3, 4) << endl;
}
}
int main(){
test();
return 0;
}
十、C语言结构体怎么定义节省内存?
(1)在保证值域足够的情况下,用小字节变量代替大字节变量,如用short替代int,float替代double
(2)将各成员按其所占字节数从小到大声明,以尽量减少中间的填补空间(字节对齐)。
(3)可以取消字节对齐,#pragma pack(1) ,当然这会牺牲效率,谨慎采用。
十一、struct和class的区别
1.struct 是值类型,class 是对象类型
2.struct 不能被继承,class 可以被继承
3.struct 默认的访问权限是public,而class 默认的访问权限是private.
4.struct总是有默认的构造函数,即使是重载默认构造函数仍然会保留。这是因为Struct的构造函数是由编译器自动生成的,但是如果重载构造函数,必需对struct中的变量全部初始化。并且Struct的用途是那些描述轻量级的对象,例如Line,Point等,并且效率比较高。class在没有重载构造函数时有默认的无参数构造函数,但是一被重载些默认构造函数将被覆盖。
5.struct的new和class的new是不同的。struct的new就是执行一下构造函数创建一个新实例再对所有的字段进行Copy。而class则是在堆上分配一块内存然后再执行构造函数,struct的内存并不是在new的时候分配的,而是在定义的时候分配
十二、指针vs引用
a.不存在空引用。引用必须连接到一块合法的内存。
b.一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
c.引用必须在创建时被初始化。指针可以在任何时间被初始化。
指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。
十三、强制类型转换
? 强制类型转换是把变量从一种类型转换为另一种数据类型。如果您想存储一个 long 类型的值到一个简单的int中,您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型。
? 如果一个运算符两边的运算数类型不同,先要将其转换为相同的类型,即较低类型转换为较高类型,然后再参加运算。
? 常用的算术转换是隐式地把值强制转换为相同的类型。编译器首先执行整数提升,如果操作数类型不同,则它们会被转换为下列层次中出现的最高层次的类型:long double,double,float,unsigned long long,long long,unsigned long,long,unsigned int,int,char.(高到低)
下面代码的输出是:
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
? 当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。
? 类似的:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void)
{
int a = 10;
printf ("sizeof ((a > 5) ? 4 : 8.0) = %d\n", sizeof ((a > 5) ? 4 : 8.0));
return 0;
}
? 输出结果: ? sizeof ((a > 5) ? 4 : 8.0) = 8
? 除了有符号类型和无符号类型混合使用时 自动转换为无符号类型,较小的类型和较大的类型混合使用 会被转换成较大的类型,防止数据丢失。
十四、评价以下代码
1、处理器位数相关
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
? 对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下: unsigned int compzero = ~0;
2、#define 与 typedef
#define dPS struct s * ;
typedef struct s * tPS;
? 以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
? typedef更好。首先你要了解 typedef 和 define 的区别, 宏定义只是简单的字符串代换,是在预处理完成的 ,而 typedef是在编译时处理的 ,它不是作简单的代换,而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。
? 上面两种情况,从形式上看这两者相似,但在实际使用中却不相同。
? *dPS p1,p2; ** 在宏代换后变成: struct s p1, p2; 定义p1为一个指向结构的指针,p2为一个实际的结构。
? tPS p3,p4; 而typedef代换后,正确地定义了p3 和p4 两个指针。
? 总结,typedef和#define的不同之处:
? 1、与#define不同,typedef 给出的符号名称仅限于对类型,而不是对值。
? 2、typedef 的解释由编译器,而不是是处理器执行。
? 3、虽然它的范围有限,但在其受限范围内,typedef 比 #define 更灵活。
3、优先级问题
#include <stdio.h>
int main (void)
{
int a = 5,b = 7,c;
c = a+++b;
printf ("c = %d\n", c);
return 0;
}
? 输出结果:c = 12
? 单目运算符++的优先级大于双目运算符+;
4、printf的输出方向问题
#include <stdio.h>
int main (void)
{
int a = 5;
printf ("%d\t%d", a,a++);
return 0;
}
输出结果为:6 5
所以,printf()函数的指令执行是从右向左。
|