C++快速入门学习笔记(一)

一、C++语言与面向对象思想介绍

C++是一种语言,仅仅是它的语法、特性、标准类库就已经是一门非常高深的课程,所以在开始学习的时候,必项先要打好基础。
C语言有一个优点,即它的速度可以很快。写出来的程序可以很精练、简单、小巧,如果将C和C++相比较,C++就经常会为了解决某个问题绕一个大圈,所以代码量相对较大,并且里边有一些不是必项的代码。但C++有个绝对的优势,就是针对不同的对象去做实例化,这就是所谓的OO(Object Oriented:面向对象)思想。
在面对对象的思想中,任何事物都可以被看做一个对象。一个再复杂的模型结构都是由千千万万个对象组成的。这是根本思想。
例如:地球可以看做一个相当复杂的对象地球.上的每种动物、每种植物、空气、水、土壤等都是对象。
它们彼此之间互相联系、组合最终形成了地球。而对于每个对象,抽象起来最终可以分为两个要素:属性和行为。
在面对对象编程中,程序员不再面对一个个函数和变量,而是要放眼大局,面对一个个对象来看问题。
每个对象都是一个完整的独立的个体,它是由相关的属性和行为组合,与外界分隔。
例如,一家跨国公司,它在全球有很多分公司,每个公司都有自己的员互和一套自己的运作体系。对于总公司而言,分公司好比“黑箱”总公司不需干预分公司如何调配人员工作等,只需下达命令和指标。
面向对象是当前软件开发方法的主流,其概念和应用早已超越了程序设计和软件开发本身而扩展到更加宽广的范围。
举个例子,例如我们的图形用户界面(GUI)编程,面向对象在这方面的能力是相当出众,以至于人们在那些并不直接支持面向对象的语言(例如C)中也创建了一些模拟OO的软件结构,以便进行GUI编程。典型例子:GNOME/GTK+工具。
面向对象的4个特点:
1.封装
封装意味着把对象的属性和方法结合成一个独立的系统单位,并尽可能隐藏对象的内部细节。封装是面向对象思想描述的基础,从此程序员面对的就不再是许多复杂的函数和过程实现,而是少数具有行为能力的个体实例。
2.抽象
抽象的过程是对具体问题进行概括的过程, 是对一类公共问题进行统一描述的过程。为了使某些必要的信息得以顺利的交流,设计者必项制定一个抽象,就如同切个协议,个得到所有参与活动的有效个体支持的协议。
3.继承
子类对象拥有与其基类相同的全部属性和方法,称为继承。
4.多态
多态是指在基类中定义的属性和行为被子类继承后,可以具有不同的数据类型或者表现行为等特性。
下节课,我们将通过实例对t比C和C++在编程上的差异来进行学习。

二、从一个小程序说起

问题:对一个整型数组求和。
要求:定义一个存储着n个元素的数组,要求用C语言完成这个任务。

#include <stdio.h> 
int main(){
	int data[]={0,1,2,3,4,5,6,7,8,9};
	int size = sizeof(data) / sizeof(data[0]);
	printf("data size是: %d\n",  sizeof(data));//size is 40
	printf("结果是: %d\n", addArray( data, size ));
	return 0;
}
int addArray( int array[], int n ){
	int sum = 0;
	int i;
	printf("array size是: %d\n",  sizeof(array));//size is 4
	for(i=0;i<n;i++)
		sum += array [i] ;
	return sum; //45
}

思考:
数组名与指针的关系:在《C专家编程》中作者采用大篇幅对C的数组和指针作了对比来跟我们说数组和指针他们的关系很复杂。数组名就是传递指针,因为传递一个首地址不会浪费空间。

//上面参数data数组名就相当于是数组的首地址,使用指针得到相同的结果
int addArray( int *array, int n ){
	int sum = 0;
	int i;
	for(i=0;i<n;i++)
		sum += *array++ ;//地址偏移
	return sum; //45
}

以上两个程序从本质上来说是相同的,我们得出结论:将函数的参数声明为数组和声明为指针是一样的。现在我们来考虑一个新的问题:将该程序用C++来实现试试。

#include <iostream>
using namespace std;
 //上面参数data数组名就相当于是数组的首地址,使用指针得到相同的结果
int addArray( int array[], int n ){
	int sum = 0;int i;	 
	for(i=0;i<n;i++)
		sum += array[i] ;//地址偏移
	return sum; //45
}
int main(){
	int data[]={0,1,2,3,4,5,6,7,8,9};
	int size = sizeof(data) / sizeof(data[0]);
	cout << "结果是: " <<  addArray( data, size ) << endl;
	return 0;
}

程序分析
这个小程序展示了C++对象的第一次使用。对象在哪里只就是这个cout。cout是一个输出流对象,它是”’console out(控制台输出)“的缩写。 是属于basic_ ostream 类的对象。ostream类在iostream头文件中定义。注意
什么是输出流?其实也就是一个概念,在C++中引入了很多类似的概念:例如数据可以从键盘流入程序,又可以从程序流向屏幕、打印机等~
using namesapce std;
这条指令带给我们一个全新的概念:名字空间。就是C++标准库所使用的所有标识符(即类、函数、对象等的名称)都是在同一个特殊的名字空间(std)中来定义的。
如果我们没有使用这条指令,我们将需要使用std::cout这样的语法来调用输出流对象。
不知道大家有没有注意到“<<” 这个符号很眼熟。这个操作符不是C语言里边位运算的左移操作符吗?难道在C++里边改规则了事实上不是这样的,它只不过体现了C++的另一个特点:支持重载。重载事实上就是允许我们按照不同的方式使用同一个操作符。我们后边会详细讲解。
课后思考
要求:编写一个程序,要求用户输入一串整数和任意数目的空格,这些整数必项位于同一行中,但允许出现在该行中的任何位置。当用户按下键盘上的“Enter” 键时,数据输入结束程序自动对所有的整数进行求和并打印出结果。

