Basic Knowledge

本文深入探讨了C++中的高级特性,如#define与const的区别、内存对齐的原因及规则、不同内存分配方式及其区别,以及const关键字和引用变量的用法。

#define跟const的区别

1. 编译器处理方式不同  

define宏是在预处理阶段展开。
const常量是编译运行阶段使用。

2. 类型和安全检查不同

define宏没有类型,不做任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。

3. 存储方式不同

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存
const常量会在内存中分配(可以是堆中也可以是栈中)。

4. 提高了效率

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

const关键字作用
  1. 阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了
  2. 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const
  3. 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

  4. 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

  5. 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”
//先忽略类型名(编译器解析的时候也是忽略类型名)
//const离哪个近。"近水楼台先得月",离谁近就修饰谁, 判断时忽略括号中的类型
const (int)*p;  //修饰*p, 指向的对象不可变
(int)const *p;  //同上
(int)*const p;  //修饰p, p不可变
const(int)*const p; //前一个const修饰*p, 后一个修饰p, 都不可变

引用变量

引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
声明引用时,必须同时对其进行初始化 , 不能再把该引用名作为其他变量名的别名。
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
不能建立引用的数组。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据在函数中不被改变,就应使用常引用。

int a = 0;
const int &b = a;
将引用作为函数参数传递
  1. 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
  2. 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
  3. 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

#include, #import, @class

#include <x.h> : 用于对系统自带的头文件引用,编译器会在系统文件目录下去查找该文件
#include “x.h” : 用户自定义的文件引用,编译器查找顺序->用户目录->安装目录->系统文件

在使用#include的时候要注意处理重复引用
例如:ClassA和ClassB同时引用了ClassC,不做重复引用处理的时候在ClassD中同时引用ClassA,ClassB编译会提示对ClassC重复引用的错误.
#import 大部分功能和#include相同,但处理了重复引用问题

#ifndef _CLASSC_H
#define _CLASSC_H
#include "ClassC"
#endif

@class主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字,而这个类的定义实现是暂时不用知道的,后面会告诉你
也是因为在@class仅仅只是声明一个类,所以在后面的实现文件里面是需要去#import这个类,这时候才包含了这个被引用的类的所有信息。

综上所述#include,#import与@class的区别可以做一下理解:

#include与#import在引用一个类的时候会包含这个类的所有信息包括变量方法等,但是这样做会对编译效率造成影响.
比如有100个类都#import了ClassA,那么在编译的时候这100个类都会去对ClassA处理.又比如A被B引用,B被C引用,C被D引用…..此时如果A被修改,那么后面的B,C,D…..都需要重新进行编译.
所以一般来说,在interface中引用一个类,就用@class,它会把这个类作为一个类型来使用,而在实现这个interface的文件中,如果需要引用这个类的实体变量或者方法之类的,还是需要import这个在@class中声明的类。


面向对象的三个基本特征,并简单叙述之?

  1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
  2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
  3. 多态:系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性。

内存对齐

原因:

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    1)how programmers see memory
    这里写图片描述
    2)how processors see memory
    CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的
    这里写图片描述

  2. 性能原因:经过内存对齐后,CPU的内存访问速度大大提升。
    这里写图片描述
    当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
    当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
    此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能

规则:

  1. 对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数
  2. 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行(#pragma pack(n) 表示设置为n字节对齐。 VC6默认8字节对齐)
    对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保 证每一项都边界对齐
struct st1
{
    char a;
    int b;
    short c;
};

struct st2
{
    short c;
    char a;
    int b;
};

int main()
{
    cout<<"sizeof(st1) is"<<sizeof(st1)<<endl;
    cout<<"sizeof(st2) is"<<sizeof(st2)<<endl;
    return 0;
}
//输出结果为:
//sizeof(st1) is 12
//sizeof(st2) is 8 

这里写图片描述

描述内存分配方式以及它们的区别

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量, 初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.

  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
int a = 0;  //全局初始化区
char* p1;   //全局未初始化区
main()
{
    int b;  //栈
    char s[]= "abc";    //栈
    char* p2;   //栈
    char* p3= "123456"; //123456\0在常量区,p3在栈上。
    static int c= 0;    //全局(静态)初始化区
    p1= (char*)malloc(10);
    p2= (char*)malloc(20);  //分配得来的10和20字节的区域就在堆区。
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值