C 的 OO 思想
OO 思想就是面向对象思想
一个再复杂的模型都是由千千万万的对象组成,就是根本思想。
世界上的所有事物都可以看做是对象,二对于每个对象,抽想起来均可以分为两个要素,属性和行为。
面向对象,不再是面对一个个函数和变量,要放眼大局,面对一个个对象来看问题。
封装思想
把对象的属性结合成一个独立的系统。
抽象
对一个具体问题进行概括的过程,例如,面包房提供一个抽象---“订单”
继承
子类有其基类(父类)的全部属性和方法,称为继承。
多态
在基类中定义的属性和行为被继承后,可以具有不同的数据类型或表现行为等特性。在共性中寻找个性。
C 与 C 的联系
如下程序
代码语言:javascript复制#include <stdio.h>
int addArray( int array[], int n);
int main()
{
int data[] = {0,1,2,3,4,5,6,7,8,9};
int size = sizeof(data) / sizeof(data[0]);
printf("the size of data is : %dn", sizeof(data));
printf("the ret is : %dn",addArray( data, size ));
return 0;
}
int addArray( int array[], int n)
{
int sum = 0;
int i;
printf("the size of array is : %dn", sizeof(array));
for( i = 0; i < n; i )
{
sum = array[i];
}
return sum;
}
结果是
代码语言:javascript复制the size of data is : 40
the size of array is : 8
the ret is : 45
为什么不同呢?
因为`array``那个地方是把他当成了一个指针,8个字节,它不会把整个数组传进来,而是只传递一个地址,这样效率高。
那么将这个程序转化为c ,下面这个就是c 版本
代码语言:javascript复制#include <iostream>
using namespace std;
int addArray( int *array, int n);
int main()
{
int data[] = {0,1,2,3,4,5,6,7,8,9};
int size = sizeof(data) / sizeof(data[0]);
cout << "the size of data is" << addArray( data, size ) << endl;
return 0;
}
int addArray( int *array, int n)
{
int sum = 0;
int i;
for( i = 0; i < n; i )
{
sum = array[i];
}
return sum;
}
这个程序展示了C 对象的第一次使用,这个对象就是cout
cout
是输出流对象,是 console out (控制台输出)的缩写,属于 basic_ostream 类的对象,而 ostream 类在中定义。
using namespace std;
C 标准库所使用的所有标识符,都是在同一个特殊的名字空间 (std) 中来定义的。与java中的包
概念是一样的。
如果没有这句话,我们将这样使用 std::cout
来调用输出流对象。
一般这句话都是给程序员偷懒用的。
<<
体现了 C 的特点,可以支持重载。
c 和 c 的简单转换
先看 c 版本,可以输入一串字符,以空格为隔,然后得出它们相加后的结果
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
int sum = 0;
char ch;
printf("please type a list of number and space : ");
while(scanf("%d", &i) == 1)
{
sum = i;
while( (ch=getchar()) == ' ' ) // 屏蔽空格
;
if( ch == 'n' )
{
break;
}
ungetc( ch,stdin ); // 将变量 出中存放的字符退回给stdio输入流
}
printf("the ret is : %d", sum);
printf("n");
return 0;
}
这是 c 版本的
代码语言:javascript复制#include <iostream>
using namespace std;
int main()
{
int sum = 0;
cout << "please type a list of number and space : ";
int i;
while(cin >> i)
{
sum = i;
while( cin.peek() == ' ' )
{
cin.get();
}
if( cin.peek() == 'n' )
{
break;
}
}
cout << "the ret is : " << sum << endl;
return 0;
}
使用了流对象 cin ,它知道如何从用户终端读取数据,
cin >> i
,这个有称为提取操作,一次从输入流对象中提取一个整数,当用户键盘输入时,对应字符将输入到操作系统的键盘缓存区中。
如果用户不进行操作,程序会进行阻塞。>>
最初被定义为右移操作符,这里进行了重载。
while(cin >> i)
中,表达式返回输入流本身,但如果到了文件尾或非法值,则返回 false 。
比如,在上面例子中,如果在程序运行时输入的是小数点,则会发生内部错误,返回0。
另外,c是在开头声明变量,而c 则允许我们在任意位置声明变量,大大提高了C 的可读性。
cin、cout 的多个方法
cin
是istream
类的对象
第一个使用例子
代码语言:javascript复制#include <iostream>
using namespace std;
// 这个程序可以忽略前 7 个字符
// 并且字符的长度最大是 9 个
int main()
{
char buf[20]; // 可以输入 19 个字符
cin.ignore(7);
cin.getline( buf, 10 );
cout << buf << endl;
return 0;
}
第二个使用例子
代码语言:javascript复制#include <iostream>
using namespace std;
// 这个程序会把你写的东西原封不动再给你打印一遍
int main()
{
char p;
cout << "type some text:" << endl;
while(cin.peek() != 'n' )
{
p = cin.get();
cout << p;
}
cout << endl;
return 0;
}
第三个小程序
代码语言:javascript复制#include <iostream>
using namespace std;
// 这个程序会提取前 20 个字符
int main()
{
const int SIZE = 50;
char buf[SIZE];
cout << "type some text:" << endl;
cin.read( buf, 20 );
cout << "the size of you type :" << cin.gcount() << endl;
cout << "what you type is :";
cout.write( buf,20);
cout << endl;
return 0;
}
文件和 IO
文件复制
先上来一个 c 语言版本的吧
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
using namespace std;
// 这个程序会复制两个文件
int main(int argc,char * argv[] )
{
FILE *in ,*out;
int ch;
if(argc != 3)
{
fprintf( stderr, "style: copyFile souer dest n");
exit( EXIT_FAILURE );
}
if( ( in = fopen( argv[1], "rb")) == NULL)
{
fprintf( stderr, "error of %s n", argv[1]);
}
if( ( out = fopen( argv[2], "wb")) == NULL)
{
fprintf( stderr, "error of %s n", argv[2]);
fclose( in ); // 必须记得关掉
exit( EXIT_FAILURE );
}
while( (ch = getc(in)) != EOF )
{
if( putc( ch,out ) == EOF )
{
break;
}
}
if( ferror( in ))
{
printf("read %s error", argv[1] );
}
if( ferror( out ))
{
printf("read %s error", argv[2] );
}
printf("successful");
fclose( in );
fclose( in );
return 0;
}
首先
int main(int argc,char * argv[] )
中 int argc
是几个参数
而 int * argv[]
的每个指针指向命令行的字符串,如
a.exe a.txt b.txt
这个就是 argv[] = {a.exe,a.txt,b.txt}
in 和 out 是我们声明的两个文件指针,为 IO 流对象使用
通过 fopen() 函数,我们以二进制形式按可读、可写的方式打开两个文件,并返回指针给 in 和 out 。
然后就是 c ,不过这个例子则只输出文件内容,而不是复制 运用的是 读取类 ifstream
代码语言:javascript复制#include <fstream>
#include <iostream>
using namespace std;
// 这个程序会复制两个文件
int main()
{
ifstream in;
in.open( "b" );
if( !in )
{
cerr << "open this error" << endl;
return 0;
}
char x;
while( in >> x )
{
cout << x;
}
return 0;
}
运用写入类 ofstream
代码语言:javascript复制#include <fstream>
#include <iostream>
using namespace std;
// 这个程序会创建一个文件 test.txt 并写入 0123456789
int main()
{
ofstream out;
out.open( "test.txt" );
if( !out )
{
cerr << "open this error" << endl;
return 0;
}
for( int i=0; i < 10; i )
{
out << i;
}
out << endl;
out.close();
return 0;
}
c 的文件操作
常见的打开模式
ios::in -- 打开一个可读取的文件 ios::out -- 打开一个可写入文件 ios::binary -- 以二进制形式打开一个文件 ios::app -- 写入的所有数据将追加其末尾 ios::trunk -- 删除文件原内容 ios::nocreat -- 如果要打开的文件不存在,则 open 函数无法进行 ios::noreplece -- 如果要打开的文件已经存在,试图用 open 函数打开时将返回一个错误。
使用打开模式的例子
代码语言:javascript复制#include <fstream>
#include <iostream>
using namespace std;
// 这个程序会在 test.txt 后追加 0123456789
int main()
{
ofstream out;
out.open( "test.txt" );
if( !out )
{
cerr << "open this error" << endl;
return 0;
}
for( int i=0; i < 10; i )
{
out << i;
}
out << endl;
out.close();
return 0;
}
或者
代码语言:javascript复制#include <fstream>
#include <iostream>
using namespace std;
// 这个程序会在 test.txt 后追加 ccgxk.com
int main()
{
fstream fp( "test.txt" , ios::app | ios::out );
if( !fp )
{
cerr << "open this error" << endl;
return 0;
}
fp << "ccgxk.com";
static char str[10];
fp.seekg(ios::beg); // 让文件指针指向文件头,而 ios::end 则是文件尾
fp >> str;
cout << str << endl;
fp.close();
return 0;
}
为什么可以用 ios::app | ios::out ? 因为 ios::app = 0x1 ios::out = 0x2....它们相加不会互相干扰
小结
代码语言:javascript复制#include <fstream>
#include <iostream>
// 这个程序简单演示一个输入输出
int main()
{
char answer;
std::cout << "can you yes?(y/n)"<<"n";
std::cin >> answer;
switch(answer)
{
case 'y':
case 'Y':
std::cout << "successful" << "n";
break;
case 'n':
case 'N':
std::cout << "error" << "n";
break;
default:
std::cout << "warning!" << "n";
break;
}
std::cin.ignore(100,'n'); // 加上这两行,就能阻塞了
std::cin.get();
return 0;
}
代码语言:javascript复制#include <iostream>
// 这个程序简单演示一个华氏温度与摄氏温度的相互转换
int main()
{
// 华氏温度 == 摄氏温度 X 9.0 ÷ 5.0 32
const unsigned short ADD_SUBTRACT = 32 ;
const double RATIO = 9.0 / 5.0 ;
double tempIn , tempOut;
char typeIn, typeOut;
std::cout << "type a temp";
std::cin >> tempIn >> typeIn;
std::cin.ignore(100, 'n');
std::cout << "n";
switch (typeIn)
{
case 'C':
case 'c':
tempOut = tempIn * RATIO ADD_SUBTRACT;
typeOut = 'F';
typeIn = 'c';
break;
case 'F':
case 'f':
tempOut = (tempIn - ADD_SUBTRACT) / RATIO;
typeOut = 'C';
typeIn = 'F';
break;
default:
tempOut = 'E';
break;
}
if( typeOut != 'E')
{
std::cout << tempIn <<typeIn
<< " = " << tempOut
<< typeOut << "nn";
}
return 0;
}
函数的重载
有着同样的名字,却有不同参数,相同用途的函数。
重载函数,可以简化编程工作和提高代码可读性。它不是一个面向对象的特征。只是简化编程工作的一种方案。简化工作也是C 的一个追求。
重载一定要谨慎,不要无的放矢,乱点鸳鸯。
我们只能通过不同的参数进行重载,但不能通过不同的返回值。
通过下面这个例子来说明一个简单的重载
代码语言:javascript复制#include <iostream>
void convertTemperature( double tempIn, char typeIn);
void convertTemperature( int tempIn, char typeIn);
// 这个程序简单演示一个华氏温度与摄氏温度的相互转换
int main()
{
double tempIn ;
char typeIn;
int tempIntIn;
std::cout << "type a temp";
std::cin >> tempIn >> typeIn;
std::cin.ignore(100, 'n');
std::cout << "n";
convertTemperature( tempIn, typeIn);
std::cout << "type a temp(int)";
std::cin >> tempIntIn >> typeIn;
std::cin.ignore(100, 'n');
std::cout << "n";
convertTemperature( tempIntIn, typeIn);
return 0;
}
void convertTemperature( double tempIn, char typeIn)
{
// 华氏温度 == 摄氏温度 X 9.0 ÷ 5.0 32
const unsigned short ADD_SUBTRACT = 32 ;
const double RATIO = 9.0 / 5.0 ;
double tempOut;
char typeOut;
switch (typeIn)
{
case 'C':
case 'c':
tempOut = tempIn * RATIO ADD_SUBTRACT;
typeOut = 'F';
typeIn = 'c';
break;
case 'F':
case 'f':
tempOut = (tempIn - ADD_SUBTRACT) / RATIO;
typeOut = 'C';
typeIn = 'F';
break;
default:
tempOut = 'E';
break;
}
if( typeOut != 'E')
{
std::cout << tempIn <<typeIn
<< " = " << tempOut
<< typeOut << "nn";
}
}
void convertTemperature( int tempIn, char typeIn)
{
// 华氏温度 == 摄氏温度 X 9.0 ÷ 5.0 32
const unsigned short ADD_SUBTRACT = 32 ;
const double RATIO = 9.0 / 5.0 ;
int tempOut;
char typeOut;
switch (typeIn)
{
case 'C':
case 'c':
tempOut = tempIn * RATIO ADD_SUBTRACT;
typeOut = 'F';
typeIn = 'c';
break;
case 'F':
case 'f':
tempOut = (tempIn - ADD_SUBTRACT) / RATIO;
typeOut = 'C';
typeIn = 'F';
break;
default:
tempOut = 'E';
break;
}
if( typeOut != 'E')
{
std::cout << tempIn <<typeIn
<< " = " << tempOut
<< typeOut << "nn";
}
}
复杂的数据类型
复杂 == 简单 简单
数据类型亦是如此,面向对象更是如初。
分为三类,数组、指针、结构。
数组
优点是可以吧很多同类型的值储存到同一变量下。
一个例子,给定 10 个数,将它们加起来
代码语言:javascript复制#include <iostream>
// 这个程序可以得到
int main()
{
const unsigned short ITEM = 10;
int num[ITEM];
std::cout << "type " << ITEM << " int:nn";
for(int i = 0; i < ITEM ; i )
{
std::cout << "the " << i 1 << " num:";
while ( !(std::cin >> num[i]) ) // 如果用户输入非法字符,提示重新输
{
std::cin.clear();
std::cin.ignore( 100, 'n');
std::cout << "type the num again!";
}
}
int total = 0; // 必须为 0 ,否则会出错
for(int j = 0; j < ITEM; j )
{
total = num[j];
}
std::cout << "total is" << total << "n";
std::cout << "total(ping_jun) is" << (float)total/ITEM;
return 0;
}
一个简单的 string 字符串
代码语言:javascript复制#include <iostream>
#include <string>
// 这个程序可以输什么就什么
int main()
{
std::string str;
std::cout << "type some text:";
std::getline( std::cin, str);
std::cout << str;
return 0;
}
指针
地址是在内存里的地址。在c 中,变量类型是根据它们的自然边界进行对齐的。
一般32位操作系统的内存对齐值为 1000H = 4kb ,4kb 就是一页,64则是8kb,文件对齐值为 200H
变量的地址在程序执行期间是不会发生变化的。
一般我们这样声明 type * pointerName
int * p;
int pp = 123;
p = &pp;
注意:
int *p1,p2,p3
X // 这样只会将 p1 给设置为指针类型
int *p1,*p2,*p3
V
另外还有 void * p 这样的指针。
寻址
对于变量我们可以用两种方法进行索引,一是变量名,二是通过地址。
这里我们要用一个新的操作符,叫做取址的操作符 "&",它的作用就是获得变量的地址。
我们习惯这样使用
代码语言:javascript复制int var = 123;
std::cout << "Address is :" << &var;
解引用
std::cout << *aPointer ;
假设 aPointer 是变量 a 的指针,那么 aPointer 和 a 它们表示的将是同一个值,因此 aPointer = 123 ;
实例
代码语言:javascript复制#include <iostream>
#include <string>
// 这个程序是指针的使用实例
int main()
{
int a = 123;
float b = 3.14;
char c = 'C';
unsigned long d = 19880808;
std::string e = "I Love Kohunglee";
std::cout << "a is" << a << "n";
std::cout << "b is" << b << "n";
std::cout << "c is" << c << "n";
std::cout << "d is" << d << "n";
std::cout << "e is" << e << "n";
int *aP = &a;
float *bP = &b;
char *cP = &c;
unsigned long *dP = &d;
std::string *eP = &e;
*aP = 1234;
*bP = 3.1415;
*cP = 'D';
*dP = 120220223;
*eP = "I Love ccgxk";
std::cout << "a is -" << a << "n";
std::cout << "b is -" << b << "n";
std::cout << "c is -" << c << "n";
std::cout << "d is -" << d << "n";
std::cout << "e is -" << e << "nn";
return 0;
}
星号的两种用途
第一种是创建指针, [ex] int *myP = &myInt;
第二种是对指针进行解引用 [ex] *myP = 3998
群 P 和无类型指针
C 允许群 P ,一个变量可以有多个指针
也支持无类型指针
void *vP
注意,一个无类型指针解引用前,要先将它转化为一种适当的数据类型
指针和数组
数组有很多个地址,但是指针指向的是数组是基地址,也就是第一个元素的地址。
如果想通过指针访问其他数组的元素,应该怎么办呢?它是按照数据类型来进行 的
这是一个例子
代码语言:javascript复制#include <iostream>
#include <string>
// 这个程序是数组指针的使用实例
int main()
{
const unsigned short ITEMS = 5;
int intArray[ITEMS] = {1,2,3,4,5};
char charArray[ITEMS] = {'c','c','g','x','k'};
int *intPrt = intArray;
char *charPrt = charArray;
std::cout << "array out : " << "n";
for( int i= 0; i < ITEMS; i )
{
std::cout << *intPrt << " at " << intPrt<< 'n';
intPrt ;
}
std::cout << "array2 out : " << 'n';
for( int i= 0; i < ITEMS; i )
{
std::cout << *charPrt << " at " << charPrt<< 'n';
charPrt ;
}
return 0;
}
结构
是程序员定义的数据类型,structure
就是结构体,里面的成员是没有限制的。
example
代码语言:javascript复制struct test
{
std::string:: name;
}; // 切记,这里有一个分号
结构与指针
指针指向第一个元素的地址。
指针类型必须与指向地址的变量类型一样。
比如 test *pTest = &test
我们可以通过对指针进行解引用来访问响应的变量值。
譬如 (*pJiayu).name = "newName";
或者 i.e.
pJiayu -> name = "newName";
传值、传址和传引用
一个通过解引用来交换两个变量的案例
代码语言:javascript复制#include <iostream>
#include <string>
void swap(int *x, int *y );
void swap2(int *x, int *y );
// 这个程序是指针的交换地址实例
int main()
{
int x ,y;
std::cout << "type two num(x,y):";
std::cin >> x >> y;
swap2( &x, &y );
std::cout << "the swap ret is : x=" << x << " y=" << y << "n";
}
// 通过第三方中介变量 temp 来替换
void swap(int *x, int *y )
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
// 通过异或
void swap2(int *x, int *y )
{
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
/*
swap2 的原理
比如
*x = 10101010
*y = 11100000
xor= 01001010
*/
引用传递
c 完善了地址的这个概念
根据上面那个案例,我们可以直接这样写那个函数
代码语言:javascript复制swap( &x, &y ); --变成-->
swap( x, y );
void swap(int *x, int *y ); --变成-->
void swap(int &x, int &y );
联合、枚举和类型别名
联合
联合与结构很像,但它每次只能储存这些值中的某一个,存另一个值时会把上一个值覆盖掉。
创建
代码语言:javascript复制union password
{
unsigned long birthday;
unsigned short ssn;
char *pet;
};
使用
代码语言:javascript复制password psw_1;
psw_1.birthday = 20011206;
psw_1.pet = "bengbeng"; // 此时 psw_1.birthday 会被丢弃(覆盖)
枚举
枚举类型用来创建一个可取值的列表
enum weekdays{ one, two, three, four, five};
一个例子
代码语言:javascript复制#include <iostream>
#include <string>
// 这个程序是枚举的应用实例
int main()
{
enum week { mon, tues, wendnes, thurs, fri};
week today;
today = mon;
std::cout << "today = " << today << "n";
today = tues;
std::cout << "today = " << today << "n";
}
枚举的好处
- 对变量的可取值加以限制
- 可以用作 Switch 条件语句的 case 标号
类型别名
可以为类型创建一个别名
typedef int* intPointer;
自此后,可以像下面这样来定义整型指针了。
intpointer myPointer;
对象
对象与结构体的区别
对象的内部可以有变量和函数,而结构通常只能由各种变量构成。
创建一个类
OOP 的过程的第一步是创建一个类,每个类和变量一样都有一个名字。
代码语言:javascript复制class Car
{
public:
std::string color;
std::string engine;
float gas_tank;
unsigned int wheel;
void fill_tank( float liter );
void running(void);=
};
void Car::fill_tank(float liter)
{
gas_tank = liter;
}
类名第一个字母采用大写是一种习惯。
有些程序员喜欢吧类的声明和函数的定义分别存入 .h 和 .cpp 文件内。
C 允许在类里声明常量,但不允许对它进行赋值。
你也可以在声明某个类的时候,创建一些该类的对象,但也应避免使用这种做法,就如下面一样。
代码语言:javascript复制class Car
{
} car1, car2;
当然,吧一个对象赋值给另一个同类对象时,将会自动使同名属性有同样的值,这是一个快捷操作。就像下面这样。
代码语言:javascript复制Car car1, car2;
car1.setColor("WHITE");
...
car1 = car2;
构造器
创建构造器
代码语言:javascript复制class Car
{
public:
std::string color;
std::string engine;
float gas_tank;
unsigned int wheel;
Car(void);
void fill_tank( float liter );
void running(void);=
};
Car::Car(void)
{
this -> color = red;
}
创建析构器
析构器是不带参数的,所以析构器的声明永远是如下格式
~ClassName();
类的继承
继承的语法
class SubClass : public SuperClass {...}
class Pig : public Animal {...}