c++基础

多文件结构和编译预处理命令

C++程序的一般组织结构

•一个源程序可以划分为多个源文件:

  • 类声明文件(.h文件)

  • 类实现文件(.cpp文件)

  • 类的使用文件(main()所在的.cpp文件)

外部变量

•在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。

•这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型或定义函数时用extern修饰,其效果与不加修饰的缺省状态是一样的。

namespace

•使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。

  **namespace** { //匿名的命名空间

 int n;

 void f() {

 n++;

 }

  }

•这里被“namespace { …… }”括起的区域都属于匿名的命名空间。

访问权限

  • public:所有

  • private:本类,友元

  • protected:本类,派生类,友元

重载

  1. 重载函数参数要不同(类型,个数或者顺序不同)

内联函数

  1. 为了提高运行效率
  2. 不要有复杂的结构(循环,swich语句)
  3. 将函数体放在类的声明中
  4. 使用inline关键字
  5. 类结构中所在的类说明内部定义的函数是内联函数。

带默认参数的函数

  1. 类中声明的时候带默认参数,具体实现的时候不带

构造函数

  1. 函数名和类名完全相同
  2. 不写构造函数时,系统会默认给一个无参的默认构造函数
  3. 个数大于等于一个

析构函数

  1. 对象销毁时被调用
  2. 不能重载,有且只有一个

委托构造函数

  1. C++11 引入

复制(拷贝)构造函数

#include <iostream>
using namespace std;

class Point {	//Point 类的定义
public:		//外部接口
	Point(int xx = 0, int yy = 0) {	//构造函数
		x = xx;
		y = yy;
	}
	Point(const Point &p);	//拷贝构造函数
	int getX() {
		return x;
	}
	int getY() {
		return y;
	}
private:	//私有数据
	int x, y;
};

//成员函数的实现
Point::Point(const Point &p) {
	x = p.x;
	y = p.y;
	cout << "Calling the copy constructor" << endl;
}

//形参为Point类对象的函数
void fun1(Point p) {
	cout << p.getX() << endl;
}

//返回值为Point类对象的函数
Point fun2() {
	Point a(1, 2);//临时对象
	return a;
}

//主程序
int main() {
	Point a(4, 5);	//第一个对象A
	Point b = a;	//情况一,用A初始化B。第一次调用拷贝构造函数
	cout << b.getX() << endl;
	fun1(b);		//情况二,对象B作为fun1的实参。第二次调用拷贝构造函数
	b = fun2();		//情况三,函数的返回值是类对象,函数返回时,调用拷贝构造函数,赋值(此时b不构造)
	cout << b.getX() << endl;
	cout << b.getY() << endl; 
	return 0;
}
  1. 函数返回对象时会增加一个临时对象,增加开销(情况三)

移动构造函数

c++11标准

左值:赋值语句左侧的对象变量。
右值:赋值语句右侧的值,不依附于对象。
float n=6;
float &lr_n=n;        //对变量n的左值引用
float &&rr_n=n;     //错误,不能将右值引用绑定到左值n上
float &&rr_n=n*n;  //将乘法结果左值绑定到左值引用
float &lr_n=n*n;  //错误,不能将左值引用绑定到右值n*n上
标准库utility中声明提供了move函数,将左值对象移动成为右值。
float n=10;
float &&rr_n = std::move(n);

default、delete函数(C++11标准)

组合

•类中的成员数据是另一个类的对象。

•可以在已有抽象的基础上实现更复杂的抽象。

原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。
声明形式:
类名::类名(对象成员所需的形参,本类成员形参)
       :对象1(参数),对象2(参数),......
{  
//函数体其他语句
}

构造组合类对象时的初始化次序

•首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。

▫成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造。

▫初始化列表中未出现的成员对象,调用用默认构造函数(即无形参的)初始化

•处理完初始化列表之后,再执行构造函数的函数体。

eg类的组合,线段(Line)类

