C++:类、函数、指针、构造函数
总阅读次
c++零碎笔记:
类、函数、引用、指针、const(指针)、auto、 constexpr变量、decltype
c++ Primer第五版
类
类是C++中面向对象编程OOP的核心概念之一,是用户定义的一种数据类型
。
其类型名就是类名
类的作者决定了类类型对象可以使用的所有操作。
类和基本类却别在于,类类型同时包含了对**
数据进行操作的函数
**
类的基本思想是数据抽象
(data abstraction)和封装
(encapsulation)。
数据抽象是一种依赖于接口(interface)和实现的编程技术。
接口
:包括用户所能执行的操作;类的
实现
包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装
实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节
,也就是说类的用户只能使用接口而无法访问实现部分。
要定义类描述的是数据格式及其用法,而对象则应对于对象实例或实例变量。
函数
函数用于创建模块。C++中函数分为2中:1. 有返回至。2.没返回值
在使用函数之前,C++编译器必须知道函数的参数类型和返回值类型。
C++程序应当为程序中每个函数提供函数原型。
- 默认实参
某些函数有这样一种形参,在函数的很多次调用中他们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参(default argument)。
引用
引用即别名:它只是为一个已经存在的对象所起的另一个名字。
引用类型引另外一种类型
定义了一个引用之后,对其进行的所有的操作都是在于之绑定的对象上进行的:
一旦定义了引用,就无法令其再绑定
到另外的对象,之后每次使用这个引用都是访问它最初绑定那个对象
1 |
|
- const的引用
把引用绑定到const对象上,就像绑定到其它对象上,我们称之为对象的引用(reference to const)
与普通引用不一样的是,对常量的引用不能被用作修改它所绑定的对象
1 |
|
指针
C++中专门用来存放单元地址的变量就是指针类型。
指针与引用类似,指针也实现了对其他对象的简介访问。但不同的指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期它可以先手指向不同的对象。
- 指针运算符
1. &
取地址
是单目运算,具有右结合性。
取值运算符用于指针类型变量,其作用是取该指针所指向内存单元中存储的内容。
2. *
取值运算符
取指针所指向内存中存储的内容
需要说明的是&
和*
出现在声明语句中其含义是不同的,它们作为单目运算和双目运算含义也是不同的,注意下列语句
1 | int &ra; // &作为定义一个int型引用ra的说明符 |
- 指针的赋值
定于了一个指针,只是得到了一个用于存储地址的指针变量,但是变量没有确定的值,其中地址的值是一个随机数。所以定义指针后必须赋值,才可以引用。
指针要么是在初始化的时候进行赋值,如:char *p = "hello world!";
要么在之后将指针指向一个已经初始化的内存空间,因为只定义而未初始化的指针不会指向任何内存空间,是不能对指针指向的地方进行赋值的。
这就是为什么如果只定义了指针,而为初始化时要使用Xalloc或者new来申请空间。
指针的初始化有2种方法
**1.**定义指针的同进行初始化赋值
1 | <数据类型> *<指针变量名>=初始地址 |
**2.**在定义后,单独使用赋值语句
1 | 指针=地址 |
用变量地址作为初始值必须先声明
指针变量的值必须是地址的常量或变量,不能是普通整数,但可以赋值为整数0,表示空指针。
“地址”中存放的数据类型与指针类型必须相符。指针的类型是它所指向变来那个的类型而不是指针本身的数据类型,任何一个指针的数据值都是unsigned long int型。
允许声明指向void类型的指针。例如
1 | void *general; |
实例代码:指针的定义、赋值与使用
1 |
|
运行结果
1 | i=10,j=10 |
- 指针作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void max_min(int a[], int n, int *max, int *min);
int main()
{
int b[N], i, big, small;
printf("Enter %d numbers:", N);
for (i = 0; i < N; i++)
scanf("%d", &b[i]);
max_min(b, N, &big, &small);
printf("Largetst: %d\n", big);
printf("Smallest:%d\n", small);
return 0;
}
void max_min(int a[], int n, int *max, int *min)
{
int i;
*max = *min = a[0];
for (i = 1; i < n; i++) {
if (a[i > *max])
*max = a[i];
else if (a[i] < *min)
*min = a[i];
}
}找出数组中最大元素和最小元素
- const指针
P56
与引用一样,指针可以指向常量或非常量,指向常量的指针(pointer to const)不能用于改变所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。
1. 常量指针
此时不能通过指针改变所指对象的值,但指针本身可以改变。例如:
1 | const int a=20; |
const int *
常量指针1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using std::cout;
using std::endl;
int main()
{
int a = 4;
int change = 5;
/*1. 指向常量的指针*/
const int * b = &a;
// int const *b = &a; //等价于const int *b=&a;
cout << *b << endl;
/*不能再给指针赋值*/ // *b = &change;
b = &change;
cout << *b << endl;
}
- 常量指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using std::cout;
using std::endl;
int main()
{
int a = 4;
int change = 5;
/* 2. 常量指针 */
int *const b = &a;
cout << *b << endl;
// b = &change; /*不能改变b的值
*b = change;
cout << *b << endl;
}
【output】
1
2
34
5
Press any key to continue . . .
- 指针和数组
[补充阅读连接][]
[补充阅读连接]: http://www.cnblogs.com/mq0036/p/3382732.html
数组是具有一定顺序关系的若干个同类型变量的集合体,数组中所有元素都是一次存储在内存单元中的,每个元素都有相应的地址。
例如,当有下列的数组定义时:
1 | int a[10]; |
则a所表示的地址就是元素a[0]的地址,即a等于&a[0]。在指针操作中,若定义了下列指针并初始化为:
1 | int *pa=a; // pa=a等价于pa=&a[0] |
通过指针能引用数组元素,则对于第i个元素。a[i]
等价于 *(a+i)
等价于 pa[i]
等价于*(pa+i)
a[i]表示数组的第i个元素的值。而a+i表示第i个元素的地址,对其间接访问,即*(a+i)就表示第i个元素的值
指针变量和数组的数组名在本质是一样的,因此指向数组的指针变量实质上也可以想数组变量那样使用下表,而数组变量又可像指针变量使用指针所以pa[i]**与 *(pa+i) **也表示第i个元素的值。
相应的,还有第i个元素的地址:
&a[i] 等价于 a+i 等价于 &pa[i] 等价于 pa+i
实例:用5种方法实现数组元素的求和运算
1 |
|
程序运行结果
1 | sum=25=25=25=25=25 |
用指针实现字符串数组
1 | char *string="I love C++!"; |
其等价于
1 | char *string; |
这里没有定义字符串数组,但对字符串常量是按字符数组处理的。
实际上在内存开辟了一个字符数组存放字符串常量。在程序中定义了一个字符指针变量string,并把字符串首地址(即存放字符串的字符数组的首地址)赋给它。
同样通过字符数组名或字符指针变量可以输出一个字符串,例如:
1 | cout <<string; |
二维数组
二维数组在内存是以一维关系顺序存放,因此对于二维数组可以分解为多个一位数组来处理。
如定义一个二维数组
1 | int array[2][3] = { {11,12,13},{21,22,23} }; |
array是二维数组名,array代表整个二维数组首地址,也是二维数组第0个元素的首地址。
实例代码:二维数组和指针
1 |
|
指针数组
如果一个数组的每个元素都是指针变量,这个数组就是指针数组,指针数组的每个元素都是同一类型的指针。
一维指针的定义形式为:
1 | 类型名 *数组名[数组长度] |
例如:
1 | int *p[4] |
定义了一个int型指针数组p,数组有4个元素,每个元素都是指向int型数据的指针。
由于[ ]比*的优先级高,因此p先与[ ]结合,形成p[4]的形式。
对于二维数组,可以按照一维指针数组来理解,数组名是它的首地址,这个指针数组的元素个数就是行数,每个元素是一个指向二维数组某一行的指针。
因此指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。
1 |
|
运行结果
1 | hello c++! i love it |
指针和构造体
用指针可以指向任何数据类型的变量,同样可以定义一个指向结构体变量的指针,我们把这种指向结构体类型变量的指针称为指向结构体的指针或结构体指针。
结构体指针定义的一般形式
1 | struct 结构体名 *结构体指针名 |
例如:
1 | struct object *op; //op为指向结构体变量的指针 |
用结构体指针引用结构体成员
1 |
|
程序运行结果
1 | Name: Mary |
可以看访问结构体指针所指向的结构体变量成员可以采用一下2种方法:
1 | (*结构体指针名).成员项 |
或
1 | 结构体指针名->成员项名 |
例如 *(p).name和 ** p->name**是等价的,都是引用结构体Person类型变量one中的成员name.
成员运算符*.的有限级高于/**所以(p).name中p两侧的括号不能省略,它表示先访问指针指向的目标的目标结构体,然后访问该结构体的成员项。
这里利用**->的形式访问结构体成员的方法更直观,->**称为指向运算符,其左边必须是一个指针变量。
指针和函数
1.指针作为函数的参数
指针和函数可以配合使用。指针既可以函数的参数,也可以作为函数的返回值。
实例代码:指针作为函数参数的调用方式
1 |
|
运行结果
1 | x=12,y=8 |
指针作为函数参数时,对这一个函数的调用就是按地址的函数调用。简称传址调用。
2.返回指针的函数
1 |
|
3.指向函数的指针
1 |
|
运行结果:
1 | add:a=3,b=7,result=10 |
构造函数
类是是一种自己定义的数据类型,类需要通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫作构造函数(constructor)。构造函数的任务是初始化类的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
注意:构造函数也是成员函数,必须是共有函数。构造函数没有返回值且与函数名相同;
构造函数是在声明对象时由系统自动调用的。
构造函数实例程序:
1 |
|
Output:
1 | Client vesion is V3.0.0.001 |
类的声明与实现
类相当于一种用户自定义的类型,同样也可以声明某个类类型的变量,这个变量就称为类的对象(也叫实例),这个声明的过程称为实例化。类和基本类型的区别在于,类类型中同时包含了对数据进行操作的函数。
类由描述某类事物的数据和处理这些数据的函数组成,是一种到处数据类型,可定义对象。要定义对象,首先必须定义类。
类定义格式如下
1 | class <类名> |
大括号
{ }
称为类体,类体由成员表组成,成员表为数据定义或函数定义。
实例:定义成员函数
1 |
|
程序运行结果:
1 | Circumference:31.4159 |
实例程序中3个成员函数都是在类中声明部分中定义的,称为内联函数
注意内敛函数不能使用switch语句。
其它
sizeof指针
1 |
|
疑问
数组、指针 比较大小???
1.
1 |
|
又如:
1 |
|
::双冒号
双冒号(::)用法
- 表示“域操作符”
例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时,
就要写成void A::f(),表示这个f()函数是类A的成员函数。 - 直接用在全局函数前,表示是全局函数
例:在VC里,你可以在调用API 函数里,在API函数名前加:: - 表示引用成员函数及变量,作用域成员运算符
例:System::Math::Sqrt() 相当于System.Math.Sqrt() - 命名空间作用域符,即2直接用在全局函数前,表示是全局函数
在运算符等级中属于最高级的!
using namespace 命名空间名(如,abc);
表示在以下程序代码中所使用的标示符(如果此标示符在abc中定义)是abc中的,包括类型名(类),变量名,函数名,对象名。。。
using abc::标示符(i);
基本类型
C++算术类型
类型 | 含义 | 最小尺寸 |
---|---|---|
类型 | 含义 | 最小尺寸 |
bool | 布尔类型 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode字符 | 16位 |
char32_t | Unicode字符 | 32位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 长整型 | 32位 |
long long | 长整型 | 64位 |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long double | 拓展精度浮点数 | 10位有效数字 |
内置型的机器实现
计算机以比特序列存储数据,每个比特非0即1,例如:
1 | 00011011011110000..... |
大多计算机以2的整数次幂个比特作为块来处理内存,可寻址的最小内存块为字节(byte),存储的基本单元称为字(word)
大多机器字节都是8比特构成,
声明和定义的关系
分离式编译*(separate compilation)*机制:允许将程序分割为若干个文件,每个文件可独立运行。
为了支持分离式编译,C++语言将声明和定义区分开来。
变量声明规定了类型、名字
变量定义:定义除了规定了类型、名字还申请了存储空间,也可能为变量赋予一个初始值
如果只想声明一个变量而非定义它,他在变量名前添加关键字extern,而且不要显示第初始化
1 | extern int i; // 声明i而定义i |
但是给extern关键字标记的变量赋一个初始值,但这么做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明了,而变成 了定义:
1 | extern double pi =3.1416; // 定义 |
const限定符
const T、const T*、T const、const T&、const T& 的区别
1: http://blog.csdn.net/luoweifu/article/details/45600415
[ const的用法及其重要性][2]
[2]: http://blog.csdn.net/zhangfuliang123/article/details/52504001
对变量进行限定放置程序更改它
因为const对象一旦创建后其值就不能再改变了,所以const对象必须初始化。
1 | const int bufSize=512; //这样就把bufSize定义了一个常量。任何试图为bufSize赋值行为都将引发错误 |
与非const类型所能参与的操作相比,const类型的对象能完成其中大部分,但也不是所有的操作都合适。主要的限制就是只能在const类型的对象上执行不改变其内容的操作。
例如:const int 和普通的int一样都能参与算术运算,也都转换成一个布尔值,等等。
- 初始化和const
利用一个对象去初始化另外一个对象,则他们是不是const都无关紧要:1
2
3int i=42;
const int ci=i; //正确:i的值被拷贝给了ci
int j=ci; //正确:ci的值被拷贝给j
bufSize
- 默认情况下const对象仅在文件内有效
当多个文件中出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。
多个文件共同使用const对象
在const变量不管是声明还是定义都要添加extern关键字,这样只需定义一次就可以了。1
2
3
4extern const int bufsize=fcn(); // file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
/* file_1.h头文件 */
extern const int buffize; // 与file_1.cpp中定义的bufsize是同一个const的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为*常量的引用**(reference to const)*与普通引用不同的是,对常量引用不能用作修改它绑定的对象:
1
2
3
4
5const int ci=1024;
const int &ri=ci; //引用及对应的对象都是常量
ri =42; //错误:ri是常量的引用,不能通过引用去改变ci
int &r2=ci; //错误:视图让一个非常量引用指向一个常量对象
注意
:定义一个引用之后,对其所有的操作都是在与之绑定的对象上进行的:
1 |
|
【output】
1 | 44 |
- const用在函数后面
[函数后面加一个const的作用(转] 1
1 |
|
指针和const
指向常量的指针*(poinger to const)*不能用于改变其所指对象的值。
1 | const double pi=3.14; //pi是一个常量,它的值不能改变 |
const指针
指针是对象而引用不是,因此指针本上可定位常量。**常量指针(const point)**必须初始化。
1 | int errNumb=0; |
顶层const
指针本身是不是常量以及指针所指的是不是一个常量是2个互相独立的问题。用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量。
contexpr和常量表达式
常量表达式(const expression)是指值不会改变并且在编译过程中就能得到计算结果的表达式。
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如:
1 | const int max_files=20; // max_files是常量表达式 |
constexpr变量
C++11新标准规定,允许将变量声明为contexpr类型以便由编译器来验证变来那个的值是否是一个常量表达式。
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
1 | constexpr int mf=20; // 20是常量表达式 |
指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
1 | const int *p=nullptr; //p是一个指向类型常量的指针 |
指针与函数名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int add(int a,int b)
{
return a+b;
}
int main()
{
//printf("address of add-function %x\n",add);
int a=(*add)(3,4);
printf("sum=%d\n",a);
return 0;
}回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef void (*PRINTS)(int );
PRINTS a = NULL ;
void callbacks(PRINTS b);
void callbacks(PRINTS b)
{
a = b;
}
void printWelcome(int len)
{
printf("欢迎欢迎 -- %d\n", len);
}
void printGoodbye(int len)
{
printf("送客送客 -- %d\n", len);
}
void main(void)
{
callbacks(printWelcome); // 给
(*a)(6);
}
类型别名
类型别名(type alias)**是一个名字,它是某种类型的同义词。
有2中方法可以用于定义类型别名。
**1. typedef
传统方法使用关键字typedef:
1 | typedef double wages; //wages是double的同义词 |
含有typedef的声明语句定义的不再是变量而是类型别名。和以前的声明语句一样,这里的声明符也可以包含类型修饰,从而也能由基本数据类型构造出复合类型来。
2. using
新标准规定一种新方法,使用**别名声明(alias declaration)**来定义类型的别名。
1 | using SI=Sales_intem; //SI是Sales_item的同义词 |
这种方法用关键字using作为别名的声明开始,其后紧接着别名和等号,其作用的是把等号的左侧的名字规定成等号右侧类型的别名.
类型的别名和类型的名字等价,只要类型的名字能出现的地方,就能使用类型别名:
1 | wages hourly,weekly; //因为前面已经用typedef wages base ,所以其等价于double hourly,weekly ; |
指针、常量和类型别名
1 | typedef char *pstring; // 类型pstring实际上是 类型 char*的别名 |
pstring实际上是指向char指针,因此,const pstring就是指向char的常量指针,而非指向常量字符的指针
注意
1 | const char *cstr=0; //是对const pstring cstr的错误理解;这条语句:声明了一个指向const char的指针 |
声明语句中用到的pstring时,其基本类型是指针。
可是用char*重写了声明语句后,数据类型就变成了char, *成为了声明符的一部分。这样改写的结果是,const char成了基本数据类型。前后2中声明含义截然不同,前者声明了一个指向char的常量指针,改写后的形式则声明了一个指向const char的指针。
auto
auto类型说明符:
用auto能让编译器替我们去分析表达式所属的类型。和原来那些只对应了一种特殊类型的说明符(比如double)不同,auto定义的变量必须有初始值。
1 | auto item =val1+val2; // item初始化为val1和val2相加的结果 |
编译器将根据val1和val2相加的结果来推断item的类型。
如果val1和val2是类Sales_tem的对象,则item的类型就是Sale_item;如果这2个变量的类型是double,则item的类型就是double,以此类推。
**auto也能在一条语句中声明多个个变量
**。但是一条声明语句只能有一个基本数据类型。
1 | auto i=0,*p=&i; // 正确:i是整数、p是整形指针 |
语句中所有变量的初始化类型数据必须一样
struct
P64
C++允许用户以类的形式自定义数据类型,而库类型 string、istream、ostream等也都是以类 的形式定义的。并且提供诸如isbn函数、>>、<<、+、+=等运算在内的一些列操作。
我们的类以关键字struct开始,紧跟着类名和类体。类体由花括号包围形成了一个新的作用域。
其中类体可以为空
类内部定义的名字必须唯一,但是可以与类外部定义的名字重复。
类体的右侧的表示结束的花括号必须写一个分号,这是因为类体后面可以紧跟着变量名以表示对该类型对象的定义。
结构体可以作为函数的参数,实现数据传递,还可以作为一个函数的返回值。
1 | struct Sales_data{ |
名词
快速索引名词
- 数据类型
数据类型决定了程序中数据和操作的意义。p30、69
算术类型(arithmetic type):**包含了字符、整形数、布尔值和浮点数;
**空类型(void):不对应具体值,仅用于特殊的场合。例如:当函数不返回任何值使用空类型作为返回值。
变量与对象
“变量”(variable) 和 “对象”(object)可以互换使用。P38页
引用
引用为对象起了另一个名字,引用类型引用另外一种类型。P45
- 字面值类型(literal type)
常量表达式需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把他们称为字面值类型
算术类型、引用、和指针都属于字面值类型。P59
- constexpr变量
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。P59
- decltype类型指示符
它的作用是选择返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。P62
define
P68