C++ 基础知识
知识点
列号 | 说明 |
---|---|
1 | 引言 |
2 | 函数与参数 |
3 | 异常 |
4 | 动态存储空间分配 |
5 | 自有数据类型 |
6 | 异常类illegalParameterValue |
引言
在检查一个程序的时候,我们应该提出如下疑问:
- 它正确吗?
- 它容易读懂吗?
- 它有完善的文档吗?
- 它容易修改吗?
- 它在运行时需要多大内存?
- 它的运行时间有多长?
- 它的通用性如何?能否不加修改就可以解决更大范围的数据?
- 它可以直接在多种计算机上编译和运行吗?或者说它需要修改之后才能运行吗?
函数与参数
参数的传递方式一:值传递
程序1-1 求两个整数的和
int sum(int a, int b)
{
return a + b;
}
在上述的函数sum中,a,b就是函数sum的形参(formal parameter),每一个形参都是整型的,如果有这样的调用:
z = sum(1,2)
那么,1和2变身分别的对应sum的a和b的实参(actual parameter)
在上述的1-1程序中,形参a和b实际上是传值参数(value parameter)。在运行时,函数sum执行前,把实参复制给形参。复制过程是由形参类型的复制构造函数(copy construstor)来完成的。如果实参和形参的类型不同,必须进行类型转换,把实参转换为形参的类型,当然,前提是这样的类型转换是被允许的。
当调用sum(1,2)时,将1的赋值给形参a,将2赋值给形参b,如果实参类型和形参类型不一样,则在赋值之前会进行类型转换。
当函数运行结束,则会调用析构函数进行释放形参。
参数的传递方式二:地址传递
程序1-2 交换两个整数值
#include <stdio.h>
void swap(int * a, int * b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int a = 3;
int b = 5;
printf("交换前:a = %d \t b = %d\n",a,b);
swap(&a,&b);
printf("交换后:a = %d \t b = %d\n",a,b);
return 0;
}
输出控制台:
交换前:a = 3 b = 5
交换后:a = 5 b = 3
从1-2的程序上看,在main函数里面的a和b通过取址符&把a和b的地址传递给函数swap形参的a和b,此时,在swap函数里面修改形参a和b的值,便会影响到实际参数的值;
注意:参数的传递不管是何种方式,其实都是值传递,这里说的地址传递不过是为了方便理解概念,实际地址传递也是值传递的一种,因为地址传递也是传值,只是传的是地址值。
模板函数
在程序1-1中的形参为整型,如果在新的需求里面需要编写另外一个与程序1-1中相同效果的函数,这个函数是求浮点型也可以求整型,如果还像程序1-1那样写,则我们需要写很多个函数,但是C++给我们提供了模板函数。
程序1-3 求两个数之和
template <typename T>
T sum(T a, T b)
{
return a + b;
}
在程序1-3中,其中template是声明模板函数数据类型的关键字,typename 是把T定义为相应的数据类型。
模板函数的作用:提高代码的编写效率。
引用参数
在程序1-3中使用的形参由于会先调用拷贝构造函数进行赋值,所以相应的运行时间会增加。例如,我们来考察一下函数被调用以及返回时所涉及的操作。当a、b是值传递时,一旦进入函数调用,类型T的复制构造函数便把相应的实参分别复制给形参的a、b,以供函数的使用。当函数返回时,类型T的析构函数被启用,以释放形式参数a、b的空间。
假设T是用户自定义的类型Student,那么它的的复制构造函数将复制Student的所有元素,而析构函数则将整个Student的成员逐个释放。如果Student的成员较少还好,但是如果Student的成员比较多并且复杂时,可想而知,通过普通传值的方式进行传参的时候,效率有多低。因此,C++提供了引用传参的方式,想要了解引用传参则先要了解什么是引用。1、引用只是别量的一个别名,和变量是占用同一块内存空间,例如,张三是阿狗的书名,但张三也是阿狗,阿狗也是张三。
程序1-4 求两个数之和
template <typename T>
T sum(T &a, T &b)
{
return a + b;
}
如程序1-4的形参T& a,便是引用的方式。引用参数传递的方式,当函数被调用是,这个程序并没有复制实参的值,在返回的时候也没有调用析构函数。
常引用
如程序1-4,这样传递的方式虽然并没什么大问题,但是却可以在函数sum内部就行修改,一般在实现sum的时候不需要修改形参的值都采用C++的另外一种参数形式,常引用,这种模式下指明引用参数是不能被函数所修改的,如在程序1-4中,a、b值并没有被修改,因此我们可以重写该代码如下:
程序1-5 求两个数之和
template <typename T>
T sum(const T &a, const T &b)
{
return a + b;
}
**返回值 **
一个函数可以返回一个值、一个引用或一个常引用。在前面的例子都是返回一个值,这种情况下,返回的对象被复制到调用环境中。对于函数sum的所有版本来说,这种复制过程性都是必要的,因为函数求和的结果存储在一个局部的临时变量中,当函数结束时,这个临时变量(以及所有其他的临时变量、局部变量和传值参数)所占用的空间都会被释放,其值也就不再有效。为了不丢失这个值,在释放临时变量、局部变量以及传值参数的空间之前,要把这个值从临时变量复制到调用该函数的环境中去。
给函数返回类型增加一个后缀&,我们便指定了引用返回(reference return) 。如:
程序1-6 求两个数之和
template <typename T>
T& sum(const T &a, const T &b)
{
return a + b;
}
重载函数
一个函数的签名是由这个函数的形参类型以及形参个数确定的,在程序1-1中,函数的sum的签名就是(int,int)。C++可以有两个或多个同名函数,但是任何两个同名的函数不能有同样的签名,定义多个同名的函数的机制就成为函数重载
编写一个模板函数count,统计a[0:n-1]中value出现的次数。
#include <stdio.h>
template <typename T,size_t value = 0>
int count(const T arr[],int size)
{
int cnt = 0;
for (int i = 0; i < size; ++i)
{
if (arr[i] == value)
{
cnt++;
}
}
return cnt;
}
int main(void)
{
int arr[] = { 1,1,2,2,2,3,3,2 };
int cnt = count<int,2>(arr, 8);
printf("计数:%d\n",cnt);
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
编写一个函数fill,给数组赋值value
#include <stdio.h>
void fill(int arr[],int size,int value)
{
for (int i = 0; i < size; ++i)
{
arr[i] = value;
}
}
int main(void)
{
int arr[] = { 1,1,2,2,2,3,3,2 };
fill(arr, 8,2);
for (int i = 0; i < 8; i++) {
printf("%d\t", arr[i]);
}
printf("\n");
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
异常
异常是表示程序出现错误的信息,例如,对a/b求值,如果b = 0时,那么除数为0,这是一个错误。对这个错误,虽然C++检查不出来,但是硬件会检查出来,并且抛出一个异常。
可以这样编写一个程序进行测试,它可以对一些异常情况进行检查,当检查有异常的情况,就抛出。例如:
程序1-7 异常抛出
int div(int a, int b)
{
if ( b <= 0 || a <= 0)
{
throw "All parameters should be > 0";
}
return a/b;
}
程序的异常抛出有很多种类型,程序1-7 只是举例说明其中一种char*,在编程的时候应该针对性的选择对应的异常处理或者自定义一个合适的异常处理类。
异常的处理,当一段代码抛出异常由这段代码的try块来处理,紧跟在try块之后的是catch块。每个catch块都有一个参数,参数的类型决定了这个catch块要捕捉的异常类型。如:
catch(char* err){}
捕捉的异常类型为char*。
动态存储空间分配
操作符:new
C++操作符new用来进行动态存储分配或运行时存储分配,它的返回值是一个指针,指向所分配空间。例如,要给一个整数动态分配存储空间:
int * ptr = new int;
执行上述语句时,系统将会分配一块sizeof(int)大小的内存块,并且把内存块的首地址返回给ptr指针变量,
如果需要为ptr所指向的内存空间填充数值,则可以执行如下操作:
ptr = 10;
一维数组
操作符new除了可以给整型分配内存空间,也可以为数组分配一片有限并且连续的内存空间,如:
int *ptrArr = new int[10]
执行上述代码,便可分配sizeof(int) * 10 的内存空间并且把首地址返回给ptrArr。
操作符:delete
当我们用到new创建的新内存块的时候,必须要用到delete释放内存,否则将会导致内存泄露等程序Bug,如上述所分配的内存可以执行如下代码进行内存的释放:
delete ptr
;
delete []ptrArr
;
自定义数据类型
C++除了支持诸如:int、float、char这样的数据类型外,还支持自定义的数据类型,比如,有一个可以处理货币的数据类型currency对象(也称实例),这种对象有三个成员:符号(+或-),美元和美分,操作如下:
- 给成员赋值
- 确定成员值(即符号、美元数目、美分数目)
- 两个对象相加
- 输出
假定用无符号长整型变量dollars、无符号整型变量cents和signType类型变量sign来描述货币对象,其中signType类型定义如下:enum signType{plus,minuus};
currency类声明如下:
程序1-8 currency类声明
class currency
{
public:
// 构造函数
currency(signType theSign = plus,
unsigned long theDollars = 0,
unsigned int theCents = 0);
// 析构函数
~currency(){}
void setValue(signType,unsigned long, unsigned int);
void setValue(double);
signType getSign() const {return sign;}
unsigned long getDollars()const{return dollars;}
unsigned int getCents()const {return cents;}
currency add(const currency&) const;
currency& increment(const currency&);
void output() const;
private:
signType sign; // 对象符号
unsigned long dollars; // 美元的数量
unsigned int cents; // 美分的数量
};
对于公有部分与类名相同的函数就是 构造函数 ,构造函数指明创建一个类对象的方法,而且没有返回值。在本例中,构造函数有三个参数,其缺省值分别是plus、0和0。构造函数的实现在本节稍后的部分给出。在创建一个currency类对象时,构造函数被自动调用。创建currency类对象有如下两种:
currency f,g(plus,3,45),h(minus,10)
currency *m = new currency(plus,8,12);
第一行声明了三个currency对象:f、g和h。其中f调用默认无参构造函数,g调用有三个参数的构造函数,h调用有两个参数的构造函数,在第二行中声明了一个类指针m。调用new 操作符创建一个currency对象,并把对象指针存储在m中。
在公有部分还有一个和类名一样的函数,不过这个函数加上了一个~符号,这个就是 析构函数 ,每当一个对象超出作用域的时候,析构函数就被自动调用来删除对象。在本例中,析构函数的定义为空函数。
注意:构造函数和析构函数都是没有返回值的函数
接下来的类还提供两个赋值的成员函数分别是:setValue(signType,unsigned long,unsigned int);setValue(double);
这里的两个函数就是重载,因为类名一样但是它们的签名不一样。除了提供赋值成员函数外,还提供访问属性的成员函数,分别是:getSign、getDollars、getCents
这三个函数返回调用对象相应数据成员,关键字const指明这些函数不会改变调用对象的属性值。我们一般把这种对象称为常量函数(constant function);
在程序1-8中并未指明复制构造函数,一般都会调用缺省的复制构造函数,仅仅复制数据成员。对于类currency来说,缺省复制构造函数已经足够了。
定义currency:
currency::currency(signType theSign,unsigned long theDollars, unsigned int theCents)
{
setValue(theSign,theDollars,theCents);
}
void currency::setValue(signType theSign,unsigned long theDollars, unsigned int theCents)
{
if (theDollars > 99)
throw illegalParameterValue("Cents should be < 100");
sign = theSign;
dollars = theDollars;
cents = theCents;
}
void currency::setValue(double theAmount)
{
if (theAmount<0)
{
sign = minus;
theAmount = -theAmount;
}
else
{
sign = plus;
dollars = (unsigned long) theAmount;
cents = (unsign int)((theAmount + 0.001 - dollars) * 100);
}
}
currency currency::add(const currency& x) const
{
long a1, a2,a3;
currency result;
a1 = dollars * 100 + cents;
if (sign == minus) a1 = -a1;
a2 = x.dollars * 100 + x.cents;
if (x.sign == minus) a2 = -a2;
a3 = a1 + a2;
if (a3 < 0) {result.sign = minus; a3 = -a3;}
else result.sign = plus;
result.dollars = a3/100;
result.cents = a3 - result.dollars * 100;
return result;
}
currency& currency::increment(const currency& x)
{
*this = add(x);
return *this;
}
void currency::output() const
{
if (sign == minus)
cout << "-";
cout << "$" << dollars << ".";
if (cents < 10) cout << "0";
cout << cents;
}
主函数应用currency类
#include <iostream>
#include "currency.h"
using namespace std;
int main(void)
{
currency g,h(plus,3,50), i, j;
g.setValue(minus,2,25);
i.setValue(-6.45);
j = h.add(g);
h.output();
cout << "+";
g.output();
cout << " = ";
j.output();
cout << endl;
j = i.add(g).add(h);
j = i.increment(g).add(h);
cout << "Attempting to initiallize with centts = 152" << endl;
try
{
i.setValue(plus,3,152);
}
catch(illegalParameterValue e)
{
cout << "Caught thrown exception " << endl;
e.outputMessage();
}
return 0;
}
异常类illegalParameteerValue
用户自定义一个illegalParameterValue.当一个函数的实参值无意义是,需要抛出illegalParameterValue类型。
illegalParameterValue声明:
class illegalParameterValue
{
public:
illegalParameterValue():message("Illegal parameter value"){}
illegalParameterValue(char* theMessage){message = theMessage;}
void outputMessage() {cout << message << endl;}
private:
string message;
};
illegalParameterValue应用:
int div(int a, int b)
{
if (b <= 0)
{
throw illegalParameterValue("All parameters should be > 0");
}
return a/b;
}
int main(void)
{
try
{
cout << sum(3,0) <<endl;
}
catch(illegalParameterValue e)
{
cout <<"The parameters to sum were 3, 0"<< endl;
cout << "illegalParameterValue exception thrown" << endl;
e.outputMessage();
return 1;
}
return 0;
}
诸葛云生
发布了17 篇原创文章 · 获赞 1 · 访问量 610
私信
关注