#include <iostream>
#include <cmath>
using namespace std;
//Point类省略
//类的组合
class Line {	//Line类的定义
public:	//外部接口
	Line(Point xp1, Point xp2);
	Line(Line &l);
	double getLen() { return len; }
private:	//私有数据成员
	Point p1, p2;	//Point类的对象p1,p2
	double len;
};
//组合类的构造函数
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
	cout << "Calling constructor of Line" << endl;
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
	len = sqrt(x * x + y * y);
}
Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的复制构造函数
	cout << "Calling the copy constructor of Line" << endl;
	len = l.len;
}
int main() {
	Point myp1(1, 1), myp2(4, 5);	//建立Point类的对象
	Line line(myp1, myp2); //建立Line类的对象
	Line line2(line);	//利用复制构造函数建立一个新对象
	cout << "The length of the line is: ";
	cout << line.getLen() << endl;
	cout << "The length of the line2 is: ";
	cout << line2.getLen() << endl;
	return 0;
}

前向引用声明

class B;  //前向引用声明
class A {
public:
  void f(B b);
};
class B {
public:
  void g(A a);
};

作用域

//5_1.cpp
#include <iostream>
using namespace std;
int i;				//全局变量,文件作用域
int main() {
    int i = 5;			//定义局部变量i并赋值
    {				//子块1
        int i;		//局部变量,局部作用域
        i = 7;
        cout << "i = " << i << endl;//输出7
    }
    cout << "i = " << i << endl;//输出5
    ::i = 10;
    cout << "i = " << i << endl;//输出5
    cout <<"i = " << ::i << endl;//输出10
    return 0;
}

静态数据成员

  • 用关键字static声明
  • 必须在类外定义和初始化,用*(::)来指明所属的类

image-20240317203139663

eg :具有静态数据成员的Point类

#include <iostream>
using namespace std;
 
class Point {	//Point类定义
public:	//外部接口
	Point(int x = 0, int y = 0) : x(x), y(y) { //构造函数
		//在构造函数中对count累加,所有对象共同维护同一个count
		count++;
	}
	Point(Point &p) {	//复制构造函数
		x = p.x;
		y = p.y;
		count++;
	}
	~Point() {  count--; }
	int getX() { return x; }
	int getY() { return y; }
        void showCount() {		//输出静态数据成员
		cout << "  Object count = " << count << endl;
	}
private:	//私有数据成员
	int x, y;
	static int count;	//静态数据成员声明,用于记录点的个数
};
int Point::count = 0;//静态数据成员定义和初始化,使用类名限定
int main() {	//主函数
	Point a(4, 5);	//定义对象a,其构造函数回使count增1
	cout << "Point A: " << a.getX() << ", " << a.getY();
	a.showCount();	//输出对象个数
 
	Point b(a);	//定义对象b,其构造函数回使count增1
	cout << "Point B: " << b.getX() << ", " << b.getY();
	b.showCount();	//输出对象个数
	return 0;
}

静态成员函数

没有this指针

静态成员函数无法访问成员变量

#include <iostream>
using namespace std;
class Point {	//Point类定义
public:	//外部接口
    Point(int x = 0, int y = 0) : x(x), y(y) { //构造函数
        //在构造函数中对count累加,所有对象共同维护同一个count
        count++;
    }
    Point(Point &p) {	//复制构造函数
        x = p.x;
        y = p.y;
        count++;
    }
    ~Point() {  count--; }
    int getX() { return x; }
    int getY() { return y; }
    static void showCount() {		//输出静态数据成员
        cout << "  Object count = " << count << endl;
    }
private:	//私有数据成员
    int x, y;
    static int count;	//静态数据成员声明,用于记录点的个数
};
int Point::count = 0;//静态数据成员定义和初始化,使用类名限定

int main() {	//主函数
    Point::showCount();
    Point a(4, 5);	//定义对象a,其构造函数回使count增1
    cout << "Point A: " << a.getX() << ", " << a.getY();
    a.showCount();	//输出对象个数
    Point b(a);	//定义对象b,其构造函数回使count增1
    cout << "Point B: " << b.getX() << ", " << b.getY();
    b.showCount();	//输出对象个数
    Point::showCount();
    return 0;
}

友元

友元不是成员函数

友元使用的原因

结合着类的特性,可知:类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行声明,为了与该类的成员函数加以区别,在声明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

优缺点