#include <stdio.h>
#include <stdlib.h>
void main(){
      int i;
      int sum = 0;
      char ch;
      printf("请输入一串整数和任意数目的空格: ");
      while( scanf("%d", &i) == 1 ){
            sum += i;
            while( (ch=getchar()) == ' ' ) ;
            if( ch == '\n' )  {
                  break;
            }
            ungetc( ch, stdin );
      }     
      printf("结果是: %d", sum);
}

c++版除了输入输出整体没有变化

#include<iostream>
using namespace std;
int main(){
    int i,sum=0;
    char c;
    cout<< "input num:";
    while(cin >> i){
        sum += i;
        while((c = getchar()) == ' ') ;
        if(c == '\n'){
            break;
        }
        ungetc(c,stdin);
    }
    cout<<"sum is"<<sum << endl;
    return 0;
}
  • 这个程序使用了流对象 cin。这个对象的类型是istream,它知道如何从用户终端读取数据。
  • cin >> i; cin 输入操作符又称为提取操作符,它一次从输入流对象cin 提取一个整数。
  • 当用户进行键盘输入时,对应的字符将输入到操作系统的键盘缓冲区中。
  • 这样,当用户点击键盘上的“Enter”键时,操作系统把键盘缓冲区的内容传输到cin流的内部缓冲区,“>>”操作符随后从这个缓冲区提取需要的信息。
  • 我们还发觉,如果用户不进行键盘的输入,程序将会阻塞。这是阻塞性(blocking)IO的例子。
  • “>>”最初定义为右移操作符,它在C++ 中进行了重载,当它按照这里所示的方式使用时,它就用于从输入流对象提取信息。另外,这个操作符对所有内建的数据类型都进行了重载,所以它可以从输入流对象提取出int, float, double 型数据,也可以提取字符串等数据。
  • 在while( cin >> i ) 中,表达式 cin >> i 返回输入流对象本身,也就是cin。但是,如果到达了文件尾或者提取操作符遇到一个非法值,这个返回值将是 false。
  • 我们小举一个例子:如果我们试图把一个浮点数读取到一个 int 型变量中,当提取操作符遇到小数点时,它将使输入流对象置于一个错误的状态中,导致 cin 的值为 false。
  • C 和 C++事实上在声明变量的事实上有点不同,C++允许我们在程序的任意位置声明变量。这大大的提高了大型C++ 程序的可读性

cin、cout对象的多个方法

例子一:cin.ignore() & cin.getline()

#include <iostream>
using namespace std;
int main(){
      char buf[20];
      cin.ignore(7); //忽略了前7个字符
      cin.getline( buf, 10 ); //取10个字符进buf数组,结尾是'0'
	 //输入了12345678901234562890
      cout << buf << endl; //890123456 输出了9个,因为结尾有0	
      return 0;
}

例子二:cin.get() & cin.peek()

int main(){
	char p;
	cout << "请输入一段文本: \n";
	while( cin.peek() != '\n' ){ //输入进流对象直到换行
		p = cin.get(); //得到字符放入p中
		cout << p;  //输入:123,输出:123
	}
	cout << endl;
	return 0;
}

例子三:cin.gcount() & cin.read()

int main(){
      const int SIZE = 50;
      char buf[SIZE];     
      cout << "请输入一段文本: ";
      cin.read( buf, 20 ); //读取20个字符
      cout << "字符串收集到的字符数为: " 
           << cin.gcount() << endl; //收集到字符个数
      cout << "输入的文本信息是: ";
      cout.write( buf, 20 ); //输出20个字符
      cout << endl;
      return 0;
}

在这里插入图片描述
实例一:cout.precision()

int main(){
      double result = sqrt(3.0);
      cout << "对 3 开方保留小数点后0 ~ 9 位,结果如下: \n" << endl;
      for( int i=0; i <= 9; i++ ){
            cout.precision(i);
            cout << result << endl;
      }
      cout << "当前的输出精度为: " << cout.precision() << endl;
      return 0;
}

在这里插入图片描述

实例二:cout.width()

int main(){
      int width = 4;
      char str[20];
      cout << "请输入一段文本: \n";
      cin.width(5);
      while( cin >> str ) {
            cout.width(width++);
            cout << str << endl;
            cin.width(5);
      }
      return 0;
}

在这里插入图片描述

三、另一个小程序

文件 I/O

编程任务:编写一个文件复制程序,功能实现将一个文件复制到另一个文件。
例如:fileCopy sourceFile destFile

