首页 > 代码库 > C/C++知识点

C/C++知识点

二维数组

意义:

  1. int **Ptr 表示指向"一群"指向整数的指针的指针。
  2. int *Ptr[ 5 ] 表示指向 5 个指向整数的指针的指针,或者说Ptr有5个指向"一群"整数的指针,Ptr是这5个指针构成的数组的地址
  3. int ( *Ptr )[ 5 ] 表示指向"一群"指向 5 个整数数组的指针的指针。

所占空间:

  1. int **Ptr 和int ( *Ptr )[ 5 ] 一样,在32位平台里,都是4字节,即一个指针。
  2. 但int *Ptr[ 5 ] 不同,它是 5 个指针,它占5 * 4 = 20 个字节的内存空间。

用法:

1. int **Ptr

因为是指针的指针,需要两次内存分配才能使用其最终内容。首先,Ptr = ( int ** )new int *[ 5 ];这样分配好了以后,它和(2)的意义相同了;然后要分别对 5 个指针进行内存分配,例如:

Ptr[ 0 ] = new int[ 20 ];

它表示为第 0 个指针分配 20 个整数,分配好以后, Ptr[ 0 ] 为指向 20 个整数的数组。这时可以使用下标用法 Ptr[ 0 ][ 0 ] 到Ptr[ 0 ][ 19 ] 了。

2. int *Ptr[ 5 ]

这样定义的话,编译器已经为它分配了 5 个指针的空间,这相当
于(1)中的第一次内存分配。根据对(1)的讨论可知,显然要对其进行一次内存分配的。否则就是"野"指针。

3. int ( *Ptr )[ 5 ]

它的意义是"一群"指针,每个指针都是指向一个 5 个整数的数组。如果想分配 k 个指针,这样写: Ptr = ( int ( * )[ 5 ] ) new int[ 5 * k ]。这是一次性的内存分配。分配好以后,Ptr 指向一片连续的地址空间,其中 Ptr[ 0 ] 指向第 0 个 5 个整数数组的首地址,Ptr[ 1 ] 指向第1 个 5 个整数数组的首地址。

综上所述,可以这样理解它们:

int ** Ptr <==> int Ptr[ x ][ y ];

int *Ptr[ 5 ] <==> int Ptr[ 5 ][ x ];

int ( *Ptr )[ 5 ] <==> int Ptr[ x ][ 5 ];

这里 x和 y 是表示若干的意思。

sizeof

功能:计算数据空间的字节数。

1. 与strlen()比较;

strlen()计算字符数组的字符数,以"\0"为结束判断,不计算为‘\0‘的数组元素。而sizeof计算数据(包括数组、变量、类型、结构体等)所占内存空间,用字节数表示。

2. 指针与静态数组的sizeof操作;

指针均可看为变量类型的一种。所有指针变量的sizeof操作结果均为4。

注意:int *p; sizeof(p)=4;

但sizeof(*p)相当于sizeof(int);     

对于静态数组,sizeof可直接计算数组大小;

例:int a[10];char b[]="hello";

sizeof(a)等于4*10=40;

sizeof(b)等于6;

注意:数组做型参时,数组名称当作指针使用!!

void  fun(char p[])

{sizeof(p)等于4}    

经典问题: 

double* (*a)[3][6]; 

cout<<sizeof(a)<<endl; // 4  a为指针

cout<<sizeof(*a)<<endl; // 72  *a为一个有3*6个指针元素的数组

cout<<sizeof(**a)<<endl; // 24  **a为数组一维的6个指针

cout<<sizeof(***a)<<endl; // 4  ***a为一维的第一个指针

cout<<sizeof(****a)<<endl; // 8  ****a为一个double变量

3. 格式的写法;

sizeof操作符,对变量或对象可以不加括号,但若是类型,须加括号。

4. 使用sizeof时string的注意事项;

string s="hello";

sizeof(s)=4,sizeof(s.c_str())=4。

注意string的大小与实现有关。

5. union 与struct的空间计算;

总体上遵循两个原则:

1)  整体空间是占用空间最大的成员(的类型)所占字节数的整倍数

2)  数据对齐原则----内存按结构成员的先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整倍数,如果不够则补齐,以此向后类推。。。。。

3)  注意:数组按照单个变量一个一个的摆放,而不是看成整体。如果成员中有自定义的类、结构体,也要注意数组问题。

struct s1

{

char a[8];

};

 

struct s2

{

double d;

};

 

struct s3

{

s1 s;

char a;

};

 

struct s4

{

s2 s;

char a; 

};

struct s5

{

s3 s;

char a;

};