利用 friend 修饰符,可以让一些普通函数 或 另一个类的成员函数 直接对某个类的保护成员和私有成员进行操作,提高了程序的运行效率;同时避免把类的成员都声明为public,最大限度地保护数据成员的安全。

但是,即使是最大限度地保护数据成员,友元也破坏了类的封装性

如果将类的封装比喻成一堵墙的话,那么友元机制就像墙上开了一个门。所以使用友元时一定要慎重。

eg友元函数 (计算两点间的距离)

#include <iostream>
#include <cmath>
using namespace std;
class Point {	//Point类声明
public:	//外部接口
	Point(int x=0, int y=0) : x(x), y(y) { }
	int getX() { return x; }
	int getY() { return y; }
	friend float dist(Point &a, Point &b); 
private:	//私有数据成员
	int x, y;
};
float dist( Point& a, Point& b) {
  double x = a.x - b.x;
  double y = a.y - b.y;
  return static_cast<float>(sqrt(x * x + y * y));
}
int main() {
  Point p1(1, 1), p2(4, 5);
  cout <<"The distance is: ";
  cout << dist(p1, p2) << endl;
  return 0;
}

友元类

声明

friend class A;
friend void A::print();

eg

class A {
  friend class B;
public:
  void display() {
    cout << x << endl;
  }
private:
  int x;
}

class B {
public:
  void set(int i);
  void display();
private:
  A a;
};
void B::set(int i) {
   a.x=i;//由于B是友元类,故B中函数可访问A中的x
}
void B::display() {
   a.display();
}

友元关系是单向的

上面案例如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。

共享数据的保护

•对于既需要共享、又需要防止改变的数据应该声明为常类型(用const进行修饰)。对于不改变对象状态的成员函数应该声明为常函数。

  • 常对象:必须进行初始化,不能被更新。

const 类名 对象名

class A
{
  public:
    A(int i,int j) {x=i; y=j;}
                     ...
  private:
    int x,y;
};
A const a(3,4); //a是常对象,不能被更新

通过常对象只能调用它的常成员函数。

  • 常成员

用const进行修饰的类成员:常数据成员和常函数成员

  • 常函数成员
  1. 使用const关键字说明的函数。

  2. 常成员函数不更新对象的数据成员

  3. 常成员函数说明格式:
    类型说明符 函数名(参数表)const;
    这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。

  4. const关键字可以被用于参与对重载函数的区分

  5. 常对象只能调用常成员函数,并且优先调用普通成员函数

  1. eg const关键字可以被用于参与对重载函数的区分
#include<iostream>
using namespace std;
class R {
public:
  R(int r1, int r2) : r1(r1), r2(r2) { }
  void print();
  void print() const;
private:
  int r1, r2;
};
void R::print() {
  cout << r1 << ":" << r2 << endl;
}
void R::print() const {
  cout << r1 << ";" << r2 << endl;
}
int main() {
  R a(5,4);
  a.print(); //调用void print()
  const R b(20,52);  
  b.print(); //调用void print() const
	return 0;
}
  1. eg 常对象只能调用常成员函数

    #include<iostream>
    using namespace std;
    class R {
    public:
      R(int r1, int r2) : r1(r1), r2(r2) { }
      void print();
    private:
      int r1, r2;
    };
    void R::print() {
      cout << r1 << ":" << r2 << endl;
    }
    int main() {
      R a(5,4);
      a.print(); //调用void print()
      const R b(20,52);  
      b.print();//出错
    	return 0;
    }
    

    image-20240322201850452

  • 常数据成员

    #include <iostream>
    using namespace std;
    class A {
    public:
    	A(int i);
    	void print();
    private:
    	const int a;
    	static const int b;  //静态常数据成员
    };
    const int A::b=10; 
    A::A(int i) : a(i) { }//只能这样初始化常数据成员,不能出现为a赋值的操作
    void A::print() {
      cout << a << ":" << b <<endl;
    }
    int main() {
    /*建立对象a和b,并以100和0作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值*/
      A a1(100), a2(0);
      a1.print();
      a2.print();
      return 0;
    }
    
  • 常引用:被引用的对象不能被更新。

const 类型说明符 &引用名

