C++概述
1. const与指针
-
const可以与指针一起使用, 组合情况可归纳为三种:
-
指向常量的指针: 一个指向常量的指针变量
const char* name = "chen";
- 这个语句的含义为: 声明一个名为name的指针变量, 它指向一个字符型常量, 初始化name为指向字符串"chen"
- 所以, 不能改变name数组内元素的值, 但可以更改name所引用的字符串
-
常指针: 把指针所指的地址,而不是它指向的对象声明为常量
char* const name = "chen";
- 这个语句的含义为: 声明一个名为name的指针变量, 该指针是指向字符型数据的常指针
- 常指针不能改变其引用对象, 即不能
name = "zhang"
-
指向常量的常指针: 这个指针本身不能改变,它所指向的地址中数据也不能改变
const char* const name = "chen";
- 这个语句的含义是:声明了一个名为name的指针变量,它是一个指向字符型常量的常指针, 用"chen"的地址初始化该指针
- 指向常量的常指针, 不能改变name数组内元素的值, 也不能改变其引用的字符串
-
-
关于const的说明:
- 如果用const定义的是一个整型常量,关键字int可以省略
- 常量一旦被建立,在程序的任何地方都不能再更改
- 与#define定义的常量有所不同,const定义的常量可以有自己的数据类型,这样C++的编译程序可以进行更加严格的类型检查,具有良好的编译时的检测性
- 函数的形参也可以用consti说明,用于保证形参在该函数内部不被改动,即在函数中对这些参数只许读,而不许写。
- 例如:希望通过函数i_Max求出整型数组a[200]中的最大值,函数原型是:
int i_Max(const int *arr)
调用的格式可以是:i_Max(arr)
, 且调用后,能够找出数组中的最大元素,但对200个数组元素的操作只许读,而不许写这样做的目的是确保原数组中的数据不被破坏,即在函数中对数组元素的操作只许读而不许写
- 例如:希望通过函数i_Max求出整型数组a[200]中的最大值,函数原型是:
2. 内联函数
-
简要介绍: 在函数说明之前, 冠以关键字"inline", 该函数就被声明为内联函数, 又称为内置函数
-
内联函数与普通函数的区别:
- 每当程序中出现对内联函数的调用时, C++编译器使用函数体中的代码替代函数调用表达式, 这样能加快代码的执行, 减少调用开销
-
对内联函数的说明:
-
内联函数在第1次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码
-
在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等
-
若内联函数较长,且调用太频繁时,程序将加长很多。通常只有规模很小(一般为1~5条语句)而使用频繁的函数才定义为内联函数,这样可大大提高运行速度
-
C++的内联函数具有与C中的宏定义#define相同的作用和相似的机理,但消除了#define的不安全因素
-
3. 带有默认参数的函数
-
C++在说明函数原型时,可为参数指定默认参数值以后调用此函数时,若省略其中某一参数, C++自动地以默认值作为相应参数的值
-
函数原型说明为:
int special(int x=5, float y=5.3);
- 以下函数调用都是允许的:
special();
special(25); //x=25, y=5.3
special(100, 79.8); //x=100, y=79.8
- 以下函数调用都是允许的:
-
对带有默认参数的函数的说明:
-
在声明函数时,所有指定默认值的参数都必须出现在不指定默认值的参数的右边,否则出错。
- 例如:
int fun(int i,int j=5,int k);
可改为:int fun(int i,int k,int j=5);
- 例如:
-
在函数调用时,若某个参数省略,则其后的参数皆应省略而采用默认值。不允许某个参数省略后,再给其后的参数指定参数值
- 例如不允许出现以下调用:
special(,21,5);
- 例如不允许出现以下调用:
-
在函数原型中默认参数可以不包含参数的名字
-
4. 重载函数
- 关于重载函数的说明:
-
若两个函数的参数个数和参数类型都相同,而只有返回值类型不同,侧不允许重载。
-
函数的重载与带默认值的函数一起使用时,有可能引起二义性
- 例如:
void Drawcircle(int r=0,int x=0,int y=0);
void Drawcircle(int r);
当执行以下的函数调用时:Drawcircle(20);
编译无法确定使用哪一个函数。
- 例如:
-
在函数调用时,如果给出的实参和形参类型不相符,C++的编译器会自动地做类型转换工作。如果转换成功,则程序继续执行。但是,在这种情况下,有可能产生不可识别的错误。
- 例如,有两个函数的原型如下:
void f_a(int x);
void f_a(long x);
如果我们用下面的数据去调用,就会出现不可识别的错误:
*int c = f_a(5.56);
--> 编译器无法确定将5.56转换成int还是long类型
- 例如,有两个函数的原型如下:
-
5. new与delete
-
格式: 指针变量名 = new 类型 delete 指针变量名
- 例:
int *ptr; ptr = new int; delete ptr;
- 例:
-
关于new与delete的说明:
-
()使用new分配的空间,使用结束后应该也只能用delete显式地释放,否则这部分空间将不能回收而变成死空间。
-
new可在为简单变量分配内存空间的同时,进行初始化。其基本形式为:指针变量名=new 类型(初值);
- 例:
int* p = new int(99);
赋初值99 改代码也等价于int* p; p=new int; *p=99;
- 例:
-
使用new可以为数组动态分配内存空间,这时需要在类型名后面缀上数组大小。
char* p=new char[10];
int* op=new int[5][4];
-
new可在为简单变量分配内存空间的同时,进行初始化。但不能为数组分配内存空间的同时,进行初始化。
-
释放动态分配的数组存储区时,可使用如下的delete格式:
delete []p;
-
使用new动态分配内存时, 如果没有足够的内存满足要求, new将返回空指针NULL
- NULL为空指针常数, 通常是0
-
6. 引用
1. 引用概述
- 概念: 建立引用是为变量另起一个名字, 变量的引用通常认为是变量的别名
- 声明一个引用的格式如下: 类型 &引用名 = 已定义的变量名
- 例如:
int i = 5; int &j = i;
- 这里,声明了一个整数类型的引用j,用整型变量i对它进行初始化,这时就可看做是变量的i引用,即是变量i的别名。也就是说,变量i和引用j占用内存的同一位置。当i变化时,j也随之变化, 反之亦然。
2. 关于引用的说明
-
对变量声明一个引用,编译系统不给它单独分配存储单元,i和j都代表同一变量单元。
-
在声明一个引用时,必须立即对它进行初始化,即声明它代表哪一个变量。不能声明完成后再赋值。
- 例如下述声明是错误的:
int i; int&j; //错误 j=i;
应该是:int i; int &j=i;
- 例如下述声明是错误的:
-
为引用提供的初始值,可以是一个变量或另一个引用。
- 例如:
int i=5; //定义整型变量i
int &j1=i, //声明j1是整型变量的引用(别名)
int &j2=j1; //声明j2是整型引用j1的引用(别名)
这样定义后,变量i有两个别名j1和j2。
- 例如:
-
指针是通过地址间接访问某个变量,需要书写间接运算符 " * " 引用是通过别名直接访问某个变量。每次使用引用时,可以不用书写间接运算符“ * ”,因而使用引用可以简化程序。
-
引用在初始化后不能再被重新声明为另一个变量的引用(别名)
-
尽管引用运算符“&”与地址操作符“&”使用相同的符号,但是它们是不一样的。引用运算符“&”仅在声明引用时使用。其他场合使用的“&”都是地址操作符
- 例如:
int j=5;
int&i=j; //声明引用i,“&”为引用运算符
i=123; //使用引用,不带引用运算符
int*pi=&i; //在此,“&”为地址操作符
cout<<π //在此,“&”为地址操作符
- 例如:
类和对象
1. 类与对象的定义
-
成员函数的定义: 成员的定义通常采用下3种方式:
-
在类声明中只给出成员函数的原型, 而将成员函数的顶部一放在类外部
- 成员函数在外定义的一般形式是: 返回值类型 类名 :: 成员函数名(参数表) { 函数体 }
- 例: Student :: 成员函数名(参数名)
-
将成员函数直接定义在类内部
-
在类声明中只给出成员函数的原型,而成员函数的定义放在类外部。但在类外部成员函数的定义前冠以关键字“inline”,使它起到内联函数的作用。
- 在类外部的案例:
inline void Student :: input(int num1, float num2) { xxxx }
- 注意: 使用inline定义内联函数时,可以在声明函数原型和定义函数时同时写inline,也可以在其中一处声明inline,效果是相同的都能按内联函数处理。但是,必须将类的声明和内联成员函数的定义都放在同一个文件(或同一个头文件)中,否测编译时无法进行代码置换。
- 在类外部的案例:
-
.
-
对象的定义可以用以下两种方法:
- 在声明类的同时,直接定义对象,即在声明类的右花括号”}"后,直接写出属于该类的对象名表。 例:
class Student { xxxxx }stu1, stu2
- 声明类之后, 在使用时再定义对象; 例:
class Student { xxxx }; Student stu1, stu2;
- 在声明类的同时,直接定义对象,即在声明类的右花括号”}"后,直接写出属于该类的对象名表。 例:
-
关于对类的说明:
- 类声明中的private和public两个关键字可以按任意顺序出现任意次。但是,如果把所有的私有成员和公有成员归类放在一起,程序将更加清晰。
- 除了private和public之外,类中的成员还可以用另一个关键字protected来说明。被protected说明的成员称为保护成员,它不能被外部函数使用,但可以通过其他方法使用它。
- 不能在类声明中给数据成员赋初值,C++规定,只有在对象定义后才能对数据成员赋初值
2. 对象中成员的访问
-
不论是数据成员,还是成员函数,只要是公有的成员,在类的外部可以通过类的对象进行访问。访问对象中的成员通常有以下三种方法:
-
通过对象名和对象选择符访问对象中的成员
- 其中 " . " 是对象选择符,简称点运算符
-
通过指向对象的指针访问对象中的成员
-
例如:
class Date { public: int year; ... } int main() { Date date, *p; p = date; cout << p -> year; }
-
-
通过对象的引用访问对象中的成员
- 如果为一个对象定义了一个引用,也就是为这个对象起了个别名
-
3. 构造函数与析构函数
1. 构造函数
-
对象的初始化:
-
定义对象时,可以对数据成员直接赋值,这种方法要求类中所有的成员,都是公有的。但是,如果类中包含私有的或保护的成员时,就不能用这种方法进行初始化。
class Complex { public: double i; double j; }; Complex com = {1.1, 2.2};
-
用普通成员函数对对象进行初始化,类似于 JavaBean中的Getter&Setter方法
-
用构造函数对对象进行初始化
-
-
定义对象的的一般语法为:
Complex *p = new Complex(xxx, xxx);
- 指针p指向new出来的Complex对象的地址
- 对象名为*p
- 此时应使用
->
来访问对象的成员
-
构造函数的说明:与普通的成员函数一样,构造函数的函数体可写在类中,也可写在类外
Complex::Complex(double r,double i) { real=r;imag=i; }
-
成员初始化列表对数据初始化
class Ex { private: int i; float j; public: A(int ch, float jh):i(ch), j(jh) { } };
- 说明:在C++中某些类型的成员是不允许在构造函数中用赋值语句直接赋值的。例如,对于用const修饰的数据成员,或是引用类型的数据成员,是不允许用赋值语句直接赋值的。因此,只能用成员初始化列表对其进行初始化
-
拷贝构造函数:
class Point { int x,y; public: Point(int a,int b) //构造函数 { x=a;y=b} Point(const Point& p) //拷贝构造函数 { x=2*p.x;y=2*p.y;} }; int main() { Point p1(10,20); Point p2(p1); //const Point& p=p1 return 0; //p1.x=10,p1.y=20; p2.x=20,p2.y=40 }
- 注意:如果没有编写自定义的拷贝构造函数,C++系统会自动地生成的一个默认的拷贝构造函数。在建立一个新对象时,这种默认的拷贝构造函数能够将一个已存在的对象的数据成员一比一地复制给新对象。
2. 析构函数
-
析构函数也是一种特殊的成员函数它执行与构造函数相反的操作,通常用于执行一些清理任务 主要有:
- 释放分配给对象的内存空间
- 其他指定的任务
-
代码展示:
class Student{ //学生类 private: int number; float score; public: Student(int number1,float score1); //构造函数的说明 ~Student(){ //析构函数 cout<<“destructing”; } }; Student::Student(int number1,float score1) { number=number1; score=score1; }
-
关于虚构函数的使用:
- 析构函数与类名相同,但它前面必须加一个波浪号(~)
- 析构函数没有参数,也没有返回值
- 当撤消对象时,编译系统会自动地调用析构函数
- 析构函数没有参数,因此它不能被重载。一个类可以有多个构造函数, 但是只能有一个析构函数
-
关于虚构函数的说明:
- 每个类必须有一个析构函数。若没有显式地为一个类定义析构函数,编译系统会自动地生成一个缺省的析构函数
- 例如:
Student∷~Student() {}
- 当撤消对象时,这个缺省的析构函数将释放分配给对象的内存空间。
- 在以下情况,对象将被撤消,编译系统也会自动地调用析构函数:
- 主函数main()运行结束
- 如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用
- 若一个对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数
4. 对象数组和对象指针
1. 对象数组
- 格式:
Class class[3];
- 共建立了四个对象,即每一个数组元素是一个对象(即ob[0]、ob[1]、ob[2]、 ob[3]),共调用了4次构造函数
- 注意:如果类中含有带有一个参数的构造函数,则定义对象数组时,可通过初始值表进行赋值
Class class[3] = {1, 2, 3};
- 当各元素对象的初值要求为不同的值时,需要定义带参数(无默认值)的构造函数
- 当各个元素的初始值为相同的值时,可以在类中定义不带参数的构造函数或带有默认参数值的构造函数
2. 对象指针
-
对象指针就是用于存放对象地址的指针变量
-
语法格式:
class *p;
-
案例:
#include<iostream> using namespace std; class exe { int x; public: void set(int a){ x=a; } void show(){ cout<<x<<endl; } }; int main() { exe ob[2]; //定义对象数组ob[2] exe *p; //定义对象指针变量p ob[0].set(10); ob[1].set(20); p=ob; //把对象数组的第一个元素的地址赋给对象指针变量p p->show(); //等价于:ob[0].show(); p++; //指向下一个索引 p->show(); //等价于:ob[1].show(); return 0; }
5. this指针
类似于Java的this,不过访问的时候不能使用点运算符,只能使用->
运算符
6. String类
-
C++支持两种类型的字符串:
- 第一种是C语言中介绍过的包括一个结束符‘\0’的字符数组,标准库函数提供了一组对其进行操作的函数,可以完成许多常用的字符串操作,如字符串复制函数strcpy、字符串连接函数strcat、求字符串长度函数strlen等。C++中仍保留了这种格式字符串。
- 第二种是在C++的标准类库中,声明了一种更方便的字符串类型,即字符串类string, string类提供了对字符串进行处理所需要的操作。
-
使用string类必须在程序的开始包括头文件string:
#include <string>
- String初始化对象:
string str1,str2;
string str3(“China”);
string str4=“China”;
- String初始化对象:
-
Java中对String类的运算操作基本上都有,不过注意的是C++的
==
操作实际上运算符就是Java中的equals()
方法
7. 向函数传递对象
-
使用对象作为函数参数
-
传值调用,函数中对形参对象成员的任何修改均不影响调用该函数的实参对象本身
-
void swap(op ob) { int temp; temp=ob.x; ob.x=ob.y; ob.y=temp; } obj.swap(obj);
-
-
使用对象指针作为函数参数
-
传址调用,函数中对形参对象成员的任何修改均影响调用该函数的实参对象本身
-
void swap(op *ob) { int temp;temp=ob->x;ob->x=ob->y;ob->y=temp; } obj.swap(&obj);
-
-
适用对象引用作为函数参数
-
传址调用,函数中对形参对象成员的任何修改均影响调用该函数的实参对象本身
-
void swap(op &ob) { int temp; temp=ob.x; ob.x=ob.y; ob.y=temp; } obj.swap(obj);
-
可见,使用对象引用作为函数参数不但具有用对象指针作函数参数的优点,而且用对象引用作函数参数将更简单、更直接。
-
8. static关键字
- 为了实现同一个类的多个对象之间的数据共享,C++提出了静态数据成员的概念。
- 与一般的数据成员不同,无论建立多少个类的对象,都只有一个静态数据成员的拷贝。从而实现了同一个类的不同对象之间的数据共享
1. 静态成员变量
-
关于静态成员变量的说明:
-
静态数据成员属于类(准确地说,是属于类中一个对象集合),而不像普通数据成员那样属于某一对象,因此可以使用“类名∷”访问静态的数据成员。
- 例:
Student::count
- 例:
-
静态数据成员初始化应在类外单独进行,而且应在定义对象之前进行。一般在主函数main之前,类声明之后的特殊地带为它提供定义和初始化
- 例:
int Student::count = 0;
- 例:
-
公有静态数据成员 可以在对象定义之前被访问
- 例:
Student::count = 1;
- 例:
-
对象定义后,公有的静态数据成员,也可以通过对象进行访问。
-
一般格式:对象. 静态数据成员名 or 对象指针->静态数据成员名
-
例如:
cout<<Stu1.count;
-
-
私有的静态数据成员不能被外界直接访问。必须通过公有的成员函数间接访问
-
C++支持静态数据成员的一个主要原因是可以不必使用全局变量
-
2. 静态成员函数
-
在类定义中,前面有static说明的成员函数称为静态成员函数
-
静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象
-
格式:static 返回类型 静态成员函数名(参数表);
-
调用公有静态成员函数的一般格式有如下几种:
- 类名::静态成员函数名(实参表)
- 对象.静态成员函数名(实参表)
- 对象指针->静态成员函数名(实参表)
-
一般而言,在静态成员函数中访问的是静态数据成员
-
关于对静态成员方法的说明:
-
一般情况下,静态函数成员主要用来访问静态数据成员。当它与静态数据成员一起使用时,达到了对同一个类中对象之间共享数据的目的
-
静态成员函数一般为公有的,私有静态成员函数不能被类外部的函数和对象访问
-
使用静态成员函数的一个原因是,可以在建立任何对象之前调用静态成员函数,以处理静态数据成员,这是普通成员函数不能实现的功能
-
编译系统将静态成员函数限定为内部连接,也就是说,与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突,维护了该函数使用的安全性,这是使用静态成员函数的另一个原因
-
静态成员函数是类的一部分 。如果要在类外调用公有的静态成员函数,使用如下格式较好:
类名∷静态成员函数名()
-
一般而言,静态成员函数不访问类中的非静态成员,非静态成员由普通成员函数访问
-
若静态成员函数需要访问非静态成员,静态成员函数只能通过对象名(对象指针或引用)访问该对象的非静态成员,例:
static void display(Small_cat& w) { cout << "这只小猫的重量是" << w.weight << "千克\n"; } //在主程序main()中: Small_cat::display(w1); //显示第一只小猫的重量
-
9. 友元函数
-
友元函数不是当前类的成员函数,但它可以访问该类所有的成员,包括私有成员、保护成员和公有成员
-
在类中声明友元函数时,需在其函数名前加上关键字friend
-
友元函数既可以是非成员函数,也可以是另一个类的成员函数
-
关于友元函数的说明:
-
友元函数的声明可以放在公有部分,也可以放在保护部分和私有部分
-
友元函数不是成员函数。因此,在类的外部定义友元函数时,不必像成员函数那样,在函数名前加上 “类名∷”
-
因为友元函数不是类的成员,所以它不能直接调用对象成员,它必须通过对象(对象指针或对象引用)作为入口参数,来调用该对象的成员。
-
一个函数可以是多个类的友元函数。当一个函数需要访问多个类时,友元函数非常有用
//定义函数prdata()是类girl和类boy的友元函数 class girl { friend void prdata( const girl plg, const boy plb); }; class boy { friend void prdata(const girl plg, const boy plb); }
-
-
注意:尽量不要使用友元函数
10. 类的组合
-
在一个类中内嵌另一个类的对象作为数据成员,称为类的组合。该内嵌对象称为对象成员,也称为子对象
-
类B中含有对象成员a后,如何完成对象成员a的初始化工作? 类B的构造函数如何定义?
class A { ... }; class B { A a; // a为对象成员 public: B(参数表0):a(参数表1) { 类B的构造函数体 } };
11. 常引用、常对象和常对象成员
1. 常引用
-
如果在说明引用时用const修饰,则被说明的引用为常引用
-
例如:
int a=5; const int& b=a;
-
b是一个常引用,它不允许更改
-
-
在实际应用中,用常引用做形参,能够避免对实参的更改,保证了数据的安全
2. 常对象
- 用const修饰说明的对象为常对象。常对象的数据成员值在对象的整个生存期内不能被改变
- 常对象的说明形式如下:
类名 const 对象名[(参数表)]; or const 类名 对象名[(参数表)];
- 常对象的说明形式如下:
- 规定:在定义对象时必须进行初始化,而且不能被更新
- 当一个对象被定义常对象后需要注意的问题:
- 不允许间接地更改常对象的数据成员,例:不允许使用getset方法
- 不允许直接更改常对象的数据成员
- 不允许常对象调用普通的成员函数
- 常对象只能调用它的常成员函数,而不能调用普通的成员函数
- 常成员函数是指由const修饰符修饰的成员函数,在常成员函数中不得修改类中的任何数据成员的值
3. 常对象成员
-
使用const说明的数据成员称为常数据成员
-
如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而不能采用在函数中直接赋值的方法
class Date { private: const int year; const int month; const int day; public: Date(int y,int m,int d); void showDate(); }; Date::Date(int y,int m,int d) :year(y),month(m),day(d) { }
-
常数据成员只能通过初始化列表对该数据成员进行初始化
4. 常成员函数
-
在类中用关键字const说明的函数为常成员函数,常成员函数的说明格式如下:
类型说明符 函数名(参数表)const
-
const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const
- 在调用时不必加const
-
如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,而不能调用普通的成员函数
-
案例
#include <iostream>
using namespace std;
class Date {
private:
int year, month, day;
public:
Date(int y,int m,int d);
void showDate();
void showDate() const;
};
Date::Date(int y,int m,int d):year(y),month(m),day(d) {}
void Date::showDate() {
cout<<"ShowDate1:"<<endl;
cout<<year<<"."<<month<<"."<<day<<endl;
}
void Date::showDate() const {
cout<<"ShowDate2:"<<endl;
cout<<year<<"."<<month<<"."<<day<<endl;
}
int main() {
Date date1(1998,4,28); //定义普通对象Date1
date1.showDate(); //调用普通函数showDate
const Date date2(2002,11,14); //定义常对象Date2
date2.showDate(); //调用常成员函数showDate
return 0;
}
- 普通成员函数和常成员函数可访问成员的区别
普通成员函数 | 常成员函数 | |
---|---|---|
普通数据成员 | 可以访问,也可以改变值 | 可以访问,但不可以改变值 |
常数据成员 | 可以访问,但不可以改变值 | 可以访问,但不可以改变值 |
常对象的数据成员 | 不允许访问和改变值 | 可以访问,但不可以改变值 |