cout<<sizeof(s1)<<endl; // 8

cout<<sizeof(s2)<<endl; // 8

cout<<sizeof(s3)<<endl; // 9

cout<<sizeof(s4)<<endl; // 16;

cout<<sizeof(s5)<<endl; // 10;

 

define

defineconst

1. 编译器处理方式不同。

define宏是在预处理阶段展开。

const常量是编译运行阶段使用。

2. 类型和安全检查不同。

define宏没有类型,不做任何类型检查,仅仅是展开。只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误

const常量有具体的类型,在编译阶段会执行类型检查。有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

3. 存储方式不同。

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。

const常量会在内存中分配(可以是堆中也可以是栈中)。

4. const  可以节省空间,避免不必要的内存分配。

例如:  

#define PI 3.14159 //常量宏  

const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ...... 

double i=Pi; //此时为Pi分配内存,以后不再分配! 

double I=PI; //编译期间进行宏替换,分配内存  

double j=Pi; //没有内存分配 

double J=PI; //再进行宏替换,又一次分配内存! 

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。 

5. 提高了效率。

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

definetypedef

#define是C中定义的语法,typedef是C++中定义的语法,二者在C++中可以通用,但#define成了预编译指令,typedef当成语句处理。typedef和define都可以用来给对象取一个别名,但是两者却有着很大不同。

1. 首先,二者执行时间不同。

关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。

define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。

#define用法例子: 

#define f(x) x*x  

main( )  

{  

int a=6,b=2,c;  

c=f(a) / f(b);  

printf("%d \n",c);  

程序的输出结果是: 36,根本原因就在于#define只是简单的字符串替换,应当加个括号“(X*X)”。

2. 功能不同。

typedef用来定义类型的别名,这些类型不只包含内部类型(int,char等),还包括自定义类型(如struct),可以起到使类型易于记忆的功能。 

如:

typedef int (*PF) (const char *, const char *); 

定义一个指向函数的指针的数据类型PF,其中函数返回值为int,参数为const char *。

typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:

typedef long double REAL; 

在不支持 long double 的机器上,该 typedef 看起来会是下面这样:

typedef double REAL; 

并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:

typedef float REAL; 

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

3. 作用域不同。

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。

void fun()   

{   

#define A int   

}  

void gun()   

{   

//在这里也可以使用A,因为宏替换没有作用域,   

//但如果上面用的是typedef,那这里就不能用A ,不过一般不在函数内使用typedef  

4. 对指针的操作。

二者修饰指针类型时,作用不同。

typedef int * pint;  

#define PINT int *  

const pint p;//p不可更改,p指向的内容可以更改,相当于 int * const p;  

const PINT p;//p可以更改,p指向的内容不能更改,相当于 const int *p;或 int const *p;  

pint s1, s2; //s1和s2都是int型指针  

PINT s3, s4; //相当于int * s3,s4;只有一个是指针。 

其实,typedef和define末尾的标号也是不一样的,希望大家不要忽略这一点。通过本文的分析,相信你已经了解了这两者之间的区别。掌握了区别之后,运用起来会更加的灵活。

static

1. 第一个作用:隐藏;

当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。

下面是a.c的内容

#include<cstdio>增加这条语句

char a = ‘A‘; // global variable

void msg()

{

     printf("Hello\n");

}

你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。

如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。

2. static的第二个作用是保持变量内容的持久;

存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

3. static的第三个作用是默认初始化为0;

其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。

4. 在C++类中的独特作用;

C++类中被static修饰的成员变量是类变量,被static修饰的成员函数是类函数。

常量指针和指针常量

常量指针:指向常量的指针,指针所指向的内容不能改变,但是可以改变其所指向的地址。

const类型 *指针常量名=&变量名;或者 类型 const *指针常量名=&变量名;

如:const int k=5,t=8;

const int *p=&k;

*p=12;(错误) //常量指针指向的内容是常量,不可以赋值

p=&t;(正确) //常量指针的地址可以修改

指针常量:指针常量是指针所指向的位置不能改变,即指针本身是一个常量。但是指针常量可以通过间接引用修改内存中的数据。

定义指针常量的语句格式为:

指针类型 *const 指针常量名=&变量名;

 

例如: int a=5,b=7;

int *const p=&a;

*p=b;(正确)//指针常量指向的内容可以修改

p=&b(错误)//指针常量是指针所指向的位置不能改变

mallocnewfreedelete

1. new 是c++中的操作符,malloc是c中的一个函数。

2. new 不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数,而malloc则只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数。

3. new出来的指针是直接带类型信息的。而malloc返回的都是void指针。