- C++ 标准的命名空间为 std ,包含很多标准的定义
- << 为运算符重载
- 命名空间类似于Java中的包
- 命名空间可以先进行使用
- :: 为访问修饰符
//查看数据类型代码
#include <iostream>
using namespace std;
int main()
{
const int a = 4;
cout << typeid(a).name() << endl;//查看数据类型
}
例子:
#include <stdlib.h>
#include <iostream>
//使用标准命名空间
using namespace std;
//自定义命名空间
namespace NSP_A{
int a = 9;
struct Teacher{
char name[20];
int age;
};
struct Student{
char name[20];
int age;
};
}
void main(){
//std::cout << "this is c plus plus" << std::endl;
cout << "this is c plus plus" << endl;
cout << "访问自定义命名空间的属性a:" << NSP_A::a << endl;
//使用命名空间中的结构体
using NSP_A::Student;
Student t;
t.age = 19;
cout << "学生的年龄为:" << t.age << endl;
getchar();
}
什么是标准I/O?
标准I/O流是指对标准输入设备(键盘、鼠标等)和标准输出设备(显示器、打印机等)进行输入输出的过程。
标准I/O的类的继承关系是什么?
string 类是 STL 中 basic_string 模板实例化得到的模板类。其定义如下:
typedef basic_string <char> string;
basic_string 此处可以不必深究。
string 类的成员函数有很多,同一个名字的函数也常会有五六个重载的版本;
1. 构造函数
string 类有多个构造函数,用法示例如下:
string s1(); // si = ""
string s2("Hello"); // s2 = "Hello"
string s3(4, 'K'); // s3 = "KKKK"
string s4("12345", 1, 3); //s4 = "234",即 "12345" 的从下标 1 开始,长度为 3 的子串
字符串下标 n 开始、长度为 m 的字符串称为“子串(n, m)”。
string 类没有接收一个整型参数或一个字符型参数的构造函数。下面的两种写法是错误的:
string s1('K');
string s2(123);
2. 对 string 对象赋值
可以用 char* 类型的变量、常量,以及 char 类型的变量、常量对 string 对象进行赋值。例如:
string s1;
s1 = "Hello"; // s1 = "Hello"
s2 = 'K'; // s2 = "K”
string 类还有 assign 成员函数,可以用来对 string 对象赋值。assign 成员函数返回对象自身的引用。例如:
string s1("12345"), s2;
s3.assign(s1); // s3 = s1
s2.assign(s1, 1, 2); // s2 = "23",即 s1 的子串(1, 2)
s2.assign(4, 'K'); // s2 = "KKKK"
s2.assign("abcde", 2, 3); // s2 = "cde",即 "abcde" 的子串(2, 3)
3. 求字符串的长度
语法:
size_type length(); |
length 成员函数返回字符串的长度。size 成员函数可以实现同样的功能。例如:
string s1;
s1 = "Hello"; // s1 = "Hello"
cout<<s1.length()<<endl;
4. string对象中字符串的连接
除了可以使用+和+=运算符对 string 对象执行字符串的连接操作外,string 类还有 append 成员函数,可以用来向字符串后面添加内容。append 成员函数返回对象自身的引用。例如:
string s1("123"), s2("abc");
s1.append(s2); // s1 = "123abc"
s1.append(s2, 1, 2); // s1 = "123abcbc"
s1.append(3, 'K'); // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3); // s1 = "123abcbcKKKCDE",添加 "ABCDE" 的子串(2, 3)
5. string对象的比较
除了可以用 <、<=、==、!=、>=、> 运算符比较 string 对象外,string 类还有 compare 成员函数,可用于比较字符串。compare 成员函数有以下返回值:
- 小于 0 表示当前的字符串小;
- 等于 0 表示两个字符串相等;
- 大于 0 表示另一个字符串小。
string s1("hello"), s2("hello, world");
int n = s1.compare(s2);
n = s1.compare(1, 2, s2, 0, 3); //比较s1的子串 (1,2) 和s2的子串 (0,3)
n = s1.compare(0, 2, s2); // 比较s1的子串 (0,2) 和 s2
n = s1.compare("Hello");
n = s1.compare(1, 2, "Hello"); //比较 s1 的子串(1,2)和"Hello”
n = s1.compare(1, 2, "Hello", 1, 2); //比较 s1 的子串(1,2)和 "Hello" 的子串(1,2)
6. 求 string 对象的子串
substr 成员函数可以用于求子串 (n, m),原型如下:
string substr(int n = 0, int m = string::npos) const; |
调用时,如果省略 m 或 m 超过了字符串的长度,则求出来的子串就是从下标 n 开始一直到字符串结束的部分。例如:
string s1 = "this is ok";
string s2 = s1.substr(2, 4); // s2 = "is i"
s2 = s1.substr(2); // s2 = "is is ok"
7. 交换两个string对象的内容
swap 成员函数可以交换两个 string 对象的内容。例如:
string s1("West”), s2("East");
s1.swap(s2); // s1 = "East",s2 = "West"
8. 查找子串和字符
string 类有一些查找子串和字符的成员函数,它们的返回值都是子串或字符在 string 对象字符串中的位置(即下标)。如果查不到,则返回 string::npos。string: :npos 是在 string 类中定义的一个静态常量。这些函数如下:
- find:从前往后查找子串或字符出现的位置。
- rfind:从后往前查找子串或字符出现的位置。
- find_first_of:从前往后查找何处出现另一个字符串中包含的字符。例如:
- s1.find_first_of("abc"); //查找s1中第一次出现"abc"中任一字符的位置
- find_last_of:从后往前查找何处出现另一个字符串中包含的字符。
- find_first_not_of:从前往后查找何处出现另一个字符串中没有包含的字符。
- find_last_not_of:从后往前查找何处出现另一个字符串中没有包含的字符。
string 类的查找成员函数的例子:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("Source Code");
int n;
if ((n = s1.find('u')) != string::npos) //查找 u 出现的位置
cout << "1) " << n << "," << s1.substr(n) << endl;
//输出 l)2,urce Code
if ((n = s1.find("Source", 3)) == string::npos)
//从下标3开始查找"Source",找不到
cout << "2) " << "Not Found" << endl; //输出 2) Not Found
if ((n = s1.find("Co")) != string::npos)
//查找子串"Co"。能找到,返回"Co"的位置
cout << "3) " << n << ", " << s1.substr(n) << endl;
//输出 3) 7, Code
if ((n = s1.find_first_of("ceo")) != string::npos)
//查找第一次出现或 'c'、'e'或'o'的位置
cout << "4) " << n << ", " << s1.substr(n) << endl;
//输出 4) l, ource Code
if ((n = s1.find_last_of('e')) != string::npos)
//查找最后一个 'e' 的位置
cout << "5) " << n << ", " << s1.substr(n) << endl; //输出 5) 10, e
if ((n = s1.find_first_not_of("eou", 1)) != string::npos)
//从下标1开始查找第一次出现非 'e'、'o' 或 'u' 字符的位置
cout << "6) " << n << ", " << s1.substr(n) << endl;
//输出 6) 3, rce Code
return 0;
}
9. 替换子串
replace 成员函数可以对 string 对象中的子串进行替换,返回值为对象自身的引用。例如:
string s1("Real Steel");
s1.replace(1, 3, "123456", 2, 4); //用 "123456" 的子串(2,4) 替换 s1 的子串(1,3)
cout << s1 << endl; //输出 R3456 Steel
string s2("Harry Potter");
s2.replace(2, 3, 5, '0'); //用 5 个 '0' 替换子串(2,3)
cout << s2 << endl; //输出 HaOOOOO Potter
int n = s2.find("OOOOO"); //查找子串 "00000" 的位置,n=2
s2.replace(n, 5, "XXX"); //将子串(n,5)替换为"XXX"
cout << s2 < < endl; //输出 HaXXX Potter
10. 删除子串
erase 成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。例如:
string s1("Real Steel");
s1.erase(1, 3); //删除子串(1, 3),此后 s1 = "R Steel"
s1.erase(5); //删除下标5及其后面的所有字符,此后 s1 = "R Ste"
11. 插入字符串
insert 成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用。例如:
string s1("Limitless"), s2("00");
s1.insert(2, "123"); //在下标 2 处插入字符串"123",s1 = "Li123mitless"
s1.insert(3, s2); //在下标 2 处插入 s2 , s1 = "Li10023mitless"
s1.insert(3, 5, 'X'); //在下标 3 处插入 5 个 'X',s1 = "Li1XXXXX0023mitless"
12. 将 string 对象作为流处理
使用流对象 istringstream 和 ostringstream,可以将 string 对象当作一个流进行输入输出。使用这两个类需要包含头文件 sstream。例子:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string src("Avatar 123 5.2 Titanic K");
istringstream istrStream(src); //建立src到istrStream的联系
string s1, s2;
int n; double d; char c;
istrStream >> s1 >> n >> d >> s2 >> c; //把src的内容当做输入流进行读取
ostringstream ostrStream;
ostrStream << s1 << endl << s2 << endl << n << endl << d << endl << c <<endl;
cout << ostrStream.str();
return 0;
}
结果:
注意:
第 11 行,从输入流 istrStream 进行读取,过程和从 cin 读取一样,只不过输入的来源由键盘变成了 string 对象 src。因此,"Avatar" 被读取到 s1,123 被读取到 n,5.2 被读取到 d,"Titanic" 被读取到s2,'K' 被读取到 c。
第 12 行,将变量的值输出到流 ostrStream。输出结果不会出现在屏幕上,而是被保存在 ostrStream 对象管理的某处。用 ostringstream 类的 str 成员函数能将输出到 ostringstream 对象中的内容提取出来。
13. 用 STL 算法操作 string 对象
string 对象也可以看作一个顺序容器,它支持随机访问迭代器,也有 begin 和 end 等成员函数。STL 中的许多算法也适用于 string 对象。下面是用 STL 算法操作 string 对象的程序示例。
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
string s("afgcbed");
string::iterator p = find(s.begin(), s.end(), 'c');
if (p!= s.end())
cout << p - s.begin() << endl; //输出 3
sort(s.begin(), s.end());
cout << s << endl; //输出 abcdefg
next_permutation(s.begin(), s.end());
cout << s << endl; //输出 abcdegf
return 0;
}
函数重载:
===函数重载:就是重新赋予同一个函数的多种功能
如果要让函数重载成功:要么就是函数间的参数个数不同,或者类型不同,或者两种都不同
什么时候用函数重载函数:当功能相近或相似时
例子:
#include <iostream>
using nameapace std;
int add(int a, int b)
{
cout<<"two args"<<endl;
return a+b;
}
int add(int a, int b,int c)
{
cout<<"three args"<<endl;
return a+b+c;
}
int main()
{
add(10,2,3);
retur,n 0;
}
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
void TestFunc(int a = 0)
{
cout << a << endl;
}
int main()
{
TestFunc();//没有传参时,使用参数的默认值
TestFunc(10);//传参时,使用指定的实参
system("pause");
return 0;
}
//1、全缺省参数 释:函数的三个参数都赋值了。
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
//2、半缺省参数 释:函数的参数没有都赋值。
void TestFunc(int a, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
例子2:
#include <iostream>
#include <string>
using namespace std;
void test(int a=100,int b=300, int c=9) //c为默认参数,不赋值的时候c=9参与运算
{
cout<<a+b+c<<endl;
}
int main()
{
test(10,20);
}
¥==注意
1、函数定义在调用函数之前,只需要在定义的时候给出默认值,原型声明不需要指定默认值;函数定义在函数调用的后边,在原型声明时不需要给出默认值,在定义给出。
**
2、一个函数不能既作为重载函数,又作为有默认参数的函数。因为当调用函数时如果少一个参数,系统无法判断是利用重载函数还是利用默认参数的函数,出现二义性,系统无法执行。
##1、半缺省参数必须从右往左依次提供,不能间隔地给出。
#include<iostream>
using namespace std;
void TestFunc(int a = 10, int b = 20, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
TestFunc();
return 0;
}
##2、缺省参数不能同时在函数声明和定义中出现,不能二者择其一。
#include<iostream>
using namespace std;
//函数声明
void TestFunc(int a = 100, int b = 200, int c = 300);
//函数定义
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
TestFunc();
system("pause");
return 0;
}
##3、实参默认从左到右传值
#include<iostream>
using namespace std;
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
TestFunc(1, 2);
system("pause");
return 0;
}
函数模板
C++ 语言支持模板。有了模板,可以只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Sawp 函数,用以交换不同类型变量的值。
在 C++ 中,模板分为函数模板和类模板两种。
- 函数模板是用于生成函数;
- 类模板则是用于生成类的。
函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名(形参表)
{
函数体
}
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
例子:
#include <iostream>
using namespace std;
template <typename K1,typename K2> //声明模板K1 K2类型
K1 add(K1 a,K2 b)
{
K2 c=a+b;
return c;
}
int main()
{
cout <<add(100,2.2f)<<endl;
return 0;
}
无参构造函数
注意:
1#构造函数没有返回值,因此也不需要在定义构造函数时声明类型,这是它和⼀般函数的⼀个重要的不同之点。
2#构造函数不需⽤户调⽤,也不能被⽤户调⽤
3#如果⽤户⾃⼰没有定义构造函数,则C++系统会⾃动⽣成⼀个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执⾏初始化操作。
4#在构造函数的函数体中不仅可以对数据成员赋初值,⽽且可以包含其他语句。但是⼀般不提倡在构造函数中加⼊与初始化⽆关的内容,以保持程序的清晰。
例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public: //访问访问修饰
string name; // 数据成员,除了静态数据成员外,不能在类内对其初始化
int age; //可以像结构体那样对成员进行初始化: 对象名.数据成员名=值,还可以通过构造函数对其初始化。
void eat() // 成员函数
{
cout << "I CAN eating" << endl;
}
void play()
{
cout << "I CAN play basketball" << endl;
}
};
int main()
{
class stu mystu; //定义一个stu类型的对象 定义一个了stu类型的变量
stu mypp; //定义了一个对象或类变量
mypp.name = "ljs"; //在类外对数据成员进行初始化
mypp.age = 12;
cout << mypp.name << mypp.age << endl;//访问类中数据成员的格式: 对象名.数据成员名
return 0;
}
默认构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
例子:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
结果:
Objectis being created Length of line :6 |
带参的构造函数
原型:
类名 实例名(实参1,实参2,...) |
例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public: //访问访问修饰
string name; // 数据成员,除了静态数据成员外,不能在类内对其初始化
int age; //可以像结构体那样对成员进行初始化: 对象名.数据成员名=值,还可以通过构造函数对其初始化。
void eat() // 成员函数
{
cout << "I CAN eating" << endl;
}
void play() //成员函数在类中定义
{
cout << "I CAN play basketball" << endl;
}
void like(int age); //在类中声明函数
};
//类外定义成员函数的格式: 函数的返回类型 类名::函数名(形参列表)
// {函数体}
void stu::like(int age) //在类外实现类中声明的成员函数
{
cout << "i like liao mei" <<age<<endl;
}
int main()
{
class stu mystu; //定义一个stu类型的对象 定义一个了stu类型的变量
stu mypp; //定义了一个对象或类变量
mypp.name = "ljs"; //在类外对数据成员进行初始化
mypp.age = 12;
cout << mypp.name << mypp.age << endl; //访问类中成员的格式: 对象名.成员名
mypp.like(45);
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public: //访问访问修饰
string name; // 数据成员,除了静态数据成员外,不能在类内对其初始化
int age; //可以像结构体那样对成员进行初始化: 对象名.数据成员名=值,还可以通过构造函数对其初始化。
void eat() // 成员函数
{
cout << "I CAN eating" << endl;
}
void play() //成员函数在类中定义
{
string name="leilei";
printf("in=%p", this); //this指向当前的对象
cout << "I CAN play basketball"<<this->name << endl;
eat(); //类内访问成员:直接写成员名
}
void like(int age); //在类中声明函数
};
//类外定义成员函数的格式: 函数的返回类型 类名::函数名(形参列表)
// {函数体}
void stu::like(int age) //在类外实现类中声明的成员函数
{
cout << "i like liao mei" <<age<<endl;
}
int main()
{
class stu mystu; //定义一个stu类型的对象 定义一个了stu类型的变量
stu mypp; //定义了一个对象或类变量
printf("out=%p", &mypp);
mypp.name = "ljs"; //在类外对数据成员进行初始化
mypp.age = 12;
cout << mypp.name << mypp.age << endl; //访问类中成员的格式: 对象名.成员名
mypp.like(45);
mypp.play();
mystu.play();
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public: //访问访问修饰
string name; // 数据成员,除了静态数据成员外,不能在类内对其初始化
const int pi;
int age; //可以像结构体那样对成员进行初始化: 对象名.数据成员名=值,还可以通过构造函数对其初始化。
//stu(string s1,int myage)
//{
// name = s1;
// age = myage;
// //pi = 100;
//}
//使用参数初始化列表来初始化类中的数据成员
stu(string s1, int myage);
void eat() // 成员函数
{
cout << "I CAN eating" <<age<< endl;
}
void play() //成员函数在类中定义
{
string name="leilei";
printf("in=%p", this); //this指向当前的对象
cout << "I CAN play basketball"<<this->name << endl;
eat(); //类内访问成员:直接写成员名
}
void like(int age); //在类中声明函数
};
//类外定义成员函数的格式: 函数的返回类型 类名::函数名(形参列表)
// {函数体}
void stu::like(int age) //在类外实现类中声明的成员函数
{
cout << "i like liao mei" <<age<<endl;
}
//使用参数初始化列表来初始化类中的数据成员
stu::stu(string s1, int myage) :name(s1), age(myage),pi(201)
{
;
}
int main()
{
//在创建对象的时候,系统会自动调用构造函数,也就是说构造函数不需要显式调用, 隐式调用构造函数
stu mypp("yaoyao",18), myleilei("hehe",21);
stu mykk = stu("gaofei", 18); //显示调用构造函数
cout << mypp.name << endl;
cout << myleilei.name <<mypp.pi<< endl;
return 0;
}
使用初始化列表来初始化字段
例子:
//使用参数初始化列表来初始化类中的数据成员
stu::stu(string s1, int myage) :name(s1), age(myage),pi(201)
{
cout<<"name="<<s1<<"myage="<<myage<<"pi="<<201<<endl;
}
上面的语法等同于如下语法:
//使用参数初始化列表来初始化类中的数据成员
stu::stu(string s1, int myage)
{
name=s1;
age=myage;
pi=201;
cout<<"name="<<s1<<"myage="<<myage<<"pi="<<201<<endl;
}
析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时使用。
析构函数的名称与类的名称完全相同,只是在前面加了波浪号(~)作为前缀,它不会反回任何值,也不能带有任何参数。析构函数有助于调出程序(比如关闭文件、释放内存等)前释放资源。
例子如下:
#include <iostream>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public: //访问访问修饰
string name; // 数据成员,除了静态数据成员外,不能在类内对其初始化
int age; //可以像结构体那样对成员进行初始化: 对象名.数据成员名=值,还可以通过构造函数对其初始化。
stu();//构造函数
~stu();//析构函数
void test()
{
this->add();
}
void add()
{
cout << "in add" << endl;
}
};
//构造函数可用于为某些成员变量设置初始值,成员的定义
stu::stu(void)
{
cout << "构造函数" << endl;
}
//析构函数:构造函数在一个类中只能有一个,不能有形参,没有返回类型,离开对象的作用域时会自动调用
stu::~stu(void)
{
cout << "析构函数" << endl;
}
void cc()
{
stu my;
cout << "11111" << endl;
my.test();
static stu my1;
}
int main()
{
cc();
cout << "22222222" << endl;
//while (1);
return 0;
}
所有的 C++ 程序都有以下两个基本要素:
1#程序语句(代码):这是程序中执行动作的部分,它们被称为函数。
2#程序数据:数据是程序的信息,会受到程序函数的影响。
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另外一个重要的概念,即OOB(数据影藏)概念。
数据封装是一种把数据和操作数据的函数据捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++通过创建类来支持封装和数据隐藏(public、protected、private)。我们知道类中包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,在类中定义的所有项目都是私有的。例如:
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
其中变量length、breadth、height都是私有的(private)。也就是说它们只能被Box类中的其他成员访问,而不能被程序中其他部分访问。这是实现封装的一种方式。
为了使类中的成员变成公有的(即程序中其他部分也能访问)那么就需要在这些成员前面使用公有成员(public)关键字来进行声明。所以在定义public标识符后边的变量或者函数就可以被程序中所有其他成员函数访问。
把一个类定义为另一个类的友元类,会暴露实现细节,从而降低了分装性。理想的做法是尽可能地对外影藏每个类的实现细节。
指针对象(C++ this指针)
在c++中,每个对象都可以通过this指针来访问自己的地址。this指针是所有成员函数的隐含参数。因此在成员函数内部,它可以用来指向调用对象。
友元函数没有this指正,因为友元不是类的成员。只有成员函数才有this指针。
this指针的例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public:
string name;
int math;
int chinese;
static int sum; //静态数据成员属于类,可以被该类所创建的所有对象所共享。
stu(int math, int chinese) :math(math), chinese(chinese)
{
}
int mysum()
{
this->sum = this->sum +this-> math + this->chinese;
this->test();
return sum;
}
static void test()
{
cout << 1112 << endl;
//cout << mysum() << endl; //2.在类中的静态成员函数不能调用类中其他非静态成员
cout <<sum<< endl; // 静态成员函数中可以使用静态使用
}
};
//对静态成员进行初始化,尽量通过外部进行初始化
int stu::sum = 0;
int main()
{
stu s1(10, 20);
cout << s1.mysum() << endl;
stu s2(10, 30);
cout << s2.mysum() << endl;
stu s3(10, 40);
cout << s3.mysum() << endl;
//s1.test();
stu::test(); // 1.类中的静态成员可以被类名或实例名进行调用
cout << stu::sum << endl;
return 0;
}
共用数据的保护
C++虽然采取了不少有效的措施(如设private保护)以增加数据的安全性,单有些数据缺往往是共享的,人们可以在不同的场合通过不同的途径访问同一个数据对象。有时无意之中的误操作会改变有关数据的状况,而这是人们所不希望出现的。那么既要使数据在一定的范围内共享,又要保证他不被随意修改,这时可以使用const,既把有关的数据定义为常量。
1、常对象
常对象中的数据成员为变量且必须要有初始值,如:
//定义函数模板
template <class Time>
Time const t1(12,34,36);//定义t1为常对象
定义常对象的一般形式为:
类名 const 对象名([实参列表]); 也可以是一下先发 const 对象名([实参列表]); |
注意:使用常对象时:
1#常对象的数据成员都是常数据成员,也就是一旦初始化就不能更新;
2#常对象的成员函数保持原样,有const修饰的是常数据成员函数,没有的则是普通成员函数;
3#只有常成员函数可以操作常对象
例子:
#include <string>
#include <iostream>
using namespace std;
class StuInfo
{
private:
string name;
short age;
string sex;
public:
//构造函数
StuInfo();
//析构函数
~StuInfo();
//带参构造函数
StuInfo(string name, string sex, short age);
void printInfo() const//该函数结尾加const才会被常对象调用成功
{
cout << name << "" << sex << endl;
}
};
StuInfo::StuInfo(string name, string sex, short age) :name(name), sex(sex), age(age)
{
;
}
//构造函数
StuInfo::StuInfo(void)
{
cout << "构造函数" << endl;
}
//析构函数
StuInfo::~StuInfo(void)
{
cout << "析构函数" << endl;
}
int main()
{
//构造函数中如果有其他成员,也不能被再次修改:
const StuInfo s1("xiaoming", "man", 13);
//常成员函数才可以操纵常对象,执行该语句会报错
s1.printInfo();
return 0;
}
常对象成员
定义:可以将对象的成员声名为const,包括常数据成员和常成员函数。
1、常数据成员
只需要在声名数据时加上const关键字
在类中定义如:const string name;
注意:只能通过构造函数的参数初始化表对常数据成员进行初始化。
const数据成员可以被const成员函数引用,也可以被非const的成员函数引用;
##类内数据成员的初始化,一般类的数据成员不能在类内直接初始化,建议通过构造方法进行初始化除非是一个常量表达式是可以直接初始化比如const int a = 12;
2、常成员函数
如果将成员函数声名为常成员函数,则只能引用本类中的数据成员,而不能修改它们。
声名成员函数如:
void print_info() const { } |
注意:
1#定义常成员函数,const的位置在函数名和括号之后,在声名函数和定义函数时都有const关键字,在调用时不必加const。
2#常成员函数不能调用另一个非const成员函数;
例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public:
string name;
int math;
int chinese;
stu(int math, int chinese) :math(math), chinese(chinese),name("xiaoming")
{
;
}
void mysum() const //常成员函数
{
//math = 10;
int a=3;
a = 100;
cout << math << chinese << endl;
}
void test()
{
math=102;
cout << math << endl;
}
};
int main()
{
const stu s1(11, 23); // 一旦定义常对象,类中的数据成员就变为常数据成员
s1.mysum(); //常对象不可以调用类中的非常成员函数。
s1.math;
stu s2(33, 44); //普通对象可以调用常成员函数。
s2.test();
return 0;
}
应用场景:
1#如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声名为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。
2#如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声名为const或将对象声名为const(常对象),然后const成员函数引用数据成员,这样起到“双保险”的作用,确切保证了书记成员不被修改。
指向对象的常指针
定义指向常变量的指针变量的一般形式为:
const 类型名 *指针变量名; |
使用注意:
1#如果一个变量已经被声名为常变量,只能用指向常量的指针变量指向它,而不能用一般的(指向非const型变量的)指针变量去指向它。
2#指向常量的指针变量除了可以指向常变量外,还可以指向未被声名为const的变量。此时不能通过此指针变量改变改变量的值。
3#指向常对象的指针最常用于函数的形参,目的是在保护形参指针锁指向的对象,使它在函数执行过程中不被改变。
指向常量的指针变量
定义指向常对象的指针变量一般形式为:
Time * const p; |
p是指向Time对象的常指针p的值(即p的指向)不能改变。
类的静态成员
在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只能存储一份供所有对象供用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
静态数据成员
静态数据成员的定义:
static 数据类型名 数据成员名; |
静态数据成员可以初始化,但只能在类体外进行初始化,原型:
数据类型名::静态数据成员名=初值; |
例如:
class stu
{
public:
const int height;
}
int stu::height=10;
静态成员函数
类中静态成员函数的定义:
static 类型名称 函数名 ([形参列表]); |
例如:
static float average();
静态函数的调用格式:
类名::静态成员函数名; |
静态成员使用举例:
#include<string.h>
#include<iostream>
using namespace std;
class Stu_info
{
public:
static int my; //定义静态数据成员
void print_info();
static void output(); //声明静态成员函数
};
void Stu_info::print_info()
{
Stu_info::my = Stu_info::my + 20;
cout << Stu_info::my << endl;
}
void Stu_info::output()//定义静态成员函数
{
cout << "heoll\n";
}
int Stu_info::my = 100;
int main()
{
Stu_info s2;
s2.print_info();
Stu_info::output(); //类名调⽤静态成员函数
return 0;
}
对象的赋值复制
对象之间的赋值也是通过赋值运算符“=”进行的。本来赋值运算符“=”只能用来对单个的变量赋值,现在被扩展为两个相同类对象之间的赋值,这是通过赋值运算符的重载实现的。实际这个通过成员复制来完成的,即将一个对象的成员值——复制给另外一个对象对应成员。
对象复制的一般形式:
对象名1 = 对象名2; |
注意:对象名1和对象名2必须是同一个类
例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
public:
string name;
int math;
int chinese;
stu(int math, int chinese) :math(math), chinese(chinese),name("yaoyao")
{
;
}
stu(const stu& pp) //拷贝构造函数
{
cout << "进入拷贝构造函数" << endl;
this->math = pp.math; //对象的赋值
//this->chinese = pp.chinese;
}
void mysum() //成员函数
{
cout << math + chinese << endl;
}
};
int main()
{
stu a1(100, 90);
stu a2(70, 30);
stu a3=a1; //stu a3(a1);
//a3 = a1;
cout << "a3_math=" << a3.math << a3.chinese << endl;
//cout <<"a2_math="<< a2.math << endl;
//a2 = a1; //对象间的赋值操作,主要针对的是对象中的数据成员
//cout << "a2_math=" << a2.math<<a2.chinese << endl;
return 0;
}
友元函数
在C++中,我们使⽤类对数据进⾏了隐藏和封装,类的数据成员 ⼀般都定义为私有成员,成员函数⼀般都定义为公有的,以此提供类与外界的通讯接⼝。但是,有时需要定义⼀些函数,这些函数不是类的⼀部分,但⼜需要频繁地访问类的数据成员,这时可以将这些函数定义为该类的友元函数。除了友元函数外,还有友元类,两者统称为友元。
作用:提⾼了程序的运⾏效率 ,但是破坏了封装性和隐藏性使得非成员函数可以访问类的私有成员。
格式为:
friend <返回类型> <函数名> (<参数列表>); |
例子:
#include <iostream>
#include <string>
using namespace std;
class stu //创建了一个类就是声明一个类型
{
private:
string name;
public:
int math;
int chinese
stu(string name,int math, int chinese) :math(math), chinese(chinese),name(name)
{
;
}
friend void myfun(stu& mypp, string new_name); //友元函数
void mysum() //成员函数
{
cout << name << endl;
cout << math + chinese << endl;
}
};
void myfun(stu& mypp, string myname)//复制构造函数
{
mypp.name = myname; //对象的赋值
}
int main()
{
stu s1("yaoyao", 12, 34);
myfun(s1, "leilei");
s1.mysum();
return 0;
}
注意:友元函数可以访问类中的私有成员和其他数据,但是访问时不可直接使⽤数据成员,需要通过对象进⾏引⽤。
前项引⽤声明
可以通过前向引用声明来解决一个不完整类时不能定义该类对象也不能在内联成员函数中使用该类的对象的问题,所谓的前向引用声明就是在引用未定义之前,将该类的名字告诉编译器,使编译器知道那是一个类名。
例子:
class B; //前项引⽤声明
class A
{
public :
void test1(B price);
};
class B
{
public:
void test2(A ak);
};
void A::test1(B price)
{
cout << "我是A类中的⼦成员函数";
}
void B::test2(A ak)
{
cout << "我是B类中的⼦成员函数";
}
int main()
{
A aa;
B bb;
aa.test1(bb);
}
在C++中可重⽤性是通过继承(inheritance)这⼀机制来实现的。继承是C++的⼀个重要组成部分。
注意:基类构造函数的调用顺序与成员初始化列表中的基类排序无关,只与派生类列表中的声名顺序有关。
派生类声名
单继承派生类的语法定义:
class 派生类名 : [继承方式] 基类名 //B类继承A类 { 派生类新增成员 } |
多继承语法定义:
class 子类名:访问控制 父类1,访问控制 父类2... { 类体 } |
注意:
1#冒号后面的被称为基类名表,基类名表的构成:访问控制+基类名;
2#访问控制表示派生类对基类的继承方式,使用关键字public、public、protected。(默认为private)
3#派生类经历的三个步骤:
1)吸收基类成员
2)改造基类成员
3)添加新的成员
***在声明派⽣类时,⼀般还应当⾃⼰定义派⽣类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
例子:
#include <iostream>
#include <string>
using namespace std;
class father
{
private:
string name;
public:
father() { cout << "我是⽗类中的构造函数" << endl; }
void show()
{
cout << "我是类中的普通成员函数" << endl;
}
};
class son :public father
{
public :
int a;//新增的数据成员
void test() //新增的成员函数
{
cout << "我是⼦类中的成员函数" << endl;
}
void show() //改造基类成员函数
{
cout << "我是改造基类成员父类中的成员函数" << endl;
}
};
int main()
{
son per1;
per1.show();
per1.test();
}
派生类的成员访问属性(继承方式)
公有继承
基类成员 | 派生类成员 | 访问权限 |
public | public | 派生类成员函数 派生类对象 |
protected | protected | 派生类成员函数 |
private | 不可见 | 基类的非私有成员函数 |
私有继承
基类成员 | 派生类成员 | 访问权限 |
public | private | 派生类成员函数 |
protected | private | 派生类成员函数 |
private | 不可见 | 基类的非私有成员函数 |
保护继承
基类成员 | 派生类成员 | 访问权限 |
public | private | 派生类成员函数 |
protected | private | 派生类成员函数 |
private | 不可见 | 基类的非私有成员函数 |
小节:
1#⼀个成员在不同的派⽣层次中的访问属性可能是不同的。它与继承⽅式有关。
2#无论是哪一种继承方式,在派生类中是不能访问基类的私有成员,私有成员只能被本类成员函数所访问。
3#继承方式选择建议:
1)需要被外部访问的成员直接设置为public;(尽量多用,经验丰富后考虑使用后两种)
2)只能在当前类被访问的成员设置为private;
3)只能在当前类和自雷中访问的成员设置为protected,protected:修饰的成员变量和成员函数是为了在家族中使用;
派生类的构造函数和析构函数
派⽣类的构造函数:
派生类类名::派生类名(参数列表):基类1(基类1初始化),基类2(基类2初始化)... { 本类成员初始化赋值语句; } |
需要注意:
1#子类在继承后的派生类在实例化的时候先调用父类的构造函数然后在调用自己的构造函数,如果定义一个类,最好写一个默认构造函数。
2#初始化的顺序与构造调用基类的次序无关,其实是与继承的次序来调用构造函数。
3#类中对派生构造函数作声名时,不包括基类构造函数名及其参数列表(即Student(n,nam,s)。只在定义函数时才将它列出。
派⽣类的析构函数:
派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的析构函数,用来对派生类型所增加的成员进行清理工作。
二义性问题
在多继承时,基类与派⽣类之间或基类之间出现,同名成员时,将出现访问时的⼆义性(不确定性)。
例如:
class A {
public:
void f();
};
class B {
public:
void f();
void g();
};
class C :public A, public B
{
public :
void g();
void h();
};
解决⼆义性问题:⽤类名来限定⽐如c1.A::F()或c1.B::f()或者利⽤同名隐藏来解决 在C中再定义⼀个f函数,在根据需要调⽤A::F()或B::f()因此,如果能⽤单继承解决的问题不⽤使⽤多重继承。
基类与派生类的转换
不同类型数据之间在⼀定的条件下可以进⾏类型的转换,⽐如我们可以把⼀个整数类型赋值给float类型,在赋值之前需要把整数类型转换为单精度类型,但是不能把整数类型赋值给指针类型。这种不同类型之间的⾃动转换和赋值称为赋值兼容。基类与派⽣类型对象之间也有赋值兼容关系,因为派⽣类中包含从基类继承的成员,因此可以将派⽣类的值赋值给继类对象。
例如:
#include <iostream>
#include <string>
using namespace std;
class per
{
public:
string name;
int age;
void run()
{
cout << name << age;
}
};
class stu :public per
{
public:
int stu_id;
string class_name;
stu()
{
name = "xiaoming";
age = 12;
stu_id = 1000;
}
};
int main()
{
per a;
stu s;
a = s;
a.run();
return 0;
}
//所谓的赋值只是对数据成员赋值,对成员函数不存在赋值问题。
//赋值后不能试图通过a去取访问派⽣类对象s的成员
小节:
1#只能用子类对其基类对象复制,而不能使用基类对象对子类对象赋值。因为基类对象不包含派生类成员,另外对于同一基类的不同派生类对象之间不能赋值。
2#如果函数的参数是基类对象或者基类对象的引用,相应的实参可用于基类对象。
继承与组合
在⼀个类中可以⽤类对象作为数据成员即⼦对象,对象成员的类型可以是本派⽣类的基类,也可以是另⼀个已定义的类。
在⼀个类中以另⼀个类的对象作为数据成员的,称为类的组合。
构造函数语法结构:
类名::类名(对象成员所需形参,本类成员形参):对象1(参数),对象2(参数),本类成员形参(参数) { 函数体其他语句; } |
例如:
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
string idCard;
string name;
string gender;
short int age;
string profession;
string phone;
Person();
Person(string idCard, string name, string gender, short int age, string profession, string phone) :idCard(idCard), name(name), gender(gender), age(age), profession(profession), phone(phone)
{
cout << "我是人类" << endl;
}
void message();
};
Person::Person()
{
cout << "我是人类" << endl;
}
void Person::message()
{
cout << "身份证" << idCard << endl;
cout << "姓名" << name << endl;
cout << "性别" << gender << endl;
cout << "年龄" << age << endl;
cout << "职业" << profession << endl;
cout << "联系方式" << phone << endl;
}
class Teacher :public Person
{
public:
string wordID;
string curriculum;
string academy;
Teacher(string idCard, string name, string gender, short int age,
string profession, string phone,string wordID, string curriculum,string academy)
:Person(idCard, name, gender, age, profession, phone), academy(academy)
{
cout << "我是教师" << endl;
};
void teach();
};
void Teacher::teach()
{
cout << "职工号:" << wordID << endl;
cout << "教授的课程名:" << curriculum << endl;
cout << "所属院系:" << academy << endl;
}
class MathTeacher :public Teacher
{
public:
string name1;
MathTeacher(string name, string idCard, string PersonName, string gender, short int age,
string profession, string phone, string wordID, string curriculum, string academy) :Teacher(idCard, PersonName, gender, age, profession,
phone, wordID, curriculum, academy), name1(name)
{
;
//Teacher::name = name;
}
void teach();
};
void MathTeacher::teach()
{
cout << name1 << "\t" << Person::name << endl;
cout << "我是从事数学教学的" << endl;
}
int main()
{
MathTeacher mt1("zhangsan","123456789", "xiaoming", "man", 18, "111", "666666","01", "C语言", "计算机院系");
mt1.teach();
cout << mt1.age << endl;
//Teacher t1;
return 0;
}
构造函数语法结构:
类名(形参):初始化列表 { 其他初始化; } |
注意:先初始化对当前对象,在进行其他初始化。
***三种情况介绍
情况1、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员的初始化);
情况2、需要初始化const修饰的类成员或初始化引用成员数据;
情况3、子类初始化父类的私有成员;