1.内存管理的开销 2.函数调用框架 3.类为什么要定义在头文件 4.C++的组合 5.在类的外部定义成员函数 6.bool类型为什么可以当做int类型 7.无符号保留原则
8.C++类型检查 9.何时会发生隐式类型转换 10函数的返回值 11.浅拷贝 12.对数组无效的指引 13.cout的使用 14.文件的读写 15.名字查找
内存管理的开销
当在栈里自动创建对象时,对象的大小和它们的生存期被准确地内置在生成的代码里,这时因为编译器知道确切的类型,数量和范围,而在堆里创建对象还包括另外的时间和空间的开销,例如使用new创建
对象,此时会调用malloc来从堆里申请一块内存,从堆里搜索一块足够大的内存来满足请求,这可以通过检查某种方式排列的映射或目录来实现,这样的映射或目录用以显示内存的使用情况,这个过程很快
但也可能要试探几次,所以它可能是不确定是,即每次运行malloc并不是花费了完全相同的时间,在指向这块内存的指针返回之前,这块内存的大小和地址必须记录下来,这样以后再调用malloc就不会使用它
而且当free时系统就会知道释放多大的内存,类stash和stack自己都将不“拥有”它们指向的对象,即当stash和stack对象出了范围,它也不会为它指向的对象调用delete,视图使它们成为普通的类是不可能的,
原因是它们是void指针,而如果delete一个void指针,唯一发生的事情就是释放内存,这是因为既没有类型信息也没有办法使得编译器知道要调用哪个析构函数
函数调用框架
当编译器为函数调用产生代码时,它首先把所有的参数压栈,然后调用函数,在函数内部产生代码,向下移动指针为函数局部变量提供存储单元(这里的向下是相对的,在压栈时,机器的栈指针可能增加也可能减少),
但是在汇编语言CALL中,CPU把程序代码中的函数调用指令的地址压栈,所有汇编语言RETURN可以使用这个地址返回到调用点,函数使用的这块内存为函数框架,有函数参数,返回地址,局部变量
类为什么要定义在头文件
类可以在函数体内定义,但是因为这样的类受到了一些限制,所以类一般都不定义在函数体内,在函数体外定义类时,在各个知道的源文件中可能只有一处该类的定义,而且如果要在不同的文件中使用同一
个类,类的定义就必须保持一致,为了确保各个文件中的类定义一致,类通常定义在头文件中,而且类所在头文件的名字应与类的名字一样
C++的组合
简单地在新类中创建已存在类的对象,因为新类是由已存在的对象组合而成,所以这种方法称为组合。
在类的外部定义成员函数
在类的外部定义成员函数时函数的定义必须与声明匹配,也就是说返回类型,参数列表,函数名都得与类内的声明保持一致,如果成员函数被声明为常量成员函数,
那么它的定义也必须在参数列表后面明确指定const,类外部定义的成员名字必须包含类名,const一般位于声明或定义的最后
bool withline() const ;// 声明
bool CC_ListAll_T1M1_L::withline()const {} //定义,若是有引用,那么应该将&放在类名之前,返回类型之后,如bool &CC_ListAll_T1M1_L::withline() const {}
bool类型为什么可以当做int整型
实际上bool型变量占用了一个字节的内存,当值为false的时候,实际存储的是0x00,为true时实际存储的是0x01,因此可以作为int整型使用
bool型只分0与非0,0为false,其余的包括负数在内都是true
无符号保留原则
当一个无符号类型与int或更小的整型混合使用时,结果类型是无符号类型,有时可能导致负数丢失符号位,所以避免使用无符号类型(除非必要),以免增加不必要的复杂性,
尤其是不要仅仅因为无符号不存在负值(如年龄,国债等)而用它来表示数量
C++类型检查
与大多数语言一样,C++也是类型决定了能对该对象进行的操作,一条表达式是否合法依赖于其中参与运算的对象的类型,C++是一种静态数据类型语言,它的类型检查发生在编译时,
因此编译器知道程序中每一个变量对应的数据类型,C++定义了几种基本内置类型,如字符,整型,浮点数等,同时也为程序员提供了自定义数据类型的机制
何时会发生隐式类型转换
在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型,
在条件中,非bool值转化为bool值
初始化过程中,初始值转换成变量的类型,在赋值语句中,右侧运算对象转换成左侧运算对象的类型
如果算术关系或运算关系的对象有多种类型,需要换换成同一种类型
函数调用也会发生类型转换
函数返回值
对于无返回值函数,那么在其语句的结尾可以没有显式的return语句,因为在最后一句会隐式的执行return;
对于有返回值函数,那么函数必须保证返回一个与函数类型一致的值,或者可以隐式转换成函数类型的,否则编译器将报错
返回一个值的方式和初始化一个值的方式完全一样,返回的值用于初始化调用点的一个临时变量,该临时变量就是函数调用的结果
浅拷贝
只是将变量内容拷贝给新的指针,但是资源没有拷贝给变量,因此拷贝之后这些变量指向的是同一个内存地址,那么当其中的一个变量销毁并且将其所指向资源进行销毁后,其后它的几个拷贝变量在
销毁时则无法回收资源,产生问题
对数组无效的指引
char name[] = "darla";char c = name [10];//对一个数组无效的指引将导致不明确行为,结果是不可预期的,此例中没有name[10]
返回数组指针
因为数组不能被拷贝,所以函数不能返回数组,不过,函数可以返回数组的指针或引用,返回一个数组指针还是比较繁琐的,最直接的就是使用类型别名
例子:typedef int arrT[10]; //arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT = int [10]; //arrT的等价声明
之后就可以arrT*func(int i); //此时func返回一个指向含有10个整数的数组的指针
比如在主函数中定义了几个数组,int a[] = {1,2,3,4,5},int b[] = {6,7,8,9,10};那么就可以在小函数的末尾使用return &a;或者return &b;
cout的使用
通常与<<结合,cout<< 通过<<操作符把一系列的参数传递给cout对象,然后cout对象按从左到右的顺序把参数打印出来,<<本来是向左移位,但是C++允许操作符重载,
和iostream对象在一起,操作符<<意思就是发送到,例子如下:
cout <<dec<<15<<endl; 表示把数字以十进制打出来,数据本来放在缓冲区,通过endl可以使数据从缓冲区输出,否则就是等待缓冲区满才输出,dec表示是十进制,oct是八进制,hex是十六进制
cin>>a>>b;输入a,b的值,cout可以重定向到文件里,cerr只能输出在显示器上,cerr不经过缓冲区,直接向显示器输出信息,clog默认数据是经过缓冲的
文件的读写
包含<fstream>,如果打算使用cin,cout,那么最好也显示包含<iostream>,尽管<fstream>会自动包含<iostream>,在<iostream>库中的getline()
例子: #include<string> #include<iostream> #include<fstream>
int main(){
ifstream in("FillString.cpp");
string s,line; //string 具有动态性,不必担心string的内存分配,会自动扩展以保存新的输入
while(getline(in, line)) //getline()每次从文件中取一行,string类有许多函数可以对字符串进行查找和操作
s += line + "\n"
cout <<s;
}
名字查找
定义:寻找与所用名字最匹配的声明的过程
1. 首先,在名字所在的块中寻找其他声明语句,只考虑在名字的使用之前出现的声明(确认是否是局部变量)
2. 如果没找到,继续查找外围作用域,继续向其他地方扩散寻找,确认是否是全局变量
3. 如果最后还是没有找到,那么程序会报错
对于定义在类内部的成员函数来说,解析规则有所区别, (1) 首先,编译成员的声明 (2) 直到类全部可见后才编译函数体。 按照这种两阶段的方式处理类可以简化类代码的组织方式,因为成员函数体
直到整个类可见后才会被处理,所以它能使用类中定义的所有名字,相反,如果函数的定义和成员的声明被同时处理,那么我们将不得不在成员函数中只使用那些已经出现的名字,
这种两阶段的处理方式只适用于成员函数中使用的名字,声明中使用的名字,包括返回类型或者参数列表中使用的名字,都必须在使用前确保可见,如果某个成员的声明
使用了类中尚未出现的名字,则编译器将会在定义该类的作用域继续查找
例子:typedef double Money; //当编译器看到balance函数的声明语句时,它将在Account类的范围内寻找对Money的声明,编译器只考虑Account中在使用Money前出现的声明,
string bal; //所以在private中定义的是无效的,因为没找到匹配的成员,所以编译器会在Account的外层继续查找,在这个例子中,编译器会找到typedef声明的Money
class Account { //该类型被作为balance函数的返回类型以及数据成员bal的类型,另一方面,balance函数体在整个类可见后才被处理,因此,该函数的return语句
public: //返回名为bal的成员而非外层作用域的string对象
Money balance(){return bal;}
private:
Money bal;
}
类型名要特殊处理:一般内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过,然而在类中如果成员使用了外层作用域中的某个名字,而该名字代表一种数据类型,
则类不能在之后重新定义该名字,尽管重新定义的类型与外层定义的类型完全一致也不行,
函数的参数列表中的变量最好不要与类成员变量重名,当重名之后如果想使用类成员变量,那么可以通过this->或类作用域来强制访问类成员
关于作者
姓名:张坤武