#include <stdio.h>
#include <stdlib.h>
int main( int argc, char* argv[] ){
	  //声明的两个文件指针,它们的类型都是 FILE*,分别作为两个 I/O 流对象使用
      FILE *in, *out;//
      //getc() 的返回值是 int 类型哦,所以我们声明时应该是 int ch。而不是char ch
      int ch;  //  
      if( argc != 3 ){//确保程序参数个数的正确性
            fprintf( stderr, "输入形式: copyFile 源文件名 目标文件名 \n" );
            exit( EXIT_FAILURE );
      }
      //以二进制的形式按可读/可写方式打开两个文件并返回两个文件指针给 in 和 out
      //如果未成功打开,我们就向标准错误流 stderr 发送一条消息
      if( ( in = fopen( argv[1], "rb") ) == NULL ){
            fprintf( stderr, "打不开文件: %s \n", argv[1] );
            exit( EXIT_FAILURE );
      }
      if( ( out = fopen( argv[2], "wb") ) == NULL ){
            fprintf( stderr, "打不开文件: %s \n", argv[2] );
            fclose( in );        // 记得擦屁股
            exit( EXIT_FAILURE );
      }
      //getc函数一次从输入流(stdin) 读取一个字符 
      //getc() 遇到文件结束标志的时候返回eof 
      //EOF 是一个宏,在stdio.h中定义,其值为一个负整数,通常是 -1
      while( (ch = getc(in)) != EOF ){ // EOF == end of file    
      		//putc() 函数把这个字符写入到输出流(stdout)
            if( putc( ch, out ) == EOF ){
                  break;
            }
      }
      //EOF 事实上有两个含义,一个是读取结束,一个是读取错误,所以这里加了条件
      if( ferror( in ) ) {
            printf("读取文件 %s 失败! \n", argv[1] );
      }
      if( ferror( out )) {
            printf("写入文件 %s 失败! \n", argv[2] );
      }
      printf("成功复制1个文件!\n");
      fclose( in );
      fclose( out ); 
      return 0;
}
  • argc 与 argv[] 在程序中,main 函数有两个参数,整型变量 argc 和字符指针数组 argv[]。
  • argc 的含义是程序的参数数量,包含本身。 argv[] 的每个指针指向命令行的一个字符串,所以argv[0]指向字符串"copyFile.exe"。argv[1] 指向字符串sourceFile,argv[2] 指向字符串destFile。

C++的文件操作

#include <fstream>
#include <iostream>
using namespace std;
int main(){
      ifstream in;     
      in.open( "test.txt" ); // ifstream in( "test.txt" );
      if( !in ) {
           cerr << "打开文件失败" << endl;
		return 0;
      }
      char x;
      while( in >> x ){ //文件输入流读取文件内容
            cout << x;
      }
      cout << endl;
      in.close();
      return 0;
}

上边的例题我们用到的是文件的读取类 ifstream。
接着我们结合例题来说说文件的写入要用到的类 ofstream

int main() {
      ofstream out;
      out.open( "test.txt" ); // ofstream out( "test.txt" );
      if( !out )  {
            cerr << "打开文件失败!" << endl;
			return 0;
      }
      for( int i=0; i < 10; i++ ) {
            out << i;  //写入内容到文件输出流
      }     
      out << endl;
      out.close();
      return 0;
}
  • ifstream in( “test.txt” ); 和 ofstream out( “test.txt” ); 可以替换上面打开文件的方法 以上代码在创建一个ifstream 和ofstream 类的对象时,将文件的名字传递给它们的构造函数。
  • 暂时我们可以这么理解构造函数:就是对象默认使用的函数(方法).
  • 事实上它还可以接受不止一个参数! 下边我们给出一个接受两个参数的实例:
    ifstream in( char* filename, int open_mode) 其中,filename表示文件的名称,它是一个字符串; open_mode 表示打开模式,其值用来定义以怎样的方式打开文件(跟open的参数一样)
  • 下面给出几种常见的打开模式:
    ios::in – 打开一个可读取文件
    ios::out – 打开一个可写入文件
    ios::binary – 以二进制的形式打开一个文件。
    ios::app – 写入的所有数据将被追加到文件的末尾
    ios::trunk – 删除文件原来已存在的内容
    ios::nocreate – 如果要打开的文件并不存在,那么以此参数调用open 函数将无法进行。
    ios::noreplece – 如果要打开的文件已存在,试图用open 函数打开时将返回一个错误。
ofstream out( "test.txt", ios::app ); //换行追加模式打开
  • 如果我需要的不只是一种打开模式,要多种并存怎么办呢? 我们只需要使用 OR 操作符:“|”
int main(){
      fstream fp("test.txt", ios::in | ios::out );
      if( !fp ) {
            cerr << "打开文件失败!" << endl;
            return 0;
      }
      fp << "IloveFishc.com!";
      static char str[100]; 
      fp.seekg(ios::beg);  // 使得文件指针指向文件头 ios::end 则是文件尾。
      fp >> str;
      cout << str << endl;
      fp.close(); 
      return 0;
}

c++版完整复制文件

