内联函数
<1>函数调用需要建立栈内存环境,进行参数传递,并产生程序执行转移,这些工作都需要一些时间开销。有些函数使用频率高,但代码却很短。
<2>编译器看到inline后,为该函数创建一段代码,以便在后面每次碰到该函数的调用都用一段代码来替换。
<3>内敛函数可以在一开始仅声明一次
<4>内联函数必须在调用之前被声明或定义。因为内联函数的代码必须在被替换之前已经生成被替换的代码。
内联函数的函数体限制
<1>内联函数中,不能含有复杂的结构控制语句,如switch和while。如果内联函数有这些语句,则编译将该函数视为普通函数那样产生函数调用代码。
<2>递归函数(自己调用自己的函数)是不能被用来做内联函数的。
<3>将大多数inline函数限制在小型的,频繁调用的函数上。
内联函数与宏定义
<1>宏定义可以代替小函数定义,但是有缺陷
<2>宏只是告诉编译器简单的替换代码,不检查参数类型
<3>往往造成语句的实际结果不能代表程序员的意图
<4>宏的作用可以用内敛函数代替:
#define MAX(a,b) ((a)>(b)?(a):(b))
通过一个内联函数可以得到所有宏定义的替换功能和所有可预见的状态以及常规函数的类型检查:
inline int Max(int a,int b)
{
return a>b?a:b;
}
带默认参数的函数
1、调用函数时可以不指定全部参数
2、为可以不指定的参数提供默认值
int add(int x=5, int y=6, int z =3)
int main()
{
add(); //所有三个参数都采用默认值
add(1,5); //第三个参数采用默认值
add(1,2,3); //不适用默认值
}
默认参数的顺序规定
如果一个函数中有多个默认参数,则形参分布中,默认参数应该从右至左逐渐定义。当调用函数时,只能向左匹配参数。
void func(int a =1, int b, int c=3, int d=4); //ERROR
void func(int a,int b=2,int c=3,int d=4); //OK
默认参数函数,调用的方法规定:
void func(int a,int b=2,int c=3,int d=4);
int main()
{
fun(10,15,20,30); //OK: 调用时给出所有参数
func(); //error: 参数a不是默认参数,需要传参
func(12,12); //OK: 参数c和d是默认参数
func(2,15,,20); //error:只能从右到左顺序匹配默认
}
函数重载
定义:两个以上的函数,取相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。
重载的重要性
在C中,即使所做事情相同,每个函数必须有唯一的名字;
int abs(int);
long labs(long);
double fabs(double);
C++求绝对值重载
int abs(int);
long abs(long);
double abs(double);
C++用一种函数命名技术可以准确判断出应该使用哪个abs()函数。
abs(-10); //调用int abs(int);
abs(-1000000); //调用long abs(long);
abs(-12.23); //调用double abs(double).
匹配重载函数的顺序
1、寻找一个严格的匹配,如果找到了,就用那个函数;
2、通过内部转换寻求一个匹配,只要找到了,就用那个函数;
3、通过用户定义的转换,寻求一个匹配,若能查出有唯一的一组转换,就用那个函数。
重载函数使用的说明
1、编译器不以形参名来区分
int add(int x, int y);
int add(int a,int b); //error
2、编译器不以返回值来区分
int add(int x,int y);
void add(int x,int y); //error
函数模板
1、泛型编程:独立于任何特定类型的方式的编写代码。
2、模板是泛型编程的基础。
(1)模板使程序员能够快速建立具有类型安全的类库集合和函数集合,它的实现,方便了大规模的软件开发。
(2)现有的框架(Framework)大都使用了模板。
(3)类模板和函数模板
函数模板的一般定义形式是:
template<类型形式参数表>返回类型 FunctionName(形式参数表)
{
//函数定义体
}
宏与函数模板
1、对于具有各种参数类型,相同个数,相同顺序的同一函数(重载函数),如果用宏定义来写:
define max(a,b) ((a)>(b)?(a):(b))
但是它不能进行类型检查,损害了数据的安全性。
template<typename T>
T max(T a, T b)
{
return a>b?a:b;
}
类和对象
1、C:结构化程序设计
程序=算法+数据结构
2、面向对象程序设计OOP(Object Oriented Programm)
程序=对象+对象+。。。
关键:让每一个对象负责执行一组相关任务。
3、面向对象编程开发范式的特性
(1)万物皆对象,
(2)程序是一组对象彼此之间在发送消息,
(3)每个对象都有自己的内存占用,可以组装成更大的对象
(3)每个对象都有类型,特定类型的所有对象可以接受相同信息,
类
1、类是创建对象的模板和蓝图
2、类是一组类似对象的共同抽象的定义
对象
1、对象是类的实例化结果
2、对象是实实在在的存在,代表现实世界的某一事物
对象三大关键特性
1、行为:对象能干什么
2、状态:对象的属性,行为结果
3、标识:对象的唯一身份
定义一个类的步骤:
1、定义类名;
2、编写类的数据成员代表属性;
3、编写类的方法代表行为;
类的建模是一个抽象和封装的过程
1、抽象:去掉不关注的、次要的信息而保留重要的信息
2、封装:信息打包
(1)具体一点:将数据和行为结合在一个包中,对对象的使用者隐藏数据的实现方式
(2)实现封装的关键:不能让类中的方法直接访问其他类的内部数据,只能通过公开行为方法间接访问
类不仅可以保护数据,还可以提供成员函数操作数据。
在类中定义成员函数:
(1)类中定义的成员函数一般为内联函数,即使没有明确用inline标示。
(2)在C++中,类定义通常在头文件中,因此这些成员函数定义也伴随着进入头文件。
在类之后定义成员函数
(1)C++允许在其他地方定义成员函数。
(2)将类定义和其他成员函数分开,是目前开发程序的通常做法。
(3)我们把类定义(头文件)看成是类的外部接口,类的成员函数定义看成是类的内部实现。
在成员函数中访问成员
(1)成员函数必须用对象来调用。
(2)在成员函数内部,访问数据成员或成员函数无须如此。
#include "tdate.h"
void Tdate::set(int m,int d,int y)
{
month=m; //不能在month前加对象名。
day=d;
year=y;
}
void Tdate::print()
{
cout<<month<<"/"<<day<<"/"<<year<<endl;
}
this指针代表当前对象占用内存空间的地址
void Tdate::set(int m,int d,int y)
{
this->month=m;
this->day=d;
this->year=y;
}
用指针来调用成员函数
(Tdate.cpp中实现了Tdate类,通过包含这个头文件就可以使用Tdate类)
#include "tdate.h"
#include <iostream>
void func(Tdate *pDate)
{
pDate->print(); //pDate是s对象的指针
if(pDate->isLeapYear())
{
cout<<"Leap year\n"
}else{
cout<<"not leap year\n";
}
}
用引用传递来访问成员函数
(1)用对象的引用来调用成员函数,看上去和使用对象自身的简单情况一样
void func(Tdate& refs)
{
refs.Print(); //refs是s对象的别名
if(refs.isLeapYear())
{
cout<<"oh oh\n"
}else{
cout<<"right\n";
}
}
封装
oop三大特性:继承、封装、多态
1、类背后隐藏的思想是数据抽象和封装
2、信息隐藏,隐藏对象的实现细节,不让外部直接访问到。
(1)将数据成员和成员函数一起包装到一个单元中,单元以类的形式实现
(2)将数据成员和成员函数包装进类中,加上具体实现的隐藏,共同被称作封装,其结果是一个带有特征和行为的数据类型。
信息隐藏是OPP最重要的功能之一,也是使用访问修饰符的原因。private protected public
信息隐藏的原因包括:
(1)对模块的任何实现细节所作的更改不会使用该模块的代码;
(2)防止用户意外的修改数据;
(3)使模块易于使用和维护。
1、除非必须公开底层的实现细节,否则应该将所有字段指定为private加以封装;
2、使数据成员私有,控制数据访问限制,增强了类的可维护性;
3、隐藏方法实现细节(方法体),向外部提供公开接口(方法头),以供安全调用。
构造函数与析构函数
编码规范:声明变量赋初值。
int l =0;
int *p =NULL;
malloc申请到的内存区域使用memset进行设置
对象在定义时进行初始化
(1)完成对象的初始化的函数是构造函数
(2)类的对象的初始化只能由类的成员函数来进行
(3)建立对象的同时,自动调用构造函数
(4)类的对象的定义涉及到一个类名和一个对象名
(5)由于类的唯一性和对象的多样性,用类名而不是对象名来作为构造函数名是比较合适的。
(6)默认构造函数
c++规定,每个类必须有一个构造函数
构造函数
(1)构造函数可以有多个
(2)构造函数可以重载
(3)构造函数用于隐式类型转换(编译器将传参转为所需类型的一种特殊情况)
explicit 禁止隐式类型转换,防止奇怪的问题
析构函数
(1)一个类可能在构造函数里分配资源,这些资源需要在对象不复存在以前被释放。
(2)析构函数也是特殊类型的成员,它没有返回类型,没有参数,不能随意调用,也没有重载。只是在类对象声明结束的时候。由系统自动调用。
字符串对象的初始化方法
初始化方法
1、string s1; 默认构造函数,s1为空字符串
2、string s2(s1); 将s2初始化为s1的一个副本
3、string s3("value"); 将s3初始化为字符串的副本
4、string s4(n,'c'); 将字符串初始化为字符c的n个副本
string操作
1、s.empty(); 如果字符串为空,返回true,否则返回false
2、s.size(); 返回字符串中字符的个数
3、s[n]; 返回字符串中的第n个字符,下表从0开始
4、s1+s2; 将s1和s2连接成为一个新的字符串,返回新生成的字符串。
5、s1=s2 将s1的内容替换为s2的内容
6、v1==v2 比较v1和v2的内容,相等则返回true,否则返回false
7、!=,<,<=,>,>= 保持这些操作字符惯有的含义
static类成员
Exp:假设有一个具有火星人和其他人物的视频游戏。每个火星人都很勇敢,而且当火星人注意到至少存在五个火星人时,它总是袭击其他空间的生物。如果少于五个人,则每个火星人将非常胆小。所以,每个火星人都需要知道现在游戏中有多少火星人。
C++提供static数据成员
其中martainCount是所有火星人共有的。
静态成员的使用
class Martain
{
public:
Martain();
~Martain();
void fight();
void hide();
static int getCount(); //定义类的静态成员函数
private:
static int martainCount; //定义类的静态成员变量
//类的层次所共有的,不是属于某个单独的对象
};
Martain::Martain(){
martainCount++; //构造函数,每增加一个火星人,个数增一
}
Martain::~Martain(){
martainCount--; //析构函数,每减少一个火星人,个数减一
}
int Martain::getCount(){
return martainCount; //静态成员函数,获取当前的火星人个数
}
int Martain::martainCount = 0; //火星人个数初始化。静态数据成员在类外初始化。
#ifndef #define 中的头文件名为什么是__xxx_h
在b.h头文件中用
#ifndef _B_H_
#define _B_H_
//...头文件体
#endif
包含住头文件体的内容,在第一次包含B_H时,B_H未定义,此时定义B_H,在第二次包含时,B_H已定义#ifndef B_H为false,头文件体
#define _B_H_
//...头文件体
#endif
之间的内容会被丢弃。因此避免了重复包含头文件体的内容而重复定义。
#ifndef _MARTAIN_H_
#define _MARTAIN_H_
class Martain
{
public:
Martain();
~Martain();
void fight();
void hide();
static int getCount();
private:
static int martainCount; //define class static member
};
#endif
#include "martain.h"
int Martain::martainCount = 0;
Martain::Martain(){
martainCount++;
}
Martain::~Martain(){
martainCount--;
}
int Martain::getCount(){
return martainCount;
}
void Martain::fight(){
}
void Martain::hide(){
}
#include <iostream>
using namespace std;
#include "martain.h"
void func()
{
Martain c;
int count = Martain::getCount();
cout<<"count = "<<count<<endl;
}
int main(int argc, char* argv[])
{
int count = Martain::getCount();
cout<<"count = "<<count<<endl;
Martain a;
count = Martain::getCount();
cout<<"count = "<<count<<endl;
Martain b;
count = Martain::getCount();
cout<<"count = "<<count<<endl;
func(); //在func()中,构造函数使个数加一,当函数结束调用时,析构函数使个数减一,所以再次统计会计数减一
count = Martain::getCount();
cout<<"count = "<<count<<endl;
}
static数据成员
(1)在static数据成员中不能使用this指针
(2)即使没有实例化的对象,static数据成员和成员函数仍然可以使用
(3)static成员的名字在类的作用域中,因此可以避免与其他类的成员或者全局对象名字冲突;
(4)可以实施封装,static成员可以是私有成员,而全局对象不可以
(5)通过阅读代码容易看出static成员是与特定类关联的,清晰的现实程序员的意图。
动态内存分配
(1)C语言的动态内存分配
malloc/free函数
(2)内存区域
(3)C++的运算符new、delete
01、在堆上生成对象,需要自动调用构造函数
02、在堆上生成的对象,在释放时需要自动调用析构函数
03、new/delete,malloc/free需要配对使用
04、new[]/delte[]生成和释放对象数组
05、new/delete是运算符,可以重载;malloc/free是函数调用,不可以重载;
#include <iostream>
using namespace std;
#include <stdlib.h>
class Test{
public:
Test(int val=0):m_val(val)
{
cout<<"Test"<<endl;
}
~Test(){
cout<<"~Test"<<endl;
}
private:
int m_val;
};
int main()
{
{
Test a;
}
cout<<"end of }"<<endl;
//在括号开始时,调用构造函数,结束时调用析构函数
Test* pVal = new Test;
delete pVal;
pVal = NULL; //释放内存时,指针置为NULL
int *p = (int *)malloc(sizeof(int));
free(p);
p=NULL;
Test *pArray = new Test[2]; //new一个数组指针
delete[] pArray;
pVal =new Test(3); //初始化一个类
delete pVal;
return 0;
}
BSS段
(1)代码存放在代码区,数据则根据数据类型的不同存放在不同的区域中
(2)Bss段存放在没有初始化或者初始化为0的全局变量
(3)大多数的操作系统在加载程序的时候会把bss全局变量清零。为了保证程序的可移植性,最好手工把变量初始化为0.
int bcc_array[1024*1024];
int main(int argc, int *argv[])
{
return 0;
}
data段存放初始化为非0的代码段
int bcc_array[1024*1024]={1};
int main(int argc, int *argv[])
{
return 0;
}
仅仅将初始值改为非0后,文件size就变大
代码规范:尽量不要声明全局变量,全局变量在整个程序运行过程中都会存在
静态变量
1、静态变量在第一次进入作用域时被初始化,以后不必再初始化。
2、静态成员变量在类之间共享数据,也是放在全局/静态数据区中。并且只有一份拷贝
#include <stdlib.h>
#include <stdio.h>
int i=0;
class A
{
public:
int val;
static int nCount;
A(){++nCount;};
~A(){--nCount;};
};
int A::nCount=0;
int main(int argc ,char* argv[])
{
A a;
A b;
printf("Number of A is %d====>\n\n",A::nCount);
printf("no static variable: 0x%x\n",&a.val);//栈上
printf("no static variable: 0x%x\n",&b.val);//栈上
printf("static variable: 0x%x\n",&A::nCount);//全局数据区
printf("ststic class member:0x%x\n",&a.nCount);//全局数据区
printf("ststic class member:0x%x\n",&b.nCount);//全局数据区
printf("global member:0x%x\n",&i); //全局数据区
return 0;
}
常量数据区
1、rodata存放常量数据
2、常量不一定放在rodata中,有些立即数直接和指令编码在一起,放在text中。
3、字符串常量,编译器会去掉重复的字符串,保证只有一个副本。
4、常量是不能修改的
5、字符串会被编译器自动放到dodata中,加const关键字修饰的全局变量也放在rodata中。
(1)栈中存储自动变量或者局部变量,以及传递的参数等;
(2)在一个函数内部定义了一个变量,或者向函数传递参数时,这些变量和参数存储在栈上,当变量退出这些变量的作用域时,这些栈上的存储单元会被释放。
(3)堆是用户程序控制的存储区,存储动态产生的数据;
(4)当用malloc/new来申请一块内存或者创建一个对象时,申请的内存在堆上分配,需要记录得到的地址,并且在不要的时候释放这些内存。
(5)栈一般很小,满足不了程序逻辑的要求。
(6)对象的生命周期是指对象从创建到被销毁的过程,创建对象时要占用一定的内存。因此整个程序占用的内存随着对象的创建和销毁动态的发生变化。
(7)变量的作用域决定了对象的生命周期。
(8)全局对象在main之前被创建,main退出后被销毁。
(9)静态对象和全局对象类似,第一次进入作用域被创建,但是程序开始时,内存已经分配好了。
(10)作用域由{}决定,并不一定是整个函数
#include <stdio.h>
#include <stdlib.h>
class A{
public:
A() {printf("A created\n");}
~A() {printf("A distroyed\n");}
};
class B{
public:
B() {printf("B created\n");}
~B() {printf("B distroyed\n");}
};
A globalA;
B globalB;
int foo(void)
{
printf("\nfoo()----------->\n");
A localA;
static B localB;
printf("foo()<------------->\n");
return 0;
}
int main(int argc,char *argv[])
{
printf("main()--------------------->\n");
foo();
foo();
printf("main()<------------------------\n");
return 0;
}
说明:
全局globalA,A的构造函数被调用
全局globalB,B的构造函数被调用
第一次调用foo()
局部变量localA,A的构造函数被调用
局部静态变量localB,B的构造函数被调用
局部变量localA,A的析构函数被调用
第二次调用foo()
局部变量localA,A的构造函数被调用
局部变量localA,A的析构函数被调用
退出main函数
局部静态变量localB,B的析构函数被调用
全局globalB,B的析构函数被调用
全局globalA,A的析构函数被调用
(1)通过new创建对象,但容易造成内存泄漏。通过new创建的对象一直存在,直到被delete销毁。
(2)隐藏在中间的临时变量的创建和销毁,生命周期很短,容易造成问题。
------拷贝构造函数
#include <stdio.h>
class A
{
public:
A(){ printf("A created\n");}
~A(){printf("A destoryed\n");}
A(const A& a) {printf("A created with a copy\n");}
};
A *foo(A a)
{
printf("foo-------------->\n");
A *p = new A();
printf("foo<--------------------\n");
return p;
}
A *boo(const A& a)
{
A *p = new A();
printf("boo<------------------\n");
return p;
}
int main(int argc, char *argv[])
{
printf("main()----------------->\n");
A a;
A *p = foo(a);
delete p;
p =NULL;
p =boo(a);
delete p;
p= NULL;
printf("main()<----------------\n");
return 0;
}
说明:
全局变量a,A的构造函数
foo中a,A的拷贝构造函数
foo中new,A的构造函数
foo中a,A的析构函数
delete ,A的析构函数
boo中new,A的构造函数
delete ,A的构造函数
全局变量a,析构函数