经验:
1、一个函数只实现相应的一个单功能
2、用extern声明外部变量
(1)在同一个文件内,若定义在文件中间,要在文件前面使用,可以在前面用extern声明
(2)在相关联的不同的文件内,若文件A定义了变量a,文件B要使用变量a,若都定义变量a会出现重复定义错误,在文件B中只要加上extern a(不用加类型如int,但建议加上,否则可能出现不可预期的错误,实际操作中的经验),就可以使用(如果不想要某变量被另一个文件外部引用,可以加上static以防止外部引用)
3、关于变量的声明和定义
一般为了叙述方便,把建立存储空间的声明称为定义,而把不需要建立存储空间的声明称为声明。
4、typedef的用法
typedef int INTEGER 这样在程序中可以用INTEGER代替int来进行定义,如INTERGER a;
typedef struct
{
int month;
int day;
int year;
}DATE; 这样可以用DATE定义变量:DATE birthday; DATE *p;它代表一个结构体类型
typedef int NUM[100]; 这样直接用NUM n; 就可以定义整型数组变量n
typedef int ARR[10]; 这样用ARR a,b,c就等同于int a[10],b[10],c[10];
typedef 与 #define的区别:(如typedef int COUNT与 #define COUNT int)
#define 在预编译时处理,它只能做简单的字符串替换,而typedef是编译时处理的,并非简单替换。typedef有利于程序的通用和移植
typedef int (*pcb)(int , int) ;
pcb p1,p2;
以上等价于: int (*p1)(int,int);
int (*p2)(int,int);
5、数组的用法
char a[10]; 定义一个数组
char *p; 定义了一个指针
对指针p进行附初值时可以: p = &a[0]; 或 p = a; 也可以在定义时赋 char *p = a(或者&a[0]);
p+i 和 a+i 就是a[i]的地址值,也就是说p+1或a+1是指向数组中的下一个元素
*(p+i) 和*(a+i)是p+i 和 a+i指向的数组元素,即a[i]
指向数组的指针变量也可以带下标, 如p[i]与*(p+i)等价
引用一个数组元素有以下方法: a[i] ; *(a+i) ; *(p+i)
对一个数组赋值,可以a[10] = {'a','b','c','d','e','f'};
a[10] = {"hello!"}; 长度为7,末尾自动加上'/0'结束符
a[10] = "hello!";
关于释放指针
int *point = new int(10);
int *pointArray = new int[4];
释放是, delete point;
delete [ ] pointArray; //如果delete pointArray 会造成内存泄漏
6、sizeof
(1)用于数据类型 : sizeof(type); 如 sizeof(int) 结果为4,表示4个字节
(2)用于变量: sizeof(var_name);或者sizeof var_name; 带括号的用法更普遍
(注意:sizeof操作符不能用于函数类型,不完全类型或位字段)
(3)sizeof 的操作结果:sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型
I.若操作数具有类型char、unsigned char或signed char,其结果等于1
II.int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double 类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。
III.当操作数具有数组类型时,其结果是数组的总字节数
IIII.联合类型操作数的sizeof是其最大字节成员的字节数
IIIII.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小
(4)主要用途
I.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信
II.sizeof的另一个的主要用途是计算数组中元素的个数
7、对于位移动"<<" ">>" 左右移动都是补0
如0x01<<1 得到0x02 0x80右移一位得到0x40;
移位只针对C标准中的char和int类型,因此不能对float,double等进行位操作
8、选择输出类型
如果一个数a=10;分别输出它的十进制、八进制、十六进制为:
cout<<dec<<a<<' ' //输出十进制数
<<oct<<a<<' ' //输出八进制数
<<hex<<a<<endl; //输出十六进制数
9、(一) 无名的函数形参
声明函数时可以包含一个或多个用不到的形式参数。这种情况多出现在用一个通用的函数指针调用多个函数的场合,其中有些函数不需要函数指针声明中的所有参数。看下面的例子:
int fun(int x,int y)
{
return x*2;
}
尽管这样的用法是正确的,但大多数C和C++的编译器都会给出一个警告,说参数y在程序中没有被用到。为了避免这样的警告,C++允许声明一个无名形参,以告诉编译器存在该参数,且调用者需要为其传递一个实际参数,但是函数不会用到这个参数。下面给出使用了无名参数的C++函数代码:
int fun(int x,int) //注意不同点
{
return x*2;
}
(二) 函数的默认参数
C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型:
#include "iostream.h"
void show(int=1,float=2.3,long=6);
int main()
{
show();
show(2);
show(4,5.6);
show(8,12.34,50L);
return 0;
}
void show(int first,float second,long third)
{
cout<<"first="<<first
<<"second="<<second
<<"third="<<third<<endl;
}
10、static的用法
(1)表示退出一个块后依然存在的局部变量/* 可以解决频繁调用某函数时某个初始变量不变的问题 */
(2)表示不能被其他文件访问的全局变量和函数
(3)属于类且不属于类对象的变量和函数
11、变量作用域
C++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C语言中,必须要在函数开头部分。而且C++允许重复定义变量,C语言也是做不到这一点的。看下面的程序:
#include "iostream.h"
int a;
int main()
{
cin>>a;
for(int i=1;i<=10;i++) //C语言中,不允许在这里定义变量
{
static int a=0; //C语言中,同一函数块,不允许有同名变量
a+=i;
cout<<::a<<" "<<a<<endl;
}
return 0;
}
12、输出一些预定义的内容
cerr<<__FILE__ //输出正在被编译的文件名字,包含路径
<<__LINE__ //记录文件已经编译的行数
<<__DATE__
<<__TIME__
13、assert()
assert()是c语言标准库中提供的一个通用预处理器宏。在代码中长利用assert()来判断一个必需的前提条件,以便程序能够正确执行。需包含头文件#include<assert.h> assert.h是C库头文件的C名字,C++库头文件的C++名字总是以字母C开头,后面是去掉后缀.h的C名字
使用断言来发现软件问题,提高代码可测性。
说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。
示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)
#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试
void exam_assert( char * file_name, unsigned int line_no )
{
printf( "/n[EXAM]Assert failed: %s, line %u/n",
file_name, line_no );
abort( );
}
#define EXAM_ASSERT( condition )
if (condition) // 若条件成立,则无动作
NULL;
else // 否则报告
exam_assert( __FILE__, __LINE__ )
#else // 若不使用断言测试
#define EXAM_ASSERT(condition) NULL
#endif /* end of ASSERT */
14、include 预处理器指示符preprocessor include directive
#include<>表示这个文件是一个工程或标准头文件,查找过程会检查预定义的目录。可通过设置搜索路径环境变量或命令行选项来修改这些目录。 #include" " 表明该文件是用户提供的头文件,查找该文件时将从当前文件目录开始
15、exit()
标准库函数exit()终止程序的执行。当函数中出现exit()时,该函数会立即结束全部程序,强制返回操作系统。
exit()的作用类似于跳出整个程序。
exit()的一般形式为void exit(int return_code);
其中返回值return_code将送回调用过程,一般是操作系统。按照管理,0值一般表示正常结束,非0值表示某种错误。
该函数包含在<stdlib.h>头文件中
16、变量的存储类别(从变量值的存在时间角度来分)
(1)静态存储变量: 编译时分配存储空间的变量
定义形式 变量定义前面加static,如static int a = 8;
(2)动态存储变量: 程序运行期间分配固定的存储空间
可以是函数的形参、局部变量、函数调用时的现场保护和返回地址。这些变量函数调用时分配存储空间,函数结束时释放存储空间。
定义形式 变量定义前面加auto ,如auto int a,b,c 可省略不写。
17、 printf格式字符
格式字符 说明
d,i 以带符号的十进制形式输出整数(整数不输出符号)
o 以八进制无符号形式输出整数(不输出前导符o)
x,X 以十六进制无符号形式输出整数(不输出前导符0x),用x输出十六进制数的a~f时以小写形式输出,用X时则以大 写字母输出
u 以无符号十进制形式输出整数
c 以字符形式输出,只输出一格字符
s 输出字符串
f 以小数形式输出单、双精度数,隐含输出6位小数。
e,E 以指数形式输出实数
g,G 选用%f,或%e格式中输出宽度较短的一种格式,不输出无意义的0.
附加格式
字母l 用于长整型整型,可加在格式符d、o、x、u前面
m(代表一个正整数) 数据最小宽度
n(代表一个正整数) 对实数,表示输出n位小数;对字符串,表示截取的字符个数
- 输出的数字或字符在域内向左靠
在c语言中 ,printf("%-8d",a);即可在8个宽度内以左靠的形式显示字符。
printf("%-5.2f",a);可以在以左靠5个宽度内输出两位精度的浮点数
在c++中 ,cout<<a<<setw(8)<<b<<endl;空8个宽度再输出,需要包含头文件<iomanip>
18、大小写转换函数
toupper函数,原型int toupper(int ch),作用是在ch为字母时,返回等价的大写字母;否则返回的ch值不变。toupper即to-upper的以此函数和toupper类似,只是返回等价小写字母。
两个函数包含在<ctype.h>中
19、枚举类型enum
enum weekday{sun,mon,tue,wed,thu,fri,sta}workday,week_end;
对枚举变量赋值不能超过它的范围。weekday中的值依次是0,1,2,3,4,5,6
20、结构体struct和联合体union
struct Student
{
int num;
char name[];
int age;
int score[3];
}stu[3];
也可以使用typedef
typedef struct
{
int num;
char name[];
int age;
int score[3];
}Student;
定义时直接可以使用 Student stu[3];
对于联合体
union Description
{
int a;
char b;
double c;
}des;
可以进行赋值,des.a = 1;或des.b = 'a';或des.c = 30.11;
也可以
typedef union
{
int a;
char b;
double c;
}Description;
可以定义变量 Description des;
des.a = 1;或des.b = 'a';或des.c = 30.11;
该联合体中3个定义共占一个内存,长度为占用长度最长的那个变量长度。这里是double。
每次只能使用3个中的一个,如des.a =1;然后可以输出值,如果des.a = 1;des.b='a';printf("%d,%s",des.a,des.b);只会产生一个值。
21、引用
对一个数据的可以使用“引用”(reference),这是C++对C的一个重要扩充。C++之所以增加引用类型,主要是把它作为函数参数,以扩充函数传递数据的功能。
引用是一种新的变量类型,它的作用是为一个变量起一格别名。(就自己的理解来说,引用操作的是变量的地址,而非变量本身的值)
声明方法:int a;
int &b = a;
说明:(1)a或b作用相同,都代表同一变量,声明变量b为引用类型并不需要重新开辟地址
(2)&是引用声明符,并不代表地址
(3)在声明一个引用类型变量时,必须同时使之初始化,即声明它代表哪一个变量 。
(4)在声明变量b是变量a的引用后,在它们所在的函数执行期间,该引用类型变量b始终与其代表的变量a联系,不能再做其他变量的引用。如 int a1,a2;
int &b = a1;
int &b = a2;//企图使b又变成a2的引用是不行的
引用作为函数参数。
函数参数传递的两种情况。
(1)将变量名作为实参和形参
(2)传递变量的指针。
int main()
{
void swap(int ,int);
int i = 3,j = 5;
swap(i,j);
...
}
void swap(int a,int b)//将变量名作为实参和形参
{
int temp;
temp = a;
a = b;
b = temp;
}
运行时输出还是3 5,没有进行交换。将变量作为实参和形参,传给形参的是变量的值,传递是单向的。如果在执行函数期间形参的值发生变化,并不传回给实参。因为在调用函数时,形参和实参不是同一存储单元。
int main()
{
void swap(int *,int *);
int i = 3,i = 5;
swap(&i,&j);
cout<<i<<" "<<j<<endl;
return 0;
}
void swap(int *p1,int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
调用函数时把变量i和j的地址传给形参p1和p2(它们是指针变量),因此*p1和i为同一内存单元,*p2和j为同一内存单元。
这种虚实传递仍然是“值传递”方式,只是实参的值是变量的地址而已。
int main()
{
void swap(int &,int &);
int i=3,j=5;
swap(i,j);
....
}
void swap(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
在swap函数的形参列表中声明a和b是整型变量的引用。注意,此处&a不是a的地址,而是指“a是一个引用型变量”,&是引用型变量的声明符。此时并没有对它们初始化,即没有指定它们是哪个变量的别名,它们也不占存储单元。当main调用函数时,由实参将变量名传给形参,i的名字传给引用变量a,这样a就成了i的别名。
其实虚实结合时是把实参i的地址传到形参a,是形参a的地址取实参i的地址,让a和i共享同一单元。这就是地址传递方式。
与使用指针变量作形参时有什么不同?
1、使用引用类型不必在swap函数中申明形参是指针变量。指针变量要另外开辟内存单元,其内容是地址。‘
2、在main函数中调用swap时,实参不必用变量地址。系统向形参传送的是实参的地址而不是实参的值。
22、数值型和字符型数据的字节数和数值范围(依据谭浩强C++)
类型标志符 字节 数值范围
int 4 32位 (c中是16位)
unsigned int 4 32
short 2 16位
long 4 32位 -2147483648~+2147483647
char 1 8位 -128~127
unsigned char 1 8 0~255
float 4 32 3.4*10e(-38)~3.4*10e(38)
double 8 64
long double 8 64(c中为80位)
23、指针的一些用法和注意
定义指针 int *p; //这里的*是指针声明符号
int a = 5;
p = &a;
printf("%d",*p); //这里的*是指针运算符,它表示“指向”,*p指p所指向的内容
区别 变量的指针 和 指向变量的指针变量;前者是指变量的地址,后者是指针变量,用来指向另一个变量。比如上面p就是指针变量,而&a是a的指针,即p的内容。
关于形参实参的值传递,单向传递的问题:
int main()
{
int x=5,y=9;
int *point1,*point2;
point1 = &x,point2 = &y;
swap(...);
printf("%d,%d",a,b);
}
写一个swap()函数来实现两个数交换。
(1) void swap(int *p1,int *p2)
(2) void swap(int a, int b)
void swap(int *p1,int *p2)
{
int tmp;
tmp = *p1;
*p1 = *p2; //指针变量所指向的变量值发生改变,在函数调用结束后依然保留
*p2 = tmp;
}
void swap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
对于当调用函数void swap(int *p1,int *p2)来实现时,输出为9,5,即实现了两个数的交换;而调用void swap(int a, int b)时,输出仍然为5,9;
这里需要注意的问题是: 什么是函数调用时,实参变量的值传给形参变量。这里即调用函数swap(point1,point2)时,实参变量point1、point2的值传给了形参变量p1,p2。
“值传递”是单向的。这句话是说实参将值传给形参以后,形参无论进行什么操作,都无法将其值再回送给实参,即形参无法再改变实参的值。swap(int a, int b)就是这种情况。为了使在函数中改变了的变量值能被main函数所使用,不能采取上述把要改变值的变量作为参数传给形参的办法,而应该用指针变量作为函数参数。在函数执行过程中,指针变量所指向的变量值发生该变,函数调用结束后,变量值依然保存。
24、volatile
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
25、类模板 template<class 类型参数名>
为了解决类里面的类型参数问题。与typedef int DataType这种感觉差不多。就是为了使同一作用的类对不同数据类型都有效。
具体使用方法:
template<class numtype>
class Compare
{
private :
numtype x,y;
public:
Compare(numtype a,numtype b)
{x = a; y = b;}
numtype max()
{return (x>y)?x:y;}
}
main()
{
Compare<int> cmp1(3,7); //注意这里再Compare后加<数据类型>
cout<<cmp1.max();
Compare<float> cmp2(3.1,7.5);
cout<<cmp2.max();
}
注意,类模板的类型参数还可以有多个,每个类型前面都必须加class
template<class t1,class t2>
class someclass
{...};
在定义时分别代入实际类型名,如 someclass<int,float> obj;
26、文件操作 FILE
文件操作主要涉及到几个函数:fopen,fwrite,fread,fclose。
1、要创建文件指针:FILE *fp;
2、可以利用fopen先创建一个没有的文件 fp=fopen("new.txt","w"),也可以利用它打开一个已有文件,但文件内容会被清空,可查询fopen中各打开类型的用法。
3、利用fwrite函数写数据。size_t fwrite( void* buffer, //要写的数据的指针,比如要写int a=10;就给&a
size_t size, //写的数据类型长度,可用sizeof(int)这种来实现
size_t count, //要写的数据个数,如果是一个a,就1,如果要写一个数组,就给 //数组长度
FILE* stream //给文件指针fp
);
4、利用fread读数据 size_t fread(
void* buffer, //要读的数据的指针,比如要写int a=10;就给&a
size_t size, //读的数据类型长度,可用sizeof(int)这种来实现
size_t count, //要读的数据个数,如果是一个a,就1,如果要写一个数组,就给 //数组长度
FILE* stream //给文件指针fp
);
27、sizeof 和 strlen
sizeof{variable|type}, strlen(const char[]);
sizeof可以返回变量类型如int char float等的长度,并且单位是字节,如int 32位的话就返回4;还可返回变量的长度,如int a=10;sizeof(a) 是等于4的(这里是int为32位长);
strlen用于返回字符数组的实际长度,如char str[20] = "China"; strlen(str) 的值是5,而sizeof(str)为20,要特别注意。
列举几个例子好理解
char a;
int b;
char arr[50] = "Prison Break";
int arr2[50] = "Prison Break"; //这种定义方法是通不过的
sizeof(a) 为 1
sizeof(b) 为 4
sizeof(arr) 为 50
strlen(arr) 为 12
strlen(arr2) //编译不过,strlen的参数要求是 char[]
补充:对于一个struct来说,比如struct people
{
int age; //这里把int当作32位
char sex;
long number;
}
struct people me;
在8位机上,也就是一些单片机上,sizeof(me)为4+1+4=9Byte,如果题目给出为32位平台下,则sizeof(me) = 4+4+4=12Byte。
理解32位存储机制 先存32位的age,再存8位的sex,当再存32位的number时,一个单位的32位只剩下24位,不够,所以只能到下一个单位存number,因此sex也相当于占了32位,因此sizeof的结果是12。
自然对界:
struct 是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float 等)的变量,也可以是
一些复合数据类型(如array、struct、union 等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,
以提高运算效率。缺省情况下,编译器为结构体的每个成员按其自然对界(natural alignment)条件分配空间。各
个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size 最大的成员对齐。
例如:
struct naturalalign
{
char a;
short b;
char c;
};
在上述结构体中,size 最大的是short,其长度为2 字节,因而结构体中的char 成员a、c 都以2 为单位对齐,
sizeof(naturalalign)的结果等于6;
指定对界:
一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack (n),编译器将按照n 个字节对齐;
使用伪指令#pragma pack (),取消自定义字节对齐方式。
注意:如果#pragma pack (n)中指定的n 大于结构体中最大成员的size,则其不起作用,结构体
仍然按照size 最大的成员进行对界。
例如:
#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()
当n 为4、8、16 时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n 为2时,其发挥了作用,使得sizeof (naturalalign)的结果为6。
28、大端小端
嵌入式系统人员应该对Big-endian和Little-endian非常清楚。小端模式是指CPU对操作数的存放方式是从低字节到高字节,而大端模式是指CPU对操作数的存放是从低字节到高字节。
例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为
内存地址 0x4000 0x4001
存放内容 0x34 0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 0x4000 0x4001
存放内容 0x12 0x34
编程实现方式:若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
int checkCPU( )
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return(c.b ==1);
}
}
联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写
29、运算符重载 (参阅C++ P317)
在C++中,比如复数的相加,在类中声明一个函数 Complex operator+(Complex &c2);
并实现它:
Complex Complex::operator+(Comlex &c2)
{
return Complex(real+c2.real,imag+c2.imag);
}
在主函数中
int main()
{
Complex c1(1,2),c2(2,3),c3;
c3 = c1 + c2; //即可完成相加的结果
}
一般来说,应该令赋值操作符返回一个reference to *this. 即返回应该是一个引用。
在实现 = 操作符的时候应该注意自我赋值 问题。
class Bitmap{...};
class Widget{
....
private:
Bitmap *pb;
};
Widget& Widget::operator=(const Widget& rhs)
{
delete pb; //如果是自我赋值, 这里不只是销毁当前对象的bitmap, 也是销毁rhs的bigmap,那么将导致错误
pb = new Bitmap(*rhs.pb);
return *this;
}
自我赋值解决方案:
1. 证同测试 (identity test)
Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
具备自我赋值安全性,但是不具备异常安全性(因为无论什么情况导致new Bitmap出现错误, pb的指向都会出错)。
2. 异常安全性测试
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
通常保证了异常安全性的都能够保证自我赋值安全性。
这里即便new 操作抛出异常,pb还是指向原位置, 他并不高效,但是行得通。
3. Copy and swap
class Widget{
...
void swap(Widget &rhs); //交换*this 和rhs的数据
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
这种方法好比是值传递方式,直接进行拷贝然后copy,确保不会出现new问题。缺点是为了伶俐巧妙的修补而牺牲了清晰性。
30、头文件重复包含
为了避免头文件重复包含,需要在头文件开头加 #ifndef _HEARDER_
#define _HEARDER_
...
#endif
第一次编译发现没有定义_HEADER_宏,于是定义并编译。若另有文件包含了此头文件,会发现已定义了_DERDER_,于是跳到#endif执行。
31、memset和memcpy
(1) void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
如:char *p = "My space is TommySpace";
利用memset(p, 'M', 5);可以将 *p改变成 "MMMMMace is TommySpace"
通常它用作内存初始化
char str[100];
memset(str,0,100); //将开辟的内存全部初始化为0值
一般用在对定义的字符串进行初始化为‘ ’或‘/0’;例:char a[100];memset(a, '/0', sizeof(a));
还可以方便的清空一个结构类型的变量或数组。
struct sample_struct
{
char csName[16];
int iSeq;
int iType;
};
对于变量
struct sample_strcut stTest; 一般的清空方式为:
stTest.csName[0] = '/0'
stTest.iSeq = 0;
stTest.iType = 0;
利用memset可以一次清空: memset(&stTest, 0, sizeof(stTest));
如果是数组:struct sample_struct Test[10];
利用memset一次清空: memset(Test, 0, sizeof(10*(struct sample_struct));
(2) extern void *memcpy(void *dest, void *src, unsigned int count);
功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
32、时钟周期、机器周期、指令周期
时钟周期:也称震荡周期,是时钟脉冲的倒数。比如单片机外接晶振为12M,那么时钟周期为1/12000秒,是计算机中最基本、最小的时间单位。 在一个时钟周期内,CPU仅完成一个最基本的动作。一个时钟周期定义为一个节拍(用P表示)
机器周期 在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成
指令周期 指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。
32、容器类 Vector
参考资料 1
参考资料 2
创建、拷贝、销毁操作:
vector<Elem> c
vector<Elem> c1(c2)
vector<Elem> c(n)
c.~vector<Elem>()
vector<Elem> c(n,elem)
vector<Elem> c(beg,end)
非修改操作(包括大小操作和比较操作):
c.size()
c.empty()
c.max_size()
capacity()
reserve()
c1 == c2
c1 != c2
c1 < c2
c1 > c2
c1 <= c2
c1 >= c2
reserve(n) 增加容量至n,提供的n不小于当前大小。
赋值操作:
c1 = c2
c.assign(n,value) 把n个值为value的元素赋给c
c.assign(beg,end) 把范围[beg,end)内的元素赋给c
c1.swap(c2)
swap(c1,c2)
元素访问操作:
c.at(idx) 返回索引号为idx的元素(如果索引号idx超出范围,抛出range error exception)。
c[idx] 返回索引号为idx的元素(无范围检查)。
c.front() 返回第一个元素(不会检查第一个元素是否存在)。
c.back() 返回最后一个元素(不会检查最后一个元素是否存在)。
对一个空容器调用[],front()和back()经常导致不确定行为,所以我们在调用[]时必须确保索引号是
有效的,在调用front()和back()时容器是非空的。
std::vector<Elem> coll; // empty!
if (coll.size() > 5) {
coll [5] = elem; // OK
}
if (!coll.empty()) {
cout << coll.front(); // OK
}
coll.at(5) = elem; // throws out_of_range exception
迭代器操作:
c.begin()
c.end()
c.rbegin()
c.rend()
c.insert(pos,elem) 在pos迭代器所指的位置处插入一个elem的拷贝,并返回新元素的位置。
c.insert(pos,n,elem) 在pos迭代器所指的位置处插入n个elem的拷贝,没有返回。
c.insert(pos,beg,end) 在pos迭代器所指的位置处插入范围[beg,end)内所有元素的拷贝,没有返回。
c.push_back(elem) 在末尾追加一个elem的拷贝。
c.pop_back() 移除最后一个元素,不会返回这个元素。
c.erase(pos) 移除pos迭代器位置处的元素,并返回下一个元素的位置。
c.erase(beg,end) 移除[beg,end)范围内所有的元素,并返回下一个元素的位置。
c.resize(num) 改变元素的数目为num,如果size()增加,新元素通过默认
函数创建。
c.resize(num,elem) 改变元素的数目为num,如果size()增加,新元素拷贝elem得到。
c.clear() 移除所有元素(使容器变为空)。
将vector用作普通数组:
对于vector容器类有下面的表达式成立:
&v[i] == &v[0] + i
其中v为vector容器类,定义如下:
std::vector<T> v;
下面看一个vector用作普通数组的例子:
std::vector<char> v;
v.resize(41); // 分配能容纳41个字符的空间,包括'/0'在内。
strcpy(&v[0], "hello, world"); // copy a C-string into the vector
printf("%s/n", &v[0]);
注意:不能将一个迭代器当作第一个元素的地址,迭代器是特别实现、有特殊含义的类型。
例如下面这样写是不对的:
printf("%s/n", v.begin()); // ERROR (might work, but not portable)
printf("%s/n", &v[0]); // OK
33. static_cast
用法:static_cast < type-id > ( expression )
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性 。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
C++中static_cast和reinterpret_cast的区别
C++primer第五章里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释
1、C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:
int i;
float f = 166.7f;
i = static_cast<int>(f);
此时结果,i的值为166。
2、C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:
int i;
char *p = "This is a example.";
i = reinterpret_cast<int>(p);
此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,一个明显的现象是在转换前后没有数位损失。
static_cast 和 reinterpret_cast 以及 c语言中的强制转换的区别:
static_cast 是c++中采用的强制转换方法, 具有一定的安全性,即相关部分类型才能转换。
reinterpret_cast 是c++中更类似与C的强制转换方法, 它允许更自由的转换, 但是编程人员需要清楚自己在做什么转换。
c中的强制转换是随便怎么转换都行,不进行限制。
34. final 与static
final:
final可修饰类、域(变量和常量)、方法 (而static不修饰类)
1、final修饰类,表示该类不可被继承。
2、final修饰变量 程序中经常需要定义各种类型的常量,如:3.24268,"201"等等。这时候我们就用final来修饰一个类似于标志符名字。
3、修饰方法: final修饰的方法,称为最终方法。最终方法不可被子类重新定义,即不可被覆盖。
final 与const 如果是限定一些基本类型,没有太大区别,但是用于类, final则 表示其不可继承, 而const的类对象不可调用非const成员函数。
35. 引用、传值和传地址
引用vs 传值
传值操作是一个拷贝操作,能够进行操作的是实参的一个拷贝(形参), 以及返回值的一个拷贝。 如果传值对象代价高,这样的花销是不值得的。
如果采用引用,即把引用结合const使用, 如 func(const CClassA& a){...} 则可以避免这种开销,因为他不使用拷贝,不会产生新的对象。
但是对于内建类型,如int,float, char这些传值会比引用的效率高, 非内建类型通常使用引用效率高
引用vs 传地址
对于引用和传地址(即指针方式),因为指针需要随时判断是否为空,进行有效性判断,而引用在声明变量是必须初始化,因此无有效性判断,更加安全
36. 虚函数的使用
(1)当类要用来被继承的时候, 析构函数通常被声明为虚函数
需要说明的是, 如果类有其他虚函数, 那么也应该有相应的虚析构函数。
为什么这种类的析构函数通常要被声明为虚函数呢? 这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
参考
c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的
37. 函数指针
int max(int x,int y);
int (*pFun)(int x, int y); //这里pFun就是指向 int max(int x,int y)的函数指针, 其声明方法跟函数基本一样,只是名字有所替换(*pFun)替代了max
pFun = max ; //给指针赋值, 函数名代表函数入口地址
38. 构造函数
对于类中有一个成员变量是其他的构造函数带参数的类,如果不定义成指针,而是普通成员变量,其成员变量的构造需要使用构造函数初始化参数列表:
class A
{
public:
A(int a){ m_a = a;}
private: int m_a;
}
class B
{
public:
B(int b): m_b(b), m_classA(b) // 这里这样实现初始化
{
m_pA = new A(b);
}
private:
int m_b;
A m_classA;
A *m_pA;
}
int main()
{
B b(5);
}
强调, 构造函数是用来进行初始化的,最好使用成员初始化列表来进行成员初始化,那种使用函数调用和成员变量赋值的方式不是真正的初始化,效率也不如初始化列表高。
构造函数初始化顺序: 父类高于子类构造函数, 成员变量初始化顺序跟申明顺序相同。
39 const 的用法
(1) const 与函数
void fun(int a) const //任何不会修改数据成员的函数都因该声明为const类型
{...}
这说明函数内部不能改变类的成员变量的值。 如果修改了编译器会报错,大大提高了程序健壮性
const void fun() //通常用于运算符的重载
{...}
比如运算符重载后, 比如 a*b=c, 表示讲c付给了a*b的结果,这样是不行的,如果使用了const修饰,则编译不通过。
(2) const 与指针
char greeting[] = "hello";
char *p = greeting; //non-const pointer, non-const data
const char*p = greeting; //non-const pointer, const data
char * const p = greeting; //const pointer, non-const data
const char* const p = greeting; //const pointer, const data
char *p
40 mutable 的用法
当const用来 这样修饰时 void fun() const {...}, 函数内部的类数据成员是不能被修改的,但是如果定义时声明为 mutable, 则可以再这里被修改。
41 casting 转型
转型是用来将进行一些类型转换的, 如const 型转为非const型, 非const型转为const型
static_cast<const T&>(*this)[position] //为*this加上const
const string str = "hello";
cosnt_cast<char&> str[3]; //将const转除
42 堆栈
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
43 virtual
在基类中,通常会出现virtual 的函数或者析构函数。 virtual 一般用于拿来继承的积累,virtual 函数的目的是允许derived class的实现得以客制化。
比较特殊的是析构函数是否采用virtual。
如果一个类是用来作为基类,而且目的是实现多态,那么通常会有一个virtual的析构函数。或者说任何class只要带有virtual函数都几乎确定应该有一个virtual析构函数。
除了客制化,virtual析构函数还可以避免一下情况。
class base
{
base();
~base();
void print();
。。。
}
class deriveClass:public base
{
deriveClass();
~deriveClass();
void print();
。。。
}
base *p = new deriveClass;
delete p;
当我们delete p 的时候要注意,如果base类的析构函数是non-virtual的, 那么delete只会调用~base(),而不会调用~deriveClass,这样会导致非父类的数据不会被销毁,造成内存泄漏。
如果基类采用了virtual ~base(), 那么这里就~deriveClass() (同时也应该调用了~base()), 达到我们真正的目的。
另外一种情况就是关于上述print()函数。
写一个函数 LetPrint(base b){ b.print();} 目的是调用print打印函数
deriveClass child;
LetPrint(child); 这里调用的是base的print()还是deriveClass的print()呢? 如果print函数在基类不是virtual的,那么一句LetPrint的参数类型,就是调用base的print,如果print是virtual的呢?是不是应该就是deriveClass
的print呢? 不然, 这里还是会调用base的print。因为LetPrint采用的是值传递的方式,这样会使继承的对象出现 切割 引用传值 的方式
LetPrint(const base &b) 这样的话,print就是deriveClass的print了。
总结起来,如果传入一个deriveClass 对象,会有如下结果
const Base &b Base b
virtual print (deriveClass) print (Base) print
print (Base)print (Base)print
44 explicit
c++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的。所谓构造函数是显示的还是隐式的说明如下
class Myclass
{
public:
Myclass(int a);
...
};
int main()
{
Myclass m = 10; //默认的构造函数会隐式的把他转化为 Myclass temp(10); Myclass m = temp;
}
如果在构造函数前面加上 explicit Myclass(int a);
则上述 Myclass m = 10 不会通过隐式转换。
45. frient 友元
友元是为了让其他函数或其他类访问类的私有成员,避免将私有成员申明为public。
class Internet
{
public :
Internet(char *name,char *address)
{
strcpy(Internet::name,name);
strcpy(Internet::address,address);
}
friend void ShowN(Internet &obj);//友元函数的声明
friend class friendClass;
public :
char name[20];
char address[20];
};
void ShowN(Internet &obj)//函数定义,不能写成,void Internet::ShowN(Internet &obj)
{
cout <<obj.name<<endl;
}
class friendclass
{
public:
char* subtractfrom()
{
Internet in;
return in.name;
}
};
void main ()
{
Internet a("中国软件开发实验室","www.cndev-lab.com");
ShowN(a);
friendclass c;
cout<< c.substractform<<endl;
cin .get();
}
友元函数并不能看做是类的成员函数,它只是个被声明为类友元的普通函数,所以在类外部函数的定义部分不能够写成void Internet::ShowN(Internet &obj
46. 异常处理 try catch
某一个可能发生异常的地方使用 throw **(任意类型), 并把这一部分放入try{}里面去, catch紧跟try并进行出错处理
int fun(int a,int b)
{
int c = a+b;
if(c<0) throw c;
return c;
}
int main()
{
int a = 3, b = -4;
try
{
cout<<fun(a,b)<<endl;
}
catch(int)
{
cout<<a<<" "<<b<<" "<<"和小于0"<<endl;
}
cout<<"end"<<endl;
return 0;
}
几个注意:
(1) try catch 中间不能夹杂其他语句 如
try{}
int a = 0;
catch(int){}
(2) try 可以配多个catch, 因为可能抛出不同类型的异常
(3) 删节号 ... 表示可以捕捉任何异常 catch(...) { cout<<"OK";}
(4) try -catch 结构可以出现在不同函数中。
他的找寻顺序是逐次往上层搜索catch (如果找不到catch ,会系统会调用系统terminal是程序终止)
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhongjiekangping/archive/2009/12/06/4952730.aspx