int main(int count , char* arg[]){ 
	if(count != 3){
		cerr << "参数不正确!输入形式: copyFile 源文件名 目标文件名 \n" << endl;
		return 0;
	}
	fstream is(arg[1], ios::in);
	fstream os(arg[2], ios::out);
    if( !is ) {
		cerr << "打开文件失败!" << arg[1] << endl;
		return 0;
    }
	if( !os ){
		cerr << "打开文件失败!" << arg[2] << endl;
		is.close();
		return 0;
	}
	char ch;
	while(is >> ch){
		os << ch;
	};
	is.close();
	os.close();	
	return 0;

四、输入输出小结

题目:这个程序将向用户提出一个“Y/N”问题,然后把用户输入的值赋值给answer变量。
要求:针对用户输入‘Y’或‘y’和‘N’或‘n’进行过滤;发掘程序可能存在的任何问题,想想为什么?
如果是在exe运行的话,必须加上后三行,否则程序会一闪而过

#include <iostream>
int main(){
      char answer;
      std::cout << "可以格式化您的硬盘吗?[Y/N]";
      std::cin >> answer;
      std::cout << "\n";
      switch( answer ) {
	      case 'Y':
	      case 'y':
	            std::cout << "我想还是别了, 数据丢了妈妈会骂的~" << "\n";
	            break;
	      case 'N':
	      case 'n':
	            std::cout << "您的选择是正确的, 硬盘不能随便给人格式化!" << "\n";
	            break;
	      default:
	            std::cout << "请输入[Y/N]回答问题。" << "\n";
	            break;
      }
      std::cout << "输入任意键结束程序!" << "\n";
      std::cin.get();
      std::cin.ignore(100, '\n');//忽略前100个字符,直到遇到换行
      return 0;
}

题目:编写一个“温度单位转换程序”,提示用户以【xx.x C】或【xx.x F】的格式输入。
要求:如果用户输入的是34.2 C 程序将自动转换为 90.32 F 并输出!

#include <iostream>
int main(){     
      const unsigned short ADD_SUBTRACT = 32;
      const double RATIO = 9.0 / 5.0;     
      float tempIn, tempOut;
      char typeIn, typeOut;
      std::cout << "请以【xx.x C】或【xx.x F】的形式输入温度: ";
      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:
            typeOut = 'E';
            break;                  
      }
      if( typeOut != 'E' ){
            std::cout << tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
      } else{
            std::cout << "请按照给出格式输入!" << "\n\n";
      }
      std::cout << "请输入任意字符结束!" << "\n";
      std::cin.get();
      return 0;
}

对输入数据进行合法性检查

 while( !(std::cin >> tempIn) ) {
         std::cin.clear();
         std::cin.ignore(100, '\n');
         std::cout << "请以【xx.x】的形式输入温度: ";
 }
 do{
        std::cout << "请以【xx.x C】或【xx.x F】的形式输入温度: ";
        std::cin >> typeIn;
 }while( (typeIn != 'C') && (typeIn != 'c') && 
        (typeIn != 'F') && (typeIn != 'f')  );

cin 对象有几个专门用来报告其工作情况的成员函数,它们将返回一个真/假值来表明cin 的状态。

  • eof():如果到达文件(或输入)末尾,返回true;
  • fail():如果cin 无法工作,返回true;
  • bad():如果cin因为比较严重的原因(例如内存不足)而无法工作,返回true;
  • good():如果以上情况都没发生,返回true。

五、函数的重载

实质就是用同样的名字再定义一个有着不同参数但有着同样用途的函数。

#include <iostream>
void convertTemperature(double tempIn, char typeIn);
void convertTemperature(int tempIn, char typeIn);
int main()
{
    double tempIn;
    int tempInInt;
    char typeIn;
    std::cout << "请以【xx.x C】或【xx.x F】的形式输入温度: ";
    std::cin >> tempIn >> typeIn;
    std::cin.ignore(100, '\n');
    std::cout << "\n";
    convertTemperature(tempIn, typeIn); //浮点型输入输出
    std::cout << "请以【xx C】或【xx F】的形式输入温度: ";
    std::cin >> tempInInt >> typeIn;
    std::cin.ignore(100, '\n');
    std::cout << "\n";
    convertTemperature(tempInInt, typeIn);//整型输入输出
    return 0;
}
void convertTemperature(double tempIn, char typeIn)
{
    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:
        typeOut = 'E';
        break;
    }
    if (typeOut != 'E')
    {
        std::cout << tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
    }
    else
    {
        std::cout << "请按照给出格式输入!"
                  << "\n\n";
    }
    std::cout << "请输入任意字符结束!"
              << "\n";
    std::cin.get();
}
void convertTemperature(int tempIn, char typeIn)
{
    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:
        typeOut = 'E';
        break;
    }
    if (typeOut != 'E')
    {
        std::cout << tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
    }
    else
    {
        std::cout << "请按照给出格式输入!"
                  << "\n\n";
    }
    std::cout << "请输入任意字符结束!"
              << "\n";
    std::cin.get();
}

有以下几点需要大家注意的:

  • 对函数(方法)进行重载一定要谨慎,不要“无的放矢”或“乱点鸳鸯”;
  • 要知道重载函数越多,该程序就越不容易看懂;
  • 注意区分重载和覆盖(覆盖后边我们会讲到);
  • 我们只能通过不同参数进行重载,但不能通过不同的返回值(尽管后者也是一种区别);
  • 最后,对函数进行重载的目的是为了方便对不同数据类型进行同样的处理。

六、复杂的数据类型

