c++实验报告
更新时间:2024-04-14 16:41:01 阅读量: 综合文库 文档下载
实验一 类与对象
实验目的:
(1)
掌握类的定义和使用;掌握类对象的声明;练习具有不同访问属性的成员的访问方式;观察构造函数和析构函数的执行过程;
(2) (3)
学习类组合使用方法;
使用VC++的debug调试功能观察程序流程,跟踪观察类的构造函数、析构函数、成员函数的执行顺序。
仪器工具及材料
(1)
PC+Windows 2000+VC 6.0
内容及程序
?代码:
(1) 第一题:
#include
using namespace std;
int Max1(int a,int b);//求两个整数最大值 int Max1(int a,int b,int c); //求三个整数最大值 double Max1(double a,double b); //求两个双精度数
最大值
double Max1(double a,double b,double c); //求三个双精度数最大值
//主函数 int main() { int a,b,c; double x,y,z;
cout<<\求两个整数最大值,\\n输入两个整数:\ cin>>a>>b;
cout<<\最大值为:\
cout<<\求三个整数最大值,\\n输入三个整数:\ cin>>a>>b>>c;
cout<<\最大值为:\
cout<<\求两个双精度数最大值,\\n输入两个双精度数:\
cin>>x>>y;
cout<<\最大值为:\
cout<<\求三个双精度数最大值,\\n输入三个双精度数:\
cin>>x>>y>>z;
cout<<\最大值为:\
return 0; }
int Max1(int a,int b) {
return a>b?a:b; }
int Max1(int a,int b,int c) {
return a>b?(a>c?a:c):(b>c?b:c); }
double Max1(double a,double b)
{
return a>b?a:b; }
double Max1(double a,double b,double c) {
return a>b?(a>c?a:c):(b>c?b:c);
(2) 第二题:
}
#include
bool swap(int &a,int &b);//交换两个数值 //主函数 int main() { int a,b;
cout<<\请输入两个整数a,b:\ cin>>a>>b;
cout<<\当前a,b值为:a=\
swap(a,b);
cout<<\运行swap()交换a,b值\ cout<<\当前a,b值为:a=\ return 0; }
bool swap(int &a,int &b) { int temp; temp=a; a=b; b=temp;
return true; }
(3) 第三题:
// /CPU.CPP //类成员函数定义 #include\
int CPU::run() {
Rank=P4; frequency=150; voltage=4.55;
cout<<\正在运行。\\n 级别,频率,电压已改变!\
cout<<\级别=\频率=\
<<\电压=\
return 0; }
int CPU::stop() {
Rank=P1;
frequency=50; voltage=1.55;
cout<<\已停止。\\n 级别,频率,电压已改变!\
cout<<\级别=\频率=\
<<\电压=\
return 0; }
CPU::CPU(CPU &p) {
Rank=p.Rank; frequency=p.frequency; voltage=p.voltage;
cout<<\复制构造函数正在运行\
cout<<\级别=\频率=\
<<\电压=\}
//CPU.H //cpu类 #pragma once
#ifndef _CPU_H_ #define _CPU_H_
#include
enum CPU_Rank{P1=1,P2,P3,P4,P5,P6,P7}; //类定义 class CPU { public:
CPU():Rank(P1),frequency(0),voltage(0.0) { cout<<\构造函数运行\构造函数
~CPU(){ cout<<\析构函数运行\\n\
析构函数
private: };
#endif
///main.cpp #include\
CPU_Rank Rank;//级别 int frequency;//频率 double voltage;//电压 int run();//运行函数 int stop();//停止函数
CPU(CPU &p);//复制构造函数
//主函数 int main() {
cout<<\构造CPU对象cpu1:\ CPU cpu1; cpu1.run(); cpu1.stop();
cout<<\构造CPU对象cpu2:\ CPU cpu2(cpu1); return 0; }
(4) 第四题:
///Computer.cpp //类成员函数 #include\
Computer::Computer(CPU Cpu,RAM
Ram ,CDROM
Cdrom):cpu(Cpu),ram(Ram),cdrom(Cdrom)
{
cout<<\构造函数运行\\n\}
Computer::Computer(Computer &cmp) {
cpu=cmp.cpu; ram=cmp.ram; cdrom=cmp.cdrom;
cout<<\复制构造函数正在运行\\n\
}
Computer::~Computer() {
cout<<\析构函数运行\\n\ }
int Computer::run() {
cout<<\函数运行\\n\
return 0; }
int Computer::stop() {
cout<<\函数运行\\n\
return 0; }
int Computer::print() {
cout<<\成员变量值为:\\n\ cpu.Get();
cout<<\ cout<<\ cout<<\函数正在运行\\n\
return 0; }
///Computer.h
//类定义 #pragma once
#ifndef _COMPUTER_H_ #define _COMPUTER_H_
#include
//RAM类定义 class RAM { public:
RAM(int size=0):ram_size(size){ cout<<\
RAM构造函数正在运行\\n\
RAM(RAM& r){ ram_size=r.ram_size;
cout<<\复制构造函数正在运行\\n\
~RAM(){ cout<<\析构函数正在运行
\\n\
int Get(){ return ram_size;}//返回成员值
private: };
//CDROM类定义 class CDROM { public:
CDROM(bool a=false):empty(a){ cout<<\int ram_size;//内存大小
CDROM构造函数正在运行\\n\
CDROM(CDROM &cd){ empty=cd.empty;
cout<<\复制构造函数正在运行\\n\
~CDROM(){ cout<<\析构函数正在
运行\\n\
bool Get(){ return empty;}//返回成员值
private: };
bool empty;//是否为空标记
//类定义 class Computer { public:
Computer(CPU Cpu,RAM Ram ,CDROM
Cdrom);//构造函数
//Computer():cpu(),ram(),cdrom(){ cout<<\空
构造函数正在运行\\n\构造函数重载 private: };
CPU cpu;//芯片 RAM ram;//内存 CDROM cdrom;//光驱
Computer(Computer &cmp);//复制构函数 ~Computer();//析构函数 int run(); int stop(); int print();
#endif
///CPU.CPP与CPU.H同第三题 ///main.cpp
#include\//主函数 int main() {
cout<<\定义CPU对象:\ CPU cpu;
cout<<\定义RAM对象:\ RAM ram;
cout<<\定义CDROM对象:\ CDROM cdrom;
cout<<\定义Computer对象computer:\
Computer computer(cpu,ram,cdrom); computer.print();
return 0; }
(5) 第五题:
////见第三题
结果及分析 ? 实验结果:
① 第一题:
② 第二题:
③ 第三题:
④ 第四题:
分析:每定义一个变量将调用构造函数,形参为其他的类的构造函数中,函数调用时将先调用形参类的复制构造函数生成形参对象,在初始化列表中初始化类对象时也会调用复制构造函数,构造函数运行结束会调用形参类的析构函数。 ⑤ 第五题:见第三题
实验二 类与对象
1. 目的要求:
1) 观察程序运行中变量的作用域、生存期和可见性; 2) 学习类的静态成员的使用;
3) 学习多文件结构在C++程序中的使用。
2. 实验内容:
1) 实现客户机(CLIENT)类。定义字符型静态数据成员ServerName,保存其服务器名称;整型静态数据成员ClientNum,记录已定义的客户数量;定义静态函数ChangeServerName()改变服务器名称。 ·代码: //client.h //client头文件 #pragma once
#ifndef _CLIENT_H_ #define _CLIENT_H_ //头文件
#include
//client类定义 class CLIENT {
public:
CLIENT();//构造函数 ~CLIENT();//析构函数
CLIENT(CLIENT &P);//复制构造函数
static bool ChangeServerName(string NewName);//改变服务
器名称
static int ShowClientNum(){ return ClientNum;}
户机数量 private: static string ServerName;//服务器名
static int ClientNum;//客户机数量
};
#endif
//client.cpp //类成员函数定义 #include\
//静态成员初始化
string CLIENT::ServerName(\int CLIENT::ClientNum=0;
//构造函数 CLIENT::CLIENT() {
ClientNum++;
//添加代码
//返回客
}
//析构函数
CLIENT::~CLIENT() {
ClientNum--; }
//复制构造函数
CLIENT::CLIENT(CLIENT &P) { }
//改变服务器名称
bool CLIENT::ChangeServerName(string NewName) {
ServerName=NewName;
cout<<\当前ServerName=\
return true; }
//test.cpp
#include\ //主函数 int main() {
CLIENT client1;//CLIENT对象 string name;//服务器名 int count;//客户机数量 //添加代码 //添加代码
cout<<\已定义CLIENT对象client1\ cout<<\请输入服务器名:\ cin>>name; //改变服务器名
CLIENT::ChangeServerName(name);
cout<<\
当
前
客
户
量:\ //定义client2
CLIENT client2;//CLIENT对象
cout<<\已定义CLIENT对象client2\ cout<<\请输入改变后服务器名:\ cin>>name;
CLIENT::ChangeServerName(name);
cout<<\
当
前
客
户
量:\ //增加客户机
cout<<\请输入要增加客户机数量:\ cin>>count;
CLIENT *newCLient=new CLIENT[count];
cout<<\
当
前
客
户
量:\ //删除客户机
delete[] newCLient;
机
数
机
数
机
数
cout<<\已删除新分配客户机对象\cout<<\
当
前
客
户
机
数
量:\
return 0; }
·运行结果:
2)利用多文件结构实现题目1),在头文件client.h中定义类,在 文件client.cpp中实现该类,在文件test.cpp 中测试这个类,观察相 应的成员变量取值的变化情况,要求ClientNum能够实时记录客户机对 象的数量。 ·代码: 同第一题 ·运行结果:
3)思考并回答以下概念: 类的静态数据成员,类的静态函数成员,多文件结构,文件包含。
a)
类的静态数据成员:static关键字开始的数据成员,为一个类所共有,公有状态下可通过对象名或类名访问,在类定义外初始化。
b)
类的静态函数成员:static关键字开始的函数成员,也为一个类所共有,可通过对象或类名访问,该函数不能直接访问非静态成员,需通过对象名访问。可直接访问静态成员与成员函数。
3. 主要仪器设备及软件:Windows 2000+VC 6.0
实验三 数组、指针与字符串实验
1. 目的要求:
1) 学习使用数组;学习字符串数据的组织和处理;学习标准C++库的使用; 2) 掌握指针的使用方法;练习通过debug观察指针的内容及其所指的对象的内容;练习通过动态内存分配实现动态数组,并体会指针在其中的作用;
3) 分别使用字符数组和标准C++库练习处理字符串的方法。
2.实验内容:
1) 编写一个类用于处理3×3矩阵转置,测试转置的效果,输出
转置前后的矩阵。 .代码: //Matrix.h //Matrix.h类声明
//实现任意类型矩阵的转置
#ifndef MATRIX_H #define MATRIX_H //头文件 #include
//声明模板类 template
};
public:
ReveMatrix(){} ~ReveMatrix(){}
//转置n*m的矩阵
static T* getReverse(T* a,int n,int m);//返回指针类型
//函数定义
template
T temp; int i,j;
vector
for(i=0;i for(j=0;j b[i*n+j]=a[j*m+i]; } } for(i=0;i for(j=0;j a[i*m+j]=b[i*m+j]; } } return a; } #endif //函数重载实现n*n矩阵转置 /* class ReveMatrix { public: ReveMatrix(){} ~ReveMatrix(){} //转置n*m的矩阵 static void getReverse(double* a,int n,int m); static void getReverse(float* a,int n,int m); static void getReverse(int* a,int n,int m); static void getReverse(char* a,int n,int m); static void getReverse(string* a,int n,int m); }; void ReveMatrix::getReverse(double *a,int n,int m) { double temp=0; for(int i=0;i for(int j=i;j temp=*(a+i*m+j); *(a+i*m+j)=*(a+j*n+i); *(a+j*n+i)=temp; } } } void ReveMatrix::getReverse(float* a,int n,int m) { float temp; for(int i=0;i for(int j=0;j temp=*(a+i*m+j); *(a+i*m+j)=*(a+j*n+i); *(a+j*n+i)=temp; } } } void ReveMatrix::getReverse(int *a,int n,int m) { int temp; for(int i=0;i for(int j=0;j temp=*(a+i*m+j); *(a+i*m+j)=*(a+j*n+i); *(a+j*n+i)=temp; } } } void ReveMatrix::getReverse(char* a,int n,int m) { char temp; for(int i=0;i for(int j=0;j temp=*(a+i*m+j); *(a+i*m+j)=*(a+j*n+i); *(a+j*n+i)=temp; } } } void ReveMatrix::getReverse(string* a,int n,int m) { string temp; for(int i=0;i for(int j=0;j temp=*(a+i*m+j); *(a+i*m+j)=*(a+j*n+i); *(a+j*n+i)=temp; } } } */ //main.h #include //定义矩阵长宽 #define W 3 #define H 3 int main() { int i,j; int a[W][H]={1,2,3,4,5,6,7,8,9}; int *p; char ch[W][H+1]={'a','b','c','d','e','f','g','h','i','j','k','l'}; char* pp; cout<<\转置前矩阵:\for(i=0;i for(j=0;j cout< cout< } } p=ReveMatrix cout<<\转置后矩阵:\for(i=0;i for(j=0;j cout< cout< cout<<\转置前矩阵:\ for(i=0;i for(j=0;j cout< cout< pp=ReveMatrix for(j=0;j cout< cout< return 0; 运行结果: 2)定义一个具有构造函数和析构函数的类,如实验一的CPU类, 定义一个CPU的对象数组,观察构造函数的析构函数的调用过程。 a) 代码: //CPU.H //cpu类 #ifndef _CPU_H_ #define _CPU_H_ #include enum CPU_Rank{P1=1,P2,P3,P4,P5,P6,P7}; //类定义 class CPU { public: CPU():Rank(P1),frequency(0),voltage(0.0) { cout<<\构造函数运行\\n\ }//构造函数 CPU(CPU_Rank cr=P1,int fre=0,double vol=0.0) :Rank(cr),frequency(fre),voltage(vol) { cout<<\重载构造函数运行\\n\ }//重载构造函数 ~CPU(){ cout<<\析构函数运行\\n\析构函数 CPU(CPU &p);//复制构造函数 int run();//运行函数 int stop();//停止函数 private: CPU_Rank Rank;//级别 int frequency;//频率 double voltage;//电压 }; #endif //CPU.CPP //类成员函数定义 #include\ int CPU::run() { Rank=P4; frequency=150; voltage=4.55; cout<<\级别=\频率=\<<\电压=\ cout<<\正在运行。\\n 级别,频率,电压已改变!\ return 0; } int CPU::stop() { Rank=P1; frequency=50; voltage=1.55; cout<<\级别=\频率=\<<\电压=\ cout<<\已停止。\\n 级别,频率,电压已改变!\ return 0; } CPU::CPU(CPU &p) { Rank=p.Rank; frequency=p.frequency; voltage=p.voltage; cout<<\复制构造函数正在运行\\n\ } //main.h #include\ int main() { CPU_Rank cr=P2; CPU cpu1(cr,1,1.5); CPU cpu[10]={CPU(P4,4),CPU(cr),CPU(P3,2,2.5),CPU(cpu1),CPU(cr,4,4.5)}; return 0; } b)运行结果: 3)利用动态内存分配的方式重新完成题目2)。 a)代码: //cpu.h //cpu类 #ifndef _CPU_H_ #define _CPU_H_ #include enum CPU_Rank{P1=1,P2,P3,P4,P5,P6,P7}; //类定义 class CPU { public: CPU():Rank(P1),frequency(0),voltage(0.0) { cout<<\构造函数运行\\n\ }//构造函数 CPU(CPU_Rank cr=P1,int fre=0,double vol=0.0) :Rank(cr),frequency(fre),voltage(vol) { cout<<\重载构造函数运行\\n\ }//重载构造函数 ~CPU() { cout<<\析构函数运行\\n\析构函数 CPU(CPU &p);//复制构造函数 int run();//运行函数 int stop();//停止函数 private: CPU_Rank Rank;//级别 int frequency;//频率 double voltage;//电压 }; #endif //cpu.cpp //类成员函数定义 #include\ int CPU::run() { Rank=P4; frequency=150; voltage=4.55; cout<<\级别=\频率=\<<\电压=\ cout<<\正在运行。\\n 级别,频率,电压已改变!\ return 0; } int CPU::stop() { Rank=P1; frequency=50; voltage=1.55; cout<<\已停止。\\n 级别,频率,电压已改变!\cout<<\级别=\频率=\<<\电压=\ return 0; } CPU::CPU(CPU &p) { Rank=p.Rank; frequency=p.frequency; voltage=p.voltage; cout<<\复制构造函数正在运行\\n\} //main.cpp #include\ int main() { CPU *cpu=new CPU[10](); delete[] cpu; return 0; } b)运行结果: 4)使用系统提供的string类定义字符串对象并初始化,实现从原始字符串中提取一个子串。 .代码: //main.cpp #include //返回字符串从下标startpos开始的count个字符的子串 string substr(string str,int startpos,int count); int main() { string str=\ string substr1,substr2,substr3; cout<<\源字符串为:\ substr1=str.substr(0,5); substr2=str.substr(2,5); substr3=substr(str,4,4); cout<<\子串1=\cout<<\子串2=\cout<<\子串3=\ return 0; } string substr(string str,int startpos,int count) { string sub=\ int i,j=startpos+count; for( i=startpos;i sub+=str[i]; } return sub; } .运行结果: 5)选做:定义一个Point(二维点类)的对象数组,利用该数组实现直线的线性拟合。 a) 代码: //point.h //头文件 #ifndef POINT_H #define POINT_H //类定义 class Point { public: Point(float xx=0,float yy=0):x(xx),y(yy){} float getX()const{ return x;} float getY()const{ return y;} private: float x,y; }; #endif //main.cpp #include float lineFit(const Point Points[],int nPoint); int main() { Point p[10]={Point(6,10),Point(14,20),Point(26,30),Point(33,40), Point(46,50),Point(54,60),Point(67,70),Point(75,80), Point(84,90),Point(100,100)}; float r=lineFit(p,10); cout<<\相关系数为:\ return 0; } //直线线性拟合 //Points点集合,nPoint点数 float lineFit(const Point Points[],int nPoint) { float avgX=0,avgY=0; float lxx=0,lyy=0,lxy=0; int i; } for( i=0;i avgX+=Points[i].getX()/nPoint; avgY+=Points[i].getY()/nPoint; } for( i=0;i lxx+=(float)pow((Points[i].getX()-avgX),2); lyy+=(float)pow((Points[i].getY()-avgY),2); lxy+=(Points[i].getX()-avgX)*(Points[i].getY()-avgY); } cout<<\这些点能被直线:y=ax+b拟合\cout<<\ cout<<\ return static_cast b)运行结果: 6)选做:定义一个动态数组类。 a) 代码: //main.cpp #include class TestClass { public: TestClass(){ t=NULL;num=0;} TestClass(int n) { if(n<1) { t=NULL; num=0; } else { //动态申请内存 t=new TestClass[n](); count+=n; num=n; } } TestClass(TestClass &tc) { //重新分配内存,深复制 t=new TestClass[count]; for(int i=0;i t[i]=tc.t[i];//赋值 num=count; count+=count; } ~TestClass(){delete[] t;cout<<\动态销毁了\ void test(){cout< static int getCount(){ return count;} void (*funptr)();//声明不会出错,赋值出错 private: static int count; int num; TestClass* t; }; int TestClass::count=0; int main() { TestClass test(5); //定义类成员函数指针,写在类定义里出错 个对象1 数 void (TestClass:: *ptr)()=&TestClass::test;//不能指向静态成员函 // int *p=&TestClass::count;//指向对象的静态成员,只能指向 公有 cout<<\共动态申请了\个对象\ return 0; } b) 运行结果: //定义函数指针 int (*pstr2)()=&TestClass::getCount; cout<<\共动态申请了\个对象\TestClass test2(test); cout<<\共动态申请了\个对象\ 思考并回答:数组,指针,对象数组,动态内存分配,默认构造函数,标准类库,字符串类 string,线性拟合。 标准类库: String类: 指针: 动态内存分配: 默认构造函数: 3. 主要仪器设备及软件:Windows 2000+VC 6.0 实验四 继承与派生 1. 目的要求: 1) 学习定义和使用类的继承关系,定义派生类;熟悉不同继承方式下对基类成员的访问控制; 2) 学习利用虚基类解决二义性问题。 2.实验内容: 1)定义一个基类Animal,有私有整型成员变量age,构造其派生类dog,在其成员函数SetAge(int n)中直接给age赋值,看看会有什么问题,把 age改为公有成员变量,还会有问题吗?编程试试看。 .代码: //man.cpp #include using namespace std; class Animal { }; class dog:public Animal { }; int main() { dog d; int age=10; public: bool SetAge(int newAge) { } int getAge() { } return age; age=newAge;//private不可访问,public可访问 return true; //private: public: int age; } .运行结果: return 0; cout<<\当前年龄为:\cout<<\设置年龄为\d.SetAge(age); 2) 定义一个基类BaseClass,有整型成员变量Number,构造其派生类DerivedClass,定义该派生类的对象,观察构造函数和析构函数的执行情况。 .代码: //man.cpp #include //2)定义一个基类BaseClass,有整型成员变量Number,构造其派生类DerivedClass,定义该派生类的对象,观察构造函数和析构函数的执行情况。 class BaseClass { public: BaseClass():Number(0){ cout<<\构造函数运private: int Number; 行\ \}; class DerivedClass:public BaseClass { public: \ \ }; int main() { } return 0; DerivedClass d; ~DerivedClass(){ cout<<\析构和函数运行DerivedClass(){ cout<<\构造函数运行 ~BaseClass(){ cout<<\析构和函数运行 .运行结果: 3) 定义一个车(vehicle)基类,具有MaxSpeed、Weight等成员变量,Run、Stop等成员函数,由此派生出自行车(bicycle)类,汽车(motorcar)类。自行车(bicycle)类有高度(Height)等属性,汽车(motorcycle)类有座位数(SeatNum)等属性。从bicycle和motorcycle派生出摩托车(Motorcar)类,在继承过程中,注意把vehicle设置为虚基类。如果不把vehicle 设置为虚基类,会有什么问?编程实验及分析原因。 .代码: //man.cpp #include class vehicle { public: vehicle():MaxSpeed(0),Weight(0){ cout<<\构造函数运行~vehicle(){ cout<<\析构函数运行\bool Run(){ cout<<\bool Stop(){ cout<<\\
正在阅读:
c++实验报告04-14
保证书,保证书范文,保证书格式,保证怎么写,学生保证书02-08
我真的已经长大了作文07-03
中西方神话的相同点和不同点06-11
吃醋的典故02-19
安置规划说明书2014.11.20doc - 图文11-19
2011年修缮、装修、装饰工程类项目预选供应商采购公开招标10-30
2022-2022年中国硼铁行业市场发展态势及投资前景可行性报告(目录04-10
实验一 不同给药途径对药物作用的影响10-28
潮汕丧葬礼俗“做功德”07-11
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- c++
- 实验
- 报告
- 2018年人教版PEP四年级英语下册Unit1《My School》单元测试卷及
- 2014北外网院专升本入学考试英语题目汇编词汇和语法1(含答案)
- 不求甚解公开课优秀教案及教学设计
- 2.5.1.1菏泽市牡丹区技工学校专业教学团队规程
- 四年级下册信息技术
- 浙江专版18年高中生物第四章生物的变异第二节生物变异在生产上的
- CVD技术
- 受送电方案
- 计算机辅助设计在设计施工中的应用与探索
- 质量意识培训试题
- 国家林业局关于印发《中央财政造林补贴试点检查验收管理办法(试
- Oracle权威资料 - EBS - 基础设置全手册 - 图文
- 2018年10月邮政工人入党志愿书
- 浅谈中小企业融资中存在的问题及对策
- 电能计量复习试题及答案
- 2010-2011物理化学试卷B卷及答案
- 网站建设管理与维护题库
- 二年级奥数题 有答案
- 2018全国独立艺术类院校各专业录取分数线 - 图文
- 造纸白水特性