复制的内容不同。strcpy只容能复制字符串,而memcpy可以复制任意内。 复制的方法不同。strcpy不需要指定长度,它遇到字符串结束符 ‘\0’ 便结束。memcpy则是根据其第3个参数决定复制的长度。 用途不同。通常在在复制字符串是用strcpy,而需要复制其它类型数据时则一般用memcpy
char *a="hhb57";
printf("sizeof(a)=%d, strlen(a)=%d\n", sizeof(a), strlen(a));
output: 4, 5
void reverse(char *s) {
int i; char tmp; int len = strlen(s);
for(i=0; i<len/2;i++){
s[i]<-->s[len-i-1] //tmp = s[len-i-1]; s[len-i-1] = s[i]; s[i] = tmp;
}
}
int main(int argc, char* argv[]){
char a[]="12345657321"; //不能写成 char *s="12345657321" ,这样会将字符串作为一个常量,就无法修改了
reverse(a);
}
bool huiwen(char *s){
int len = strlen(s);
int i=len/2;
do{
if(s[i]!=s[len-i-1]) return false;
}while(i--);
return true;
}
void sub_str(char *str){
char max_str[]="\0";
int i, len, sub_len=0;
len = strlen(str);
for(i=0; i<=len; i++){
if(str[i]==' ' || str[i]=='\0'){
if(strlen(max_str) < sub_len){
strncpy(max_str, str+i-sub_len, sub_len);
max_str[sub_len]='\0';
}
sub_len=0;
}
else{
sub_len++;
}
}
printf("max_str=%s, len=%d\n", max_str, strlen(max_str));
}
void uppers(char *s , char *us)
{
for( ; *s!='\0'; s++, us++){
if(*s>='a' && *s<='z')
*us = *s -32;
else
*us = *s;
}
*us='\0';
}
char* strchr(char* s,char c)
{
while(*s != '\0' && *s != c)
{
++s;
}
return *s == c ?s:NULL;
}
if((x>=-EPSINON) && (x<=EPSINON))
程序在执行算术运算时,低类型自动隐式转换为高类型。 在赋值表达式中,右边表达式的值自动隐式转换为左边的变量的类型。
从二维数组的角度来看,a是二维数组名,a代表整个二维数组的首地址,也是二维数组0行的首地址,等于1000。 a+1代表第一行(注意有0行)的首地址,等于1008。a[0]是第一个一维数组的数组名和首地址,因此也为1000。(a+0)或a是与a[0]等效的, 它表示一维数组a[0]0 号元素的首地址,也为1000。&a[0][0]是二维数组a的0行0列元素首地址,同样是1000。因此,a,a[0],(a+0),a,&a[0][0]是相等的。 同理,a+1是二维数组1行的首地址,等于1008。a[1]是第二个一维数组的数组名和首地址,因此也为1008。&a[1][0]是二维数组a的1行0列元素地址,也是1008。因此a+1,a[1],(a+1),&a[1][0]是等同的。 由此可得出:a+i,a[i],(a+i),&a[i][0]是等同的。 此外,&a[i]和a[i]也是等同的。因为在二维数组中不能把&a[i]理解为元素a[i]的地址,不存在元素a[i]。C语言规定,它是一种地址计算方法,表示数组a第i行首地址。由此,我们得出:a[i],&a[i],(a+i)和a+i也都是等同的。 另外,a[0]也可以看成是a[0]+0,是一维数组a[0]的0号元素的首地址,而a[0]+1则是a[0]的1号元素首地址,由此可得出a[i]+j则是一维数组a[i]的j号元素首地址,它等于&a[i][j]。由a[i]=(a+i)得a[i]+j=(a+i)+j。由于(a+i)+j是二维数组a的i行j列元素的首地址,所以,该元素的值等于((a+i)+j)。
void func(int n, char (*str)[5]){
int i;
for (i = 0; i < n; i++){
printf("\nstr[%d] = %s\n", i, str[i]);
}
}
void main(){
char str[][5] = {"abc", "def", "ghi"};
func(3, str);
}
把二维数组a分解为一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量。可定义为: int (p)[4] 它表示p是一个指针变量,它指向包含4个元素的一维数组。若指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0]等。而p+i则指向一维数组a[i]。从前面的分析可得出(p+i)+j是二维数组i行j 列的元素的地址,而((p+i)+j)则是i行j列元素的值。
二维数组指针变量说明的一般形式为: 类型说明符 (指针变量名)[长度] 其中“类型说明符”为所指数组的数据类型。“”表示其后的变量是指针类型。“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。应注意“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组(本章后面介绍),意义就完全不同了。
变量的指针就是变量的地址。存放变量地址的变量是指针变量。即在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个变量的地址或称为某变量的指针。
引用必须被初始化,指针可以不用初始化。 引用初始化以后就不能被改变,而指针可以改变所指向的对象。 不存在指向空值的引用,而指针可以指向一个空值。 因为引用不能指向空值,这意味着使用引用之前不需要测试其合法性;而指针则需要经常进行测试。所以使用引用的代码效率要比使用指针的效率高,同时也使引用具有更高的安全性。
void swap(char *&a, char *&b) { char *temp; temp=a; a=b; b=temp;
指针变量的位数根据机器地址总线位数而定,对于32位地址总线的机器指针的位数就是4个字节。
void 指针表示指向不属于任何类型的对象,它与空指针完全是两回事。void指针可以应用于函数指针,也可以应用于纯粹的内存操作。
一个有10个指针的数组,该指针是指向一个整型数的:int a[10] 一个指向有10个整型数数组的指针:int (a)[10] 一个指向函数的指针,该函数有一个整形参数并返回一个整型数:int (a)(int) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型数:int (a[10])(int) ### 3.4.5 const指针 ### char * const p: 表示常量指针,p的值不可以修改。 char const * p: 表示指向常量的指针,指向的常量值不可以修改。 const char * p: 表示同 char const *p
const int *a1; //指针指向值不能变
int * const a1; //指针值不能变
在函数体,一个被声明为静态的变量在函数被调用的过程中维持其值不变 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外其他函数访问。它是一个本地的全局变量 在模块内,被声明为静态的函数只能被这一模块内的其他函数调用。即函数被限制在声明它的模块范围内。 static全局变量与普通变量的区别:static 全局变量只初始化一次,防止在其他文件单元中被引用;static局部变量只被初始化,下一次依据上一次结果值;static函数在内存中只有一份,普通函数则在每个被调用中维持一份复制品。
可以定义const常量 便于进行类型检查,const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 可以很方便地进行参数的调整和修改,同宏定义一样,可以做到不变则已,一变都变 const,定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝 提高了效率,编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
static int i;
int j;
static void init(){}
void callme(){
static int sum;
}
上面的全局变量i和init函数只能用在本文件中,全局变量j和函数callme()的全局扩充到整个工程文件。所以可以在下面的b.c中用extern关键字调用。extern告诉编译器这个变量或者函数在其他文件里已经被定义了。 文件b.c extern int j; //调用a文件里的 extern 的另外用法是当C和C++混合编程时如果C++调用的是c源文件定义的函数或者变量,那么要加extern来告诉编译器用C方式命名函数。 作用域:如果在变量定义之前要使用该变量,则在用之前加extern声明变量,作用域扩展到从声明开始,到本文件结束。 另外声明时不需要添加类型,可以这样声明:extern j; 注意: 对于声明外部构造类型(结构体,共用体)变量,要注意两点,这两点也是和声明外部基本类型变量的区别: a. 声明外部结构体类型变量时,该文件中必须要有构造类型的定义实体,否则会报错。 b. 声明外部构造体类型变量时,不能省略变量类型,否则也会报错。(声明外部基本类型变量时,却可以省略变量类型)。
A.若全局变量仅在单个文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度; B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度; C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
高————————————————————————————————————————————————————»低
初级运算符( ( ), [], ->, .) ——» 单目运算符——»双目运算符——»三目运算符——»赋值运算符——»逗号运算符
单目运算符:算术运算符—>移位运算符—>关系运算符—>逻辑位运算符—>逻辑运算符
对于一个频繁使用的短小函数,在C语言中应用宏定义,而C++用inline实现。
通过名称理解:形式参数,按照名称进行理解就是形式上存在的参数。实际参数,按照名称进行理解就是实际存在的参数。 通过作用理解: 形式参数,在定义函数时,函数名后面括号中的变量名称为“形式参数”。在函数调用之前,传递给函数的值将被复制到这些形式参数中。 实际参数,而在调用一个函数时,也就是真正使用一个函数时,函数名后面括号中的参数为“实际参数”。函数的调用者提供给函数的参数叫实际参数。实际参数是表达式计算的结果,并且被复制给函数的形式参数。
char *RetMemory(void){
char p[] = "hello world!";
return p; //返回时使用了局部变量,局部变量保存在堆栈中,在函数调用结束后被释放,所以返回该地址是没有意义的。
}
& 按位与运算通常用来对某些位清0或保留某些位
| (按位或); ^(按位异或); ~(取反)
« 左移 » 右移
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。
struct bs{
int a:8; int b:2; int c:6;
}data; //说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。
一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。 例如: struct bs { unsigned a:4 ; unsigned :0 ; /空域/ unsigned b:4 ; /从下一单元开始存放/ unsigned c:4 ; } 在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。
位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
};
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种格式输出。 【例12.6】
main(){
struct bs{
unsigned a:1; unsigned b:3; unsigned c:4;
} bit,*pbit;
bit.a=1; bit.b=7; bit.c=15; printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0; pbit->b&=3; pbit->c|=1;
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}
大端模式:是数据低位保存在内存的高地址中。 小端模式:是数据低位保存在内存的低地址中。
i&0x1 ### 7.5.2 符号位的扩展及取反运算 ###
unsigned char a=0xA0(00000000 00000000 00000000 10100000) ~a(11111111 11111111 11111111 01011111)
unsigned char c=~a (01011111) c»4 (0000 0101)
unsigned char a=0xA0(11111111 11111111 11111111 10100000) ~a(00000000 00000000 00000000 01011111)
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。其格式一般为: #Pragma Para
其中Para 为参数,下面来看一些常用的参数。
Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: #Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#pragma message("_X86 macro activated!")
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
8.1.3 #pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
8.1.4 #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以
预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
错误:#define swap(x, y) int z=x; x=y; y=z 正确:#define swap(x, y) x = x+y; y=x-y; x=x-y; 或者 x = x^y; y=x^y; x=x^y
宏是编译期进行的,编译器在编译程序时,首先进行预编译,也就是将代码中的宏替换为宏定义的内容。而函数是运行期进行的。 程序中使用宏时不会进行参数类型检查,而函数则会进行参数类型检查 宏只是编译器进行替换,而函数会再桟总定义局部变量和函数参数 宏没有生存期、作用域之类的概念,而函数有。
#define SECOND_PER_YEAR (60*60*24*365UL)
__FILE__,输出所在文件路径及文件名。
__LINE__,输出当前文件代码行号。
#define (name) do_##name (x) do_x
#define (name) #@name (x) 'x'
#:在宏展开的时候会将#后面的参数替换成字符串,如:
#define p(exp) printf(#exp);
对指针变量的定义包括三个内容:指针类型说明,即定义变量为一个指针变量;指针变量名;变量值(指针)所指向的变量的数据类型。其一般形式为:类型说明符 变量名;eg: int *p1; 其中,表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。
void swap(int *a, int *b){
int tmp; tmp = *a; *a = *b; *b = tmp;
}//改成其它方式均不可,原因是我们要改变指针指向的内容值,而不是指针本身
“野指针”是在定义后没有对其进行初始化,或者指针指向的内存被释放,而指针没有被设置为NULL。野指针随机地指向一个地址,使用这个指针进行操作时,就会更改内存的数据,造成程序数据的破坏,严重威胁着程序的安全。
void swap(int *x, int *y) {}
void main(){
int a=3, b=4;
void (*p)(int *, int *); // void (*p[10])(int *, int *),表示函数指针数组,每个指针指向一个 void swap(int *x, int *y) 函数。
p = swap;
(*p)(&a, &b);
}
主要有三种方式,分别为静态存储区分配、堆分配和桟分配。其中,静态存储区是在编译时就确定的。例如,程序中的全局变量或静态变量就在静态存储区中。堆分配又称为动态分配,例如程序中使用 new()、malloc()函数分配的内存。采用堆分配的内存,用户在使用后需要手动释放该内存,否则会出现内存泄露。桟分配属于静态分配,函数参数、函数返回值等都在桟中进行分配。函数调用后会自动释放桟空间。桟分配的特点是执行效率高,但是空间有限。
它们的返回值为 void *类型,也就是无符号指针类型,必须将其进行强制类型转换。 它们是在堆中分配空间,在使用后需要释放堆空间。
delete只调用一次析构函数,通常用于释放单个对象的堆空间。delete[]会调用数组中每一个元素(类对象)的析构函数,用于释放对象数组的堆空间。