数组

  • 在C 语言里,字符串被实际存储在一个字符数组中。
  • 在C++ 中我们也可以用同样的方法实现,但C++ 提供了更好的std::string类型,所以我们不必再使用老式的C 方法咯。
  • 课堂练习:请分别尝试用C 和C++ 实现将用户输入的字符串打印出来
#include <iostream>
#include <string>
int main()
{
	std::string str;
	std::cout << "请随便输入一个字符串: ";
	std::getline(std::cin, str);//getline不会忽略空格
	std::cout << str << "\n";
	return 0;
}

指针

int a = 456;
char b = ‘C’;
int *aPointer = &a;
char *bPointer = &b;

这会让程序保留4个内存块,两个为变量保留,两个为指针保留。变量a 和变量b 里边存放的是变量的值; 两个指针变量存放着指针的值,这些值是其他变量的地址。如下图
在这里插入图片描述

  • 当我们知道了某个变量在内存中的地址(通过指针),就可以利用指针访问位于该地址的数据。
  • 这需要对指针进行“解引用(Dereference)”处理:即在指针名的前面加上一个星号(*)。 如: std::cout << *aPointer;
  • 这里我们来理解一下:把整数变量 a 的地址存储在 aPointer 指针里之后,*aPointer 和变量 a 将代表同一个值。
  • 因此: *aPointer = 123; 将会
    在这里插入图片描述
  1. 一定要牢记的事实:指针所保存的是内存中的一个地址。它并不保存指向的数据的值本身。因此,务必确保指针对应一个已经存在的变量或者一块已经分配了的内存。

  2. 星号有两种用途,时常困惑了初学者:
    第一种是用于创建指针:int *myPointer = &myInt;
    第二种是对指针进行解引用: *myPointer = 3998;

  3. C++ 允许多个指针有同样的值,随便修改一个其他两个都会变化
    int *p1 = &myInt;
    int *p2 = &myInt;

  4. C++ 支持无类型(void)指针,就是没有被声明为某种特定类型的指针,例如:void *vPointer;
    注意:对一个无类型指针进行解引用前,必须先把它转换为一种适当的数据类型

指针和数组

计算机把数组是以一组连续的内存块保存的,例如:int myArray[3] = {1, 2, 3};在内存中是类似于这种形式存储:
在这里插入图片描述

  • 这就说明了数组拥有很多个地址,每个地址对应着一个元素。可能你会觉得要用指针指向一个数组,需要用到很多指针变量?
  • 其实在C/C++中,事实远没有想象那么困难。数组的名字其实也是一个指针(指向数组的基地址,就是第一个元素的地址)。
  • 就刚才的例子,以下两句做同样的事情:
  • int *ptr1 = &myArray[0];
  • int *ptr2 = myArray;
  • 我们轻易的将数组的基地址用指针变量保存起来,那我们接着讨论第二个问题:如果我想要通过指针访问其他数组元素,应该怎么办?
  • 试试:ptr1++;
  • 指针运算的奇妙之处就在于,以上并不将地址值简单+1处理,它是按照指向的数组的数据类型来递增的,也就是 +sizeof(int)。
  • 我们用实例来演示一下这个特性
#include <iostream>
int main()
{
	const unsigned short ITEMS = 5;
	int intArray[ITEMS] = {1, 2, 3, 4, 5};
	char charArray[ITEMS] = {'F', 'i', 's', 'h', 'C'};
	int *intPtr = intArray;
	char *charPtr = charArray;
	std::cout << "整型数组输出: " << '\n';
	for( int i=0; i < ITEMS; i++ )
	{
		std::cout << *intPtr << " at " << intPtr << '\n';
		intPtr++;
	}
	std::cout << "字符型数组输出: " << '\n';
	for( int i=0; i < ITEMS; i++ )
	{
		std::cout << *charPtr << " at " << charPtr << '\n';
		charPtr++;
	}
	return 0;
}
  1. 重载
#include <iostream>
void print( int *pBegin, int *pEnd )
{
	while( pBegin != pEnd )
	{
		std::cout << *pBegin;
		++pBegin;
	}
}
void print( char *pBegin, char *pEnd )
{
	while( pBegin != pEnd )
	{
		std::cout << *pBegin;
		++pBegin;
	}
}
int main()
{
	int num[5] = { 0, 1, 2, 3, 4 };
	char name[5] = { 'F', 'i', 's', 'h', 'C' };
	print( num, num+5 );
	std::cout << '\n';
	print( name, name+5 );
	std::cout << '\n';
	return 0;
}
  1. 泛型程序设计
#include <iostream>
template <typename elemType>
void print( elemType *pBegin, elemType *pEnd )
{
	while( pBegin != pEnd )
	{
		std::cout << *pBegin;
		++pBegin;
	}
}
int main()
{
	int num[5] = { 0, 1, 2, 3, 4 };
	char name[5] = { 'F', 'i', 's', 'h', 'C' };
	print( num, num+5 );
	std::cout << '\n';
	print( name, name+5 );
	std::cout << '\n';
	return 0;
}

小总结

  • 指针运算的重要性在高级和抽象的程序设计工作中体现得更加明显(如刚才的泛型设计)。
  • 就目前而言,大家只需要记住数组的名字同时也是一个指向其第一个元素(基地址)的指针。
  • 数组可以是任何一种数据类型,这意味着我们完全可以创建一个以指针为元素的数组