#include <iostream>
#include <cmath>
using namespace std;
class Point {	//Point类定义
public:	//外部接口
	Point(int x = 0, int y = 0)
    : x(x), y(y) { }
	int getX() { return x; }
	int getY() { return y; }
	friend float dist(const Point &p1, const Point &p2);
private:	//私有数据成员
	int x, y;
};
float dist(const Point &p1, const Point &p2) {
	double x = p1.x - p2.x;	
	double y = p1.y - p2.y;
	return static_cast<float>(sqrt(x * x + y * y));
}

int main() {	//主函数
	const Point myp1(1, 1), myp2(4, 5);	
	cout << "The distance is: ";
	cout << dist(myp1, myp2) << endl;
	return 0;
} 
  • 常数组:数组元素不能被更新

类型说明符 const 数组名[大小]

  • 常指针:指向常量的指针

对象指针

•声明形式

类名 *对象指针名;

例: Point a(5,10);

Piont *ptr;

ptr=&a;

•通过指针访问对象成员

对象指针名->成员名

ptr->getx() 相当于 (*ptr).getx();

动态创建对象(new delete)

#include <iostream>
using namespace std;
class Point {
public:
	Point() : x(0), y(0) {
		cout<<"Default Constructor called."<<endl;
	}
	Point(int x, int y) : x(x), y(y) {
		cout<< "Constructor called."<<endl;
	}
	~Point() { cout<<"Destructor called."<<endl; }
	int getX() const { return x; }
	int getY() const { return y; }
	void move(int newX, int newY) {
		x = newX;
		y = newY;
	}
private:
	int x, y;
};
int main() {
	cout << "Step one: " << endl;
	Point *ptr1 = new Point;//调用缺省构造函数
	delete ptr1;		//删除对象,自动调用析构函数
	
	cout << "Step two: " << endl;
	ptr1 = new Point(1,2);	
	delete ptr1;
    
    
    
    
	Point *ptr = new Point[2];	//创建对象数组
	ptr[0].move(5, 10);  //通过指针访问数组元素的成员
	ptr[1].move(15, 20); //通过指针访问数组元素的成员
	cout << "Deleting..." << endl;
	delete[] ptr;	    //删除整个对象数组
	return 0;
}

string类

•常用构造函数

▫string(); //缺省构造函数,建立一个长度为0的串

▫string(const char *s); //用指针s所指向的字符串常量初始化string类的对象

▫string(const string& rhs); //拷贝构造函数

•例:

▫string s1; //建立一个空字符串

▫string s2 = “abc”; //用常量建立一个初值为”abc”的字符串

▫string s3 = s2;//执行拷贝构造函数,用s2的值作为s3的初值

•常用操作符

▫s + t 将串s和t连接成一个新串

▫s = t 用t更新s

▫s == t 判断s与t是否相等

▫s != t 判断s与t是否不等

▫s < t 判断s是否小于t(按字典顺序比较)

▫s <= t 判断s是否小于或等于t (按字典顺序比较)

▫s > t 判断s是否大于t (按字典顺序比较)

▫s >= t 判断s是否大于或等于t (按字典顺序比较)

▫s[i] 访问串中下标为i的字符

•例:

▫string s1 = "abc", s2 = "def";

▫string s3 = s1 + s2; //结果是"abcdef"

▫bool s4 = (s1 < s2); //结果是true

▫char s5 = s2[1]; //结果是'e'

用getline输入整行字符串

•输入整行字符串

▫用cin的>>操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输入时被读取

▫用string头文件中的getline可以输入整行字符串,例如:

–getline(cin, s2);

•以其它字符作为分隔符输入字符串

▫输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号)

▫把分隔符作为getline的第3个参数即可,例如:

–getline(cin, s2, ',');

include <iostream>
#include <string>
using namespace std;
int main() {
	for (int i = 0; i < 2; i++)	{
		string city, state;
		getline(cin, city, ',');
		getline(cin, state);
		cout << "City:" << city << “  State:" << state << endl;
	}
	return 0;
}
posted @ 2024-03-29 20:07  jiuwen567  阅读(60)  评论(0编辑  收藏  举报