C++高质量编程学习笔记(3)

1、指针
(1)指针的指针 int *p2 = & a;int **p1 = &p2;则p1的value为p2的地址;*p2指的是p2所指的内存单元的值。
(2)指针大小为4个字节——内存地址大小。int型;
(3)& 只能用一次 ,应为& 得到的结果已经不是一个变量了;而* 可以多次使用,意义不同
(4)指针传递:指针类型参数,传递的是地址,而不是内存中的值。

2、数组
(1)数组名本身就是一个指针,为指针常量,即a等价于int * const a,因此不能修改数组名, 数组名的值就为数组第一个元素的内存单元首地址-》a ==&a【0】;
(2)将数字作为参数传递进函数的时候,其实只是传递的数组首地址,而不是整个数组。即为同类型的指针。
(3)对于多维数组,传递的是数组指针。
(4)a[i][j]; (a[i]+j);((a+i))[j];((a+i)+j);是等价的
(5)删除数组正确方法:char *p= newchar【200】 ;delete【】p; 使用的是delete【】,而不是delete。如果使用delete,置灰释放p指向的第一个元素空间,后面的空间都会丢失,导致内存泄露。
(6)删除多维数组,需要将多维数组转化为一维数组,然后用delete【】删除。

3、字符串和字符数组
(1)字符串结尾都有一个“/0”,因此如果有char aa【】= “hello”;则aa的元素为{“h”“e”“l”“l”“o”“/0”};所以不能将字符数组当做字符串,使用strlen、strcpy等字符串库函数,因为这些函数是通过判断“/0”来确定结尾的,如果是字符数组,没有“/0”将会导致内存访问冲突。
(2)
char ch =‘a’;
char *p = &ch;
cout<<p<<endl;

错误 应该为
cout<<*p<<endl;
char *
表示为字符串。
(3)字符串的拷贝只能用strcpy等函数,不能简单的=(这是字符指针的赋值) 同理比较也应该用函数。

4、函数指针
(1)注册回调函数的时候,用到函数指针。函数体的地址在编译的时候就确定了,因此函数地址是一个编译时的常量。函数指针是指向函数体的指针,其值就为函数体的首地址。在源代码层面上,函数名代表函数的首地址,所以也可以直接使用函数名来注册回调函数。
(2)通过函数指针调用函数有两种方式:
①直接将函数指针变量当做函数名,然后填入参数;
②将函数指针的反引用作为函数名
(3)函数链接的本质就是讲函数地址绑定到函数的调用语句上。
(4)类的静态函数不依赖于类的对象而存在,也不依赖于类的对象的调用,因此类的静态函数地址可以直接看为类的静态函数的函数名,可以直接指派给全局函数类型的指针。
(5)其他两种函数类型(普通函数和虚函数)也不依赖于类的对象的存在,但是其调用必须绑定到具体的对象中(this指针),去他们两个的地址需要使用&。