结构

  • 结构(Structure)是一种由程序员定义的、由其他变量类型组合而成的数据类型。

  • 当需要处理一些具有多种属性的数据时,结构往往是很好的选择。

  • 定义一个结构的基本语法是:

struct  Student 
{
std::string name;
std::string uid;
char sex;          // F==Female, M==Male
}
  • 注意:C++对于一个结构所能包含的变量的个数是没有限制的,那些变量通常我们成为该结构的成员,他们可以是任意一种合法的数据类型。
  • 回到刚才的例题,在定义了一个结构之后,就可以使用如下所示的语法来创建该类型的变量了:
Student stu;  // 创建一个Student结构类型的stu
stu.name = “小明”; //用”.”对结构成员进行赋值
stu.uid =007;
stu.sex = ‘M’;
  • 如果我们在创建一个结构类型变量的时候就已经知道它各个成员相关的值,我们可以在声明新变量的同时进行赋值。
Student stu = { “小明”,007, ‘F’ }

结构和指针

  • 在C、C++里,指针无所不能,也可以指向结构,就像指向其他任何变量那样。
  • 但我们有一个问题是:怎样才能通过指针解引用该指向结构的各个成员?(或者说是通过指针访问各个成员的值)
  • 注意:因为指针的类型必须与指向的地址的变量的类型一致,所以pstu 指针的类型也是Student
  • 创建一个指向该结构的指针:
Student *pstu = &stu;
//可以通过对指针进行解引用来访问相应的变量值
(*pstu).name = “小明2;
(*pstu).id =008;
//第二种方法 和上面是一样的
pstu -> name = “小明3;
pstu -> id =009;
pstu -> sex = F;
std::cout << pstu -> name;
std::cout << pstu -> id;
std::cout << pstu -> sex;
  • 【课后练习要求】定义一个结构,至少存储:姓名、学号、性别,实现文件存储,可以打印到屏幕
#include <iostream>
#include <fstream>
struct student
{
	std::string name;
	std::string uid;
	char sex;
};
bool ReadStudent();
void RecordStudent();
bool WriteStudent(student *stu);
int main()
{
	int i;
	while (1)
	{
		std::cout << "请选择需要进行的操作: \n";
		std::cout << "1. 打印数据到屏幕\n";
		std::cout << "2. 录入数据\n";
		std::cout << "3. 退出程序\n";
		std::cin >> i;
		switch (i)
		{
		case 1:
			ReadStudent();
			break;
		case 2:
			RecordStudent();
			break;
		case 3:
			return 0;
		}
	}
	return 0;
}
bool ReadStudent()
{
	std::string info;
	std::ifstream in("test.txt");
	while (std::getline(in, info))
	{
		std::cout << info << "\n";
	}
	return true;
}
void RecordStudent()
{
	std::string name;
	std::string uid;
	char sex;
	std::cout << "输入姓名:" << std::endl;
	std::cin >> name;
	std::cout << "输入uid:" << std::endl;
	std::cin >> uid;
	std::cout << "输入sex:" << std::endl;
	std::cin >> sex;
	student f = {name, uid, sex};
	WriteStudent(&f);
}
bool WriteStudent(student *pstu)
{
	std::ofstream out("test.txt", std::ios::app);
	out << "\n"
		<< pstu->name << " " << pstu->uid << " " << pstu->sex;
	return true;
}

七、传值、传址、传引用

  • 在编写个人函数的时候,你将受到C++中一条基本原则的限制:在默认的情况下,参数只能以值传递的方式给函数。
  • 这句话的理解是:被传递到函数的只是变量的值,永远不会是变量本身。
#include <iostream>
void changeAge( int age, int newAge )
{
	age = newAge;
	std::cout << "In this , my age is " << age << "\n";
}
// 如果想要实现计划功能,需要怎么改?
main()
{
	int age = 24;
	std::cout << "My age is " << age << "\n";	
	changeAge( age, age+1 );	
	std::cout << "Now my age is " << age << "\n";
}
  • 绕开“值传递”问题的第一种方法是向函数传递变量的地址取代它的值。我们说C语言强大,有很大一部分就是在于他的灵活,他的灵活,有大一部分就是可以利用指针进行委婉地乱改。
  • 正如我们所理解的,想要获取某个变量的地址只需要在它前边加上一个“取地址”操作符(&)就行了。 那我们可以试着这样:changeAge( &age, age+1 )
void changeAge( int *age, int newAge )
{
	*age = newAge;
	std::cout << "In this , my age is " << *age << "\n";
}
main()
{
	int age = 24;
	std::cout << "My age is " << age << "\n";	
	changeAge( &age, age+1 );	
	std::cout << "Now my age is " << age << "\n";
}

注意:如果传过去的是地址,在函数中必须要通过“*”对指针进行解引用,除非你有其他用途。

void swap( int *x, int *y )
{
	int temp;
	temp = *x;
	*x = *y;
	*y = temp;
}
main()
{
	int x, y;	
	std::cout << "请输入两个不同的值:";	
	std::cin >> x >> y;	
	swap( &x, &y );	
	std::cout << "调换后输出:" << x << ' ' << y << "\n\n"; 
}

另外的一种不需要中介的互换方案,异或方式(加解密)

void swap( int *x, int *y )
{
	*x ^= *y;
	*y ^= *x;
	*x ^= *y;
}
main()
{
	int x, y;	
	std::cout << "请输入两个不同的值:";	
	std::cin >> x >> y;	
	swap( &x, &y );	
	std::cout << "调换后输出:" << x << ' ' << y << "\n\n"; 
}

引用传递

void swap( int &x, int &y )
{
	int temp;
	temp = x;
	x = y;
	y = temp;
}
main()
{
	int x, y;	
	std::cout << "请输入两个不同的值:";	
	std::cin >> x >> y;	
	swap( x, y );	
	std::cout << "调换后输出:" << x << ' ' << y << "\n\n"; 
}
  • 传址在我们看来已经是很不错,不过C++语言的大神们在完善的过程中完善了地址这个概念。
  • 设想:如果事先就知道某个函数的参数只能接受一个地址,能不能使用某种约定使得在调用该函数时不需要使用指针的语法呢?
  • 于是乎,以引用传递方式传递输入方式的概念因此而产生了。
  • 其实他跟我们这个传址的目的是一样的,都是把地址传递给函数,但语法不同更加容易使用了。

八、联合、枚举和类型别名

联合

联合与结构有很多相似之处,联合也可以容纳多种不同类型的值,但是它每次只能存储这些值中的某一个。
应用情况:设有若干个人员的数据,其中有学生和教师,学生的数据中包括:姓名、号码、性别、职业、班级。教师的数据包括∶姓名、号码、性别、职业、职务。可以看出﹐学生和教师所包含的数据是不同的,现要求把它们放在同一表格中。
在这里插入图片描述

#include <stdio.h>
struct{
	int num;
	char name[10];
	char sex;
	char job;
	union{
		int clas;
		char position[10];
	}category;
}person[2];

int main(){
	int i;
	for(i=0;i<2;i++){
		printf("please enter the data of person:\n");
		 scanf("%d %s %c %c",&person[i].num,&person[i].name,&person[i].sex,&person[i].job);
		if(person[i].job == 's')
			scanf("%d", &person[i].category.clas);
		else if(person[i].job == 't')
			scanf("%s", person[i].category.position);
		else
			printf("Input error!");
	}
	printf("\n");
	printf("No.  name      sex job class/position\n");
	for(i=0;i<2;i++){
		if (person[i].job == 's')
			printf("%-6d%-10s%-4c%-4c%-10d\n",person[i].num, person[i].name,
			person[i].sex, person[i].job, person[i].category.clas);
		else
			printf("%-6d%-10s%-4c%-4c%-10s\n",person[i].num, person[i].name,
			person[i].sex, person[i].job, person[i].category.position);
	}
	return 0;
}

枚举类型

在实际问题中,有些变量的取值被限定在一个有限的范围内。
例 一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。
如果把这些量说明为整型,字符型或其它类型显然是不妥当的。

设有变量a,b,c被说明为上述的weekday ,声明可以采用下述任一种方式:
enum weekday {sun,mon,tue,wed,thu,fri,sat };
enum Weekday a, b, c;
或者为∶
enum weekday { sun,mon,tue,wed,thu,fri,sat }a, b, c;
或者为∶
enum {sun,mon,tue,wed,thu,fri,sat }a,b, c;

注意:

在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。
应该说明的是,枚举类型是一种基本数据类型﹐不是一种构造类型,因为它不能再分解为任何基本类型。
在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。

编译中,对枚举元素按常量处理,故称枚举常量,它们不是变量,不能对它们赋值。
枚举元素作为常量,它你是有值的,编译按定义时的顺序使它们的值为0,1,…
枚举值可以用来作判断比较。
一个整数不能直接赋给一个枚举变量。

void main(){
	enum weekday{sun,mon,tue,wed,thu,fri,sat}a, b, c;
	a = mon;//a = 1;有些编译器不行
	b = thu;
	c = sat;
	printf("%d,%d,%d",a,b,c);//1,4,6
}

类型别名

typedef保留字,声明新的类型名来代替已有的类型名,为一个类型定义创建一个别名。
声明 INTEGER为整型 typedef int INTEGER
声明结构类型 typedef struct{int month ;int day ;int year;}DATE;

typedef struct{int month ;int day ;int year;}DATE;
void main(){
	DATE d;
	d.month = 12;
	d.day =4;
	d.year = 2030;
	printf("%d,%d,%d",d.year,d.month ,d.day);//2030,12,4
}

声明NUM为整型数组类型 typedef int NUM [ 100] ;
声明STRING为字符指针类型 typedef char* STRING;
声明 POINTER为指向函数的指针类型,该函数返回整型值。
typedef int(*POINTER)();

typedef int NUM[100];
void main(){
	NUM a = {0};
	printf("%d ",sizeof(a));//400
}

typedef char* P;
void main(){
	P p1;
	p1="I love China";
	printf("%s",p1);
}

typedef void (*F)();
void fun(){
	printf("I love China");
}
void main(){
	F f ; // void (*f)();
	f = fun; //相当于 f = &fun;
	f(); 
}

九、对象

  • 使用对象进行编程是C++的核心,也是我们常说的C++比C“高级”的重要根据之一。
  • 我们从一个类开始,首先类是一个模型。当我们为这个类创建实例的时候,也就是对象本身。
  • 这跟我们之前讲解的定义和使用结构的过程很相似,但是这更有扩展性和前瞻性。
  • 对于初学者而言,我们先给大家一个区别:对象的内部可以有变量和函数,而结构通常只由各种变量构成。