class CTest
{
public :
void f(void){`cout<<“f()“<<endl;}
 static void g(void){`cout<<“g()“<<endl;}
virtual void h(void){`cout<<“h()“<<endl;}
}
typedef void (*GFPtr)(void);// 定义一个全局函数指针,GFPtr是具体的一个类型
GFPtr fp = CTest::g; //取静态函数的地址,只需要函数名。 
fp(); // 通过函数指针调用类的静态函数
typedef void (CTest::*MemFuncPtr)(void); // 声明类成员函数指针类型
MemFuncPtr mftp_1 = &CTest::f; //  声明成员函数指针变量并初始化
MemFuncPtr  mftp_2 = &CTest::h; //注意获取成员函数地址的方法
CTest obj;
(obj.*mftp_1)(); //使用对象和成员函数指针调用成员函数
(obj.*mftp_2)();
CTest *ptest = & obj;
(ptest->*mftp_1)();//使用对象指针和成员函数指针调用成员函数
(ptest->*mftp_2)();

5、引用和指针的比较
(1)& 引用 不是取地址,

int m;
int &n = m; 

这里n相等于m的别名,对n的操作就是对m的操作。n就是m本身,而不是指针或者拷贝。
(2)不要用常量初始化引用,因为在这个过程中会创建一个临时对象来接受常量,在将该临时对象初始化引用,所以只要引用不销毁,该临时变量也不是销毁。
(3)初始化话后,引用不能再赋值了。
(4)引用的主要用途是修饰函数的参数和返回值——参数的引用传递。

int getint (int &x);
{
x=x+10
}
int main ()
{
int n=0;
getint(n);
} 

参数指针传递

int getint (int *x);
{
(*x)=(*x)+10
}
int main ()
{
int n=0;
getint(&n);
} 

**

C++高级数据类型

**
1、struct 和 class 都可以用来定义类;
2、数组当做参数传给函数的时候,数组将会自动转化为指针;但是包装在struct/class中的数组器内存空间属于 struct/class的对象所有,因此把struct/class当做参数传递给函数的时候,默认是值传递,其中的数组全部拷贝到函数堆栈中。
3、需要防止结构体中的成员越界,不然会覆盖后面的成员;
4、任何类型的指针大小都一样 4个字节;给指针分配存储空间的时候,不需要知道他指向的对象的类型细节;不能交叉包含,只能交叉引用;

struct A                    struct B
{                           {
int count ;                   char ah;
char *pName;                  A ×pa; 
B *pb;               B ×pb; 
}                 }

是成立的; 
但是当

struct A                    struct B
{                           {
int count ;                   char ah;
char *pName;                  A pa; 
B pb;               B pb; 
}                 }

是错误的 ;因为你没有办法计算sizeof的值;即对象不能自包含,无论直接还是间接,编译器不知道该给这样的对象分配多少内存;不能交叉包含,只能交叉引用;

5、位域——节约存储空间 (但是运行速度会下降,因为计算机无法直接寻址到单个字节中的某些位)

struct A
{
unsigned int year :4;
unsigned int mouth:5;
unsigned int day  :5;
unsigned int hour :6;
}

使用单个的位(bit)为单位设计struct所需的存储空间。不能使用浮点型和指针型变量作为位域的成员类型,因为这两种会出现无效值;

6、防止修改位域成员出现的溢出问题;不能取一个位域对象的数据成员的地址,这是因为最小单位是字节,但是可以位域对象的地址;

7、成员对齐——程序运行期间的稳定性
用于压缩对象的内存占用量,并且不损失CPU对对象的访问效率;
使用offsetof确定一个成员数据在结构中的偏移量;
8、主要的方法还是按成员变量——从大到小的顺序从前向后依次声明每隔数据成员,并尽量使用较小成员对其的方式;——这样也不会导致到中间留出填充位影响偏移

#ifdef _MSC_VER
#progma pack(push, 8)//按8字节边界对其
#endif
struct  B
{
}
#ifdef _MSC_VER
#progma pack(pop)
#endif

9、因此计算一个struct中的大小时,不是单单对对其中成员相加,而是要考虑对其,小于4个字节的需要填充到4个字节,大于4小于8的填充到8个字节等等;但是如果多个1个字节的在最后,可以合为一个4个字节(或者2个字节)的;

10、不对齐还会导致模块间接口的语义一致性和对象的二进制兼容性,不一致的对齐导致程序运行时产生错误的结果;

**

联合 Union

**
不同类型的数据间共享存储空间的方法;不同类型的数据间的自动转化;同一时间里只能存储一个成员的值;内场大小取决于成员中字节数最多的那个,而不是累加;

枚举 Enum

成员从0开始,如果指定数值,则后面的成员一次加1;底层不是用int实现,因此数值可以超出int的最大值

C++ 编译预处理

1、文件包含 #include,<>表示为开发环境提供的搜索路径
2、宏定义 #define ——文件作用域

 #define output(word) cout<<#word<<endl;   
 output(world);
 替换后为  cout<<“world”<<endl;

inline函数(内联函数)和宏的 比较

1.内联函数在运行时可调试,而宏定义不可以;
2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
3.内联函数可以访问类的成员变量,宏定义则不能;
4.在类中声明同时定义的成员函数,自动转化为内联函数。

在 C程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行return等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。
内联函数
编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码,“断言assert”恐怕是唯一的例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。

内联函数被编译器自动的用函数的形势添加进代码,而不会出现这种情况。
内联函数的使用提高了效率(省去了很多函数调用汇编代码如:call和ret等)。
1.递归函数不能定义为内联函数
2.内联函数一般适合于不存在while和switch等复杂的结构且只有1~5条语句的小函数上,否则编译系统将该函数视为普通函数。
3.内联函数只能先定义后使用,否则编译系统也会把它认为是普通函数。
4.对内联函数不能进行异常的接口声明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值