创建简单的类

class Car
{
public:	
	std::string  color;
	std::string  engine;
	unsigned int  gas_tank;
	unsigned int  wheel;
	
	void setColor(std::string col);	
	void setEngine(std::string eng);
	void setWheel(unsigned int whe);
	void fillTank(int  liter); // 方法的声明:方法是”加油”,参数是”公升”
	int running(void);
	void warning(void); 
}; 
  • 注意,类名的第一个字母采用大写是一种习惯的上的标准,但不是硬性规定。还有在类声明末尾,必须有一个分号,这一点跟C++结构情况相同。
  • 类由变量和函数组成,对象将使用那些变量来存储信息,调用那些函数来完成操作。所以人们常常会看到一些专门术语:类里边的变量成为属性,函数成为方法。注意,他们的本质没有改变。
  • 现在我们的 Car类有了方法,但是我们只有他的原型(声明),想要使用它,我们还需要对函数进行正式的定义(即告诉系统如何去实现它)。
  • 方法的定义通常安排在类声明的后面:
void Car::setColor(std::string col)
{
	color = col;
}

void Car::setEngine(std::string eng)
{
	engine = eng;	
}

void Car::setWheel(unsigned int whe)
{
	wheel = whe;
}

void Car::fillTank(int  liter)
{
	gas_tank += liter;
}

int Car::running(void)
{
	std::cout << "我正在以120的时速往前移动。。。越过那高山越过那河。。。\n";
	gas_tank--;
	std::cout << "当前还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";	
	 
	return gas_tank;	
}

void Car::warning(void) 
{
	std::cout << "WARNING!!" << "还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!";	
}
  • 我们发觉作用域解析操作符(::),作用是告诉编译器这个方法存在于何处,或者说是属于哪一个类。
  • 其实我们对这个不应该感到陌生,从开始就没有using namespace std;这样偷懒的做法,所以我们是std::cout。
  • 事实上std::cout所引用的是std里定义的cout,而std::string数据类型其实也是一个对象。我们一直在使用对象,只是自己还不知道罢了
  • 类似于使用结构的情况,可以在声明某个类的同时立刻创建一些该类的对象:
class Car
{
。。。。。。
} car1, car2;

这种做法在C++里是允许的。但作为一种良好的编程习惯,应该避免这种做法

Car  car1, car2;
car1.setColor(“WHITE”);
。。。。。。
car2 = car1;

把一个对象赋值给另一个同类的对象将会自动使同名的属性有同样的值。

十、构造器和析构器

定义构造器

构造器和通常方法的主要区别:

  • 构造器的名字必须和它所在的类的名字一样
  • 系统在创建某个类的实例时会第一时间自动调用这个类的构造器
  • 构造器永远不会返回任何值

创建构造器,需要先把它的声明添加到类里:

class Car {
	Car( void );
};
Car::Car(void)   // 不用写 void Car::Car(void)
{
	color = “WHITE”;
	engine = “V8”;
	wheel = 4;
	gas_tank = FULL_GAS;
}
  • 构造对象数组:之前我们已经说过,数组可以是任何一种数据类型,当然也包括对象。
  • 如:Car mycar[10]; 调用语法依旧是:mycar[x].running;
  • 个类至少有一个构造器,如果你没有在类里定义一个构造器,编译器就会使用如下语法替你定义一个:ClassName::ClassName(){ }
  • 这是一个没有代码内容的空构造器,除此之外,编译器还会替你创建一个副本构造器(CopyConstructor)。

定义析构器

  • 从前边的内容我们了解到,在创建对象时,系统都会自动调用一个特殊的方法,即构造器。
  • 相应地,在销毁一个对象时,系统也应该会调用另一个特殊方法达到对应效果?没错,这就是析构器。
  • 一般来说,构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来完成事后所必须的清理工作(清理内存)。
  • 构造器和析构器两者相辅相成,有许多共同之处。首先,析构器有着和构造器/类一样的名字,只不过前边多了一个波浪符“~”前缀。
class Car 
{
	Car(void);
	~Car();
} 
  • 析构器也永远不返回任何值。
  • 析构器是不带参数的。所以析构器的声明永远是如下格式:~ClassName();
  • 在我们刚刚的例子中析构器可有可无。但是在比较复杂的类里,析构器往往至关重要(可能引起内存泄露)。
  • 例如某个类的构造器申请了一块内存,我们就必须在析构器里释放那块内存。
#include <iostream>
#include <fstream>
class Car
{
public:	
	std::ofstream output;
	std::string  color;
	std::string  engine;
	unsigned int  gas_tank;
	Car();
	~Car();
}; 
Car::Car(){
	output.open("test.txt",std::ios::app);
}
Car::~Car(){
	output.close();
	std::cout << "调用析构器释放内存" << std::endl;
}
int main()
{
	Car mycar;
	std::cout << "输入车辆信息:" << std::endl;
	std::cin >> mycar.color >> mycar.engine >> mycar.gas_tank ;
	mycar.output << "\n" << mycar.color << " " << mycar.engine << " " << mycar.gas_tank ;
	std::cout << "录入完成" << std::endl;
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值