C++语言基础
C++基础
1、C++中的数据类型
bool 布尔型 1字节(取决于编译环境)
char 字符型 1字节
int 整数型 4字节
float 浮点型 4字节
double 双浮点型 8字节
指针 32位占4字节,64位占8字节
2、typedef : 为一个已有的类型取一个新的名字。
例:typedef int feet; //该语句告知编译器,feet是int的另一个名称;
2、枚举变量
3、定义常量
4、C++函数
5、C++指针
指针是一个变量,存储的是另一个变量的地址
6、结构struct
C++中的结构即用户自己定义的一种数据类型
7、函数的参数传递
函数未被调用时,函数的形参不占有实际的内存空间,也没有实际的值。只有在函数被调用时才为形参分配存储单元,并将实参与形参结合。
8、类与对象
定义类即自定义一种数据类型,要说明类中的数据成员以及对数据成员的操作方法。
定义类的语法形式:
class 类名称{
public:
外部接口
protected:
保护型成员
private:
私有成员
};
在类中可以只对函数原型进行声明,函数的实现可以在类外定义,在类外进行函数的定义时需要用类名限制,如 void Clock :: showTime(){} 表示类Clock中的函数void showTime();
9、构造函数
构造函数一般被声明为与类同名的公有函数,在对象被创建时将被自动调用。
如果类中没有写构造函数,编译器会自动生成一个默认形式(无参,什么都不做)的构造函数,如果类中声明了构造函数,则编译器不会再为其生成生成任何形式的构造函数。
构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。
10、拷贝构造函数:使用一个已经创建好的对象(由拷贝构造函数的参数指定),去初始化同类的一个对象。
如果类中没有定义拷贝构造函数,系统就会在必要时自动生成一个默认的拷贝构造函数,默认的拷贝构造函数的功能是 把初始值对象的每个数据成员的值都复制到新建立的对象中。
class 类名{
public:
类名(形参表); //构造函数
类名(类名 &对象名);//拷贝构造函数
};
类名 ::类名(类名 &对象名){ //拷贝构造函数的实现
函数体
}
调用拷贝构造函数的三种情况:
①用类的一个对象去初始化该类的另一个对象
②如果函数的形参是类的对象,调用函数时,进行形参和实参结合时
③函数的返回值是类的对象,函数执行完成返回调用者时
#include
using namespace std;
class Point
{
public:
//构造函数
Point(int xx = 0, int yy = 0) {
X = xx;
Y = yy;
}
//拷贝构造函数
Point(Point& p);
int GetX() {
return X;
}
int GetY() {
return Y;
}
private:
int X, Y;
};
Point::Point(Point& p) {
X = p.X;
Y = p.Y;
cout << “拷贝构造函数被调用” << endl;
}
void fun1(Point p) {
cout << p.GetX() << endl;
}
Point fun2() {
Point A(1, 2);
return A;
}
int main(int argv, char** args) {
Point A(4, 5);
Point B(A); //情况①
cout << B.GetX() << endl;
fun1(B); //情况②
B = fun2(); //情况③
cout << B.GetX() << endl;
return 0;
}
11、析构函数
析构函数通常也是类的公有函数,不接受任何参数,如果不在类中定义析构函数,系统也会生成一个不做任何事的默认析构函数。
析构函数用于完成对象被删除前的一些清理工作,在对象的生存期即将结束的时刻被自动调用的。如果希望程序在对象被删除之前的时刻自动(不需要人为进行函数调用)完成某些事情,就可以把它们写到析构函数中。
12、组合类 : 一个类内嵌其他类的对象作为成员,它们之间是包含与被包含的关系
组合类中构造函数调用顺序:①先按内嵌对象在组合类的定义中出现的顺序,调用内嵌对象的构造函数 ②再调用本类构造函数的函数体
组合类中析构函数的调用顺序: 与构造函数调用顺序相反
13、前向引用声明
在引用未定义的类之前,将该类的名字告诉编译器,使编译器知道那是一个类名。
14、static静态数据成员
用static关键字声明,静态数据成员由该类的所有对象共同维护和使用,从而实现同一类中不同对象之间的数据共享。static 声明的成员属于整个类,不属于任何一个对象。只能通过类名进行访问,一般用方法为“类名::标识符”。
在类中对静态数据成员只进行声明 static int countP;必须在文件作用域(全局作用域)的某个地方使用类名限定对静态数据成员进行定义 int Point::countP=0;此时也可以进行初始化。
15、静态成员函数
静态成员函数和静态数据成员一样属于整个类,由该类的所有对象所共享。
对于公有的静态成员函数可以通过类名或对象名来调用,而一般的非静态成员函数只能通过对象名来调用。
16、友元函数和友元类
友元可以通过关键字friend进行声明
①友元函数是在类中用friend修饰的非成员函数。友元函数可以在类外定义,也可以是其他类的成员函数,友元函数可以通过对象名来访问类的私有数据成员。
②同友元函数一样,一个类可以将另一个类声明为友元类。
若A类为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护数据成员。
注:友元关系是单向的,不可被继承,不可传递。
17、常引用 : 常引用所引用的对象不能被更新
const 类型说明符 &引用名
18、常对象 :常对象的数据成员值在对象的整个生存期间内不能被改变,常对象必须进行初始化,且不能被更新。常对象只能调用类中的常成员函数。
类名 const 对象名;
19、常成员函数
类型说明符 函数名(参数列表) const;
常成员函数不能更新对象的数据成员,不能调用该类中没有用const修饰的成员函数
20、常数据成员
const 类型说明符 数据成员名;
常数据成员只能通过构造函数的初始化列表进行赋值,不能在其他任何函数中进行赋值。
21、C++程序的一般组织架构
类定义文件(.h文件)
类实现文件(.cpp文件)
类的使用文件(*.cpp,主函数文件)
22、编译预处理
C++中所有的编译预处理指令都用 # 开头,每一条预处理指令单独占一行,不要用分号结束。
①#include指令
文件包含指令,将另一个源文件嵌入到当前源文件中该点处
#include <文件名> :按标准方式搜索,文件位于C++系统目录的include子目录下
#include “文件名” :首先在当前目录中搜索,若没有,再按标准方式搜索
文件名分为老式和新式,如老式math.h、新式cmath,两种不能混用,用新式就全为新式,用老式就全为老式。如果用新式,则需要写命名空间using namespace std;
②#define 和 #undef指令
#define 宏定义命令,用于将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
#define 标识符 字符串
简单的define定义 :#define MAXSIZE 1000 //MAXSIZE代表1000
define的函数定义:#define max(a,b) (a)>(b)?(a):(b)
define的多行定义:需要在每个换行的时候加上”/”
#undef 用于删除由#define定义的宏,使之不起作用。
③条件编译指令
使用条件编译指令,可以限定程序中的某些内容要在满足一定条件的情况下才参与编译。
常用条件编译语句:
#if 常量表达式
程序段
#endif
#if 常量表达式
程序段1
#else
程序段2
#endif
#if 常量表达式1
程序段1
#elif 常量表达式2
程序段2
…
#else
程序段n
#endif
如果“标识符”经 #define 定义过,且未经undef删除,则编译程序段1,否则编译程序段2,如果没有程序段2,则 #else可以省略
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。如果没有程序段2,则 #else 可以省略。
#ifndef 标识符
程序段1
#else
程序段2
#endif
④C++中宏定义https://www.cnblogs.com/GuoXinxin/p/11684337.html
头文件中用#ifndef/#define/#endif 来防止该头文件被重复引用,
#ifndef XXX_H //意思是 “if not define XXX_H” 也就是没包含XXX.h
#define XXX_H //就定义__XXX_H__
… //此处放头文件中本来应该写的代码
#endif //否则不需要定义
若未定义XXX.h则这里就定义XXX.h,然后运行里面的内容,若下次还走到了这个文件,则进行#ifndef的判断,则#ifndef与#endif之间的内容不会再次被载入
头文件中另一种用于防止重复引用的方法
#pragma once
… //此处放头文件中本来应该写的代码
#pragma once 是个预处理指令,在头文件的最开始加入这条指令表示:这个头文件只被编译一次,是由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
⑤defined 操作符(不是指令,不要以#开头)
defined(标识符)
若“标识符”在此前被#define定义过,且没有被#undef删除过,则该表达式值为非零,否则表达式值为0.
23、数组
使用数组名作为函数参数时,实参和形参都应为数组名,且使用数组名传递数据时,传递的是地址。
对象数组:对象数组的元素为对象,数组元素不仅具有数据成员,而且还有函数成员。
24、指针和引用
指针是一种数据类型,具有指针类型的变量称为指针变量。指针变量用于存放内存单元地址。
声明指针: 数据类型 标识符;
①与地址相关的运算符 指针运算符“”和 取地址运算符“&”
- 在声明语句中出现在被声明变量之前,表示声明的是指针。
int *p;//声明p是一个int型指针 - 出现在执行语句中或声明语句的初值表达式中作为一元运算符时,表示访问指针所指对象的内容。
cout<< *p; //输出指针p所指向的内容
& 在声明语句中出现在被声明变量的左边时,表示声明的是引用。
int& rf;//声明一个int型的引用rf
& 在给变量赋初值时出现在等号右边或在执行语句中作为一元运算符出现时,表示取对象的地址。
int a,b;
int *pa ,*pb = &b;
pa = &a; //取a的地址赋给pa
②指向常量的指针:
不能通过指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象
const char* name1 = “jjj”; //name1是指向常量的指针
③指针类型为常量
指针本身的值不能被改变
char* const name2 = “jjj”;
④用指针处理数组元素
int array[5];
数组名为数组的首地址,数组中下标为i的元素就是 *(数组名+i)
array 即array[0] ;(array+3) 即array[3];
⑤指针数组
一个数组的每个元素都是指针变量,必须先赋值,后引用
⑥指向函数的指针
⑦对象指针:对象指针即用于存放对象地址的变量。
类名 *对象指针名;//声明对象指针
对象指针名 ->成员名;// 访问对象的成员
⑧强指针sp<> 和弱指针wp<> :为了解决C++中new对象后不能自动销毁
强指针与一般的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用者都放弃了对该对象的引用,则该对象将自动销毁。
sp<类型> 指针名;//例:sp p_obj;
⑨指针的引用
引用的类型为指针
输出v和p:
25、继承与派生
class 派生类名 :继承方式 基类名1,继承方式 基类名2,...{
派生类成员声明;
};
派生类继承了基类的全部数据成员和除了构造函数、析构函数之外的全部函数成员,但这些成员的访问属性由继承方式控制。
①继承方式
公有继承:基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员在派生类中不可直接访问。
私有继承(默认):基类的公有成员和保护成员都以私有成员身份出现在派生类中,基类的私有成员在派生类中不可直接访问。
私有继承时,为了保证基类的一部分接口可以在派生类中也存在,就必须在派生类中重新声明同名的成员。
私有继承的派生类作为基类再派生时,其基类的所有成员无法在新派生的类中被直接访问。
保护继承:基类的公有成员和保护成员都以保护成员的身份出现在派生类中,基类的私有成员在派生类中不可直接访问。
②派生类的构造函数
基类的构造函数和析构函数不能被继承,派生类的构造函数只负责对派生类新增的成员进行初始化。
构造派生类对象时,首先隐含调用基类和内嵌对象成员的构造函数,来初始化它们各自的数据成员,然后才执行派生类构造函数的函数体。
构造函数的调用顺序为:调用基类的构造函数->调用内嵌对象的构造函数->调用自身的构造函数。构造函数的调用次序完全不受构造函数初始化列表的表达式中的次序影响,与基类的声明次序和内嵌对象在函数中的声明次序有关。
派生类构造函数形式:
派生类名::派生类名(参数总表):基类名1(参数表1),...,基类名n(参数表n),内嵌对象名1(内嵌对象参数表1),..,内嵌对象名m(内嵌对象参数表m){
派生类新增成员的初始化语句;
}
③派生类的析构函数
派生类的析构函数调用顺序和构造函数相反
④虚基类
虚基类语法格式: class 派生类名:virtual 继承方式 基类名
类D同时继承类B和类C,类B和类又都继承于类A,若A中有函数fun(),则B和C同时继承,当在D中调用fun()时,在C++中有两种方式来实现。
使用作用域标识符 ::
#include
using namespace std;
class A {
public :
A() {
a = 5;
cout << "In A a = " << a << endl;
}
protected:
int a;
};
class B : public A {
public:
B() {
a = a + 10;
cout << "In B a = " << a << endl;
}
};
class C :public A {
public :
C() {
a = a + 20;
cout << "In C a = " << a << endl;
}
};
class D : public B, public C {
public :
D() {
cout << "B::a= " << B::a << endl;
cout << "C::a= " << C::a << endl;
}
};
int main(int argv, char** args) {
D d;
return 0;
}
使用虚基类,使派生类中只保留一份拷贝,共同维护一个成员
#include
using namespace std;
class A {
public:
A(){
a = 5;
cout << "In A a= " << a << endl;
}
protected:
int a;
};
class B :virtual public A {
public:
B(){
a = a + 10;
cout << "In B a= " << a << endl;
}
};
class C :virtual public A {
public :
C(){
a = a + 20;
cout << "In C a= " << a << endl;
}
};
class D :public B, public C {
public:
D(){
cout << "In D a= " << a << endl;
}
};
int main(int argv, char** args) {
D d;
return 0;
}
26、多态
①运算符重载
定义重载运算符就像定义函数,只不过此时函数名为 operetor@,这里@代表被重载的运算符。
参数列表中参数的个数取决于两个因素:
运算符是一元的(一个参数)还是二元的(两个参数)
运算符被定义为全局函数/友元(对于一元是一个参数,对于二元是两个参数),运算符被定义为成员函数(对于一元没有参数,对于二元是一个参数——此时该类的对象用做左侧参数)
例:Person Person::operator+(Person& p) { //定义重载‘+’的函数,函数名为operator+,
Person tmp§;
tmp.age = this->age + p.age;
tmp.name = this->name;
return tmp;
}
使用重载的‘+’时:
Person p3 = p1 + p2; //p1+p2等价于 p1.operator+(p2); p1调用operator+()函数
一、运算符重载为成员函数
函数类型 operator运算符(形参表){
函数体;
}
二、运算符重载为友元函数
friend 函数类型 operator 运算符(形参表){
函数体;
}
以下两中调用方式等价
②虚函数
在声明函数时添加virtual关键字
virtual 返回值 函数名(形参列表){
函数体
}
用基类类型的指针指向派生类的对象,从而通过基类的指针调用实际派生类的成员函数。
当基类中声明虚函数后,判断派生类中某一没有显式声明为虚函数的成员函数是否为虚函数:
该函数是否与基类虚函数具有相同函数名
该函数是否与基类的虚函数具有完全相同的参数列表
该函数是否与基类的虚函数具有完全相同的返回值
如果派生类中的成员函数满足以上三个条件,则该函数自动确定为虚函数。这时派生类的虚函数便覆盖了基类的虚函数,并且派生类中虚函数会隐藏基类中同名函数的其他重载形式。
③虚析构函数
基类的构造函数不能被声明为虚函数,但析构函数可以,如果一个类的析构函数是虚函数,那么由它派生来的所有子类的析构函数都是虚函数。
④ 纯虚函数
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的格式为:
virtual 返回类型 函数名(参数列表)=0;
⑤抽象类
带有纯虚函数的类就是抽象类。
抽象类声明了一族派生类的共同接口,接口的完整实现即纯虚函数的函数体,要由派生类自己定义。
抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍为抽象类。
抽象类不能实例化即不能定义一个抽象类的对象,但可以声明一个抽象类的指针和引用。通过指针和引用可以指向并访问派生类对象,进而访问派生类的成员。
27、函数模板和类模板
①函数模板
当两个函数只有参数类型不同,功能完全一样时,可以用函数模板
template 或 template
返回值类型 函数名(参数列表){
函数体;
}
其中T即为类型参数,在声明函数模板时,需要用到不同类型数据的地方即可用T代替。
②类模板
使用类模板用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值能取任意类型(包括系统预定义的和用户自定义的)。
template<模板参数表>
class 类名{
类成员声明
}
如果要在类模板以外定义其成员函数,要采用以下形式:
template<模板参数表>
返回值类型 类名::函数名(参数列表)
使用模板类建立对象时,应按如下形式:
模板<模板参数表> 对象名1,…对象名n;
#include
using namespace std;
struct Student
{
int id;
float gpa;
};
//模板类声明
template
class Store {
private:
T item;
int haveValue;
public:
Store();
T GetElem();
void PutElem(T x);
};
//模板类中的函数在类外实现,则必须是模板函数
template
Store::Store():haveValue(0){}
template
T Store::GetElem() {
if (haveValue == 0) {
cout << “No item present” << endl;
exit(1);
}
return item;
}
template
void Store::PutElem(T x) {
haveValue++;
item = x;
}
int main(int argv, char** args) {
Student g = { 1000,23 };//声明Student结构体变量的同时赋以初值
Store s1, s2;
Store s3;
Store D;
s1.PutElem(3);
s2.PutElem(-2);
cout << s1.GetElem() << " " << s2.GetElem() << endl;
s3.PutElem(g);
cout << s3.GetElem().id << " " << s3.GetElem().gpa <<endl;
D.PutElem(8.8);
cout << D.GetElem() << endl;
return 0;
}
28、常量const
const修饰“最靠近”它的那个。
①指向const的指针
不需要初始化,该指针可以指向任何标识符(即该指针不是const),但它所指向的元素的值不能被改变。
const int* u; // u是一个指针,它指向const int
②const指针
需要初始化,使指针本身成为const,必须把const 放在的右边,因为const修饰离它近的那个。
int d = 1;
int const u = &d; // u是一个指针,这个指针是指向int的const指针
③const修饰函数参数
函数参数按值传递,则可用指定参数是const的,此时函数的参数变量初值不会被函数改变。
void f1(const int i){
i++; //错误
}
④函数返回const值
⑤const对象和成员函数
const对象即被const修饰符修饰的对象,const对象的数据成员在其生命周期内不被改变。
如果声明一个成员函数为const,则该成员函数可以被const对象调用。
一个没有被明确声明为const的成员函数被看成是将要修改对象中数据成员的函数,且不能被const对象调用。
const修饰成员函数时,在声明和定义中都要写const修饰符。且要把const写在函数参数列表之后。
29、volatile
volatile 语法和const相同,但是volatile是指“在编译器认识的范围外,这个数据可以被改变”
30、引用
引用就是一个变量的别名,对引用的操作与对变量的直接操作完全一样。
注意和取地址符区分。取地址时符号&在变量名前挨着,引用时符号&在数据类型后挨着
数据类型& 引用变量名 = 已定义过的变量名;
定义一个引用时,如果该引用不是用作函数的参数或返回值,则该引用必须被初始化。
当函数被调用时,作为形参的引用变量并不分配新的内存空间,它将作为实参变量的别名与其共用内存。
使用引用参数可以直接操作实参变量,从而实现通过修改形参的值而达到修改对应实参的目的。
例:
引用作为函数返回值 返回类型& 函数名(参数列表){}
引用作为函数的返回值,不会在内存中产生返回值的副本。
31、inline内联函数
内联函数在函数定义时,加上关键字 inline
定义:当函数被声明为内联函数后,编译器会在用到该函数的地方将其内联展开,而不是按通常的函数调用机制进行调用,可以理解为将内联函数的函数体复制到用到该函数的地方。因此,内联函数一般适合只有几行的函数。如:
inline int max(int a, int b)
{
return a > b ? a : b;
}
则调用: cout<<max(a, b)<<endl;
在编译时展开为: cout<<(a > b ? a : b)<<endl;
关键字inline必须和函数定义体放在一起才起作用,仅在声明时加关键字inline不管用
定义在类声明中的成员函数自动地成为内联函数,即使没有关键字inline
内联函数应该在头文件中定义,这一点不同于其他函数。编译器在调用内联展开函数的代码时,必须能够找到 inline 函数的定义才能将调用函数替换为函数代码,而在头文件中仅有函数声明是不够的。
当然内联函数定义也可以放在源文件中,但此时只有定义的那个源文件可以用它,而且必须为每个源文件拷贝一份定义(即每个源文件里的定义必须是完全相同的),当然即使是放在头文件中,也是对每个定义做一份拷贝,只不过是编译器替你完成这种拷贝罢了。但相比于放在源文件中,放在头文件中既能够确保调用函数是定义是相同的,又能够保证在调用点能够找到函数定义从而完成内联(替换)。
32、extern关键字
extern 与C连用,如extern “C” void fun(int a,int b); 则告诉编译器在编译fun函数名时按照C的规则去编译而不是C++的。也可以包含复合语句
extern “C”{
double sqrt(double n);
int min(int a ,int b);
}
extern 置于变量或函数声明之前,如extern int m; extern int f(); 说明此变量/函数是在别处定义的,要在此处引用。
33、C++函数可变参数(用…表示)
函数返回类型 函数名(参数1,参数2,…){}
34、explicit关键字
explicit关键字防止类构造函数的隐式自动转换,只对只有一个关键字的构造函数和参数列表除了第一个关键字其他都有默认值的构造函数有效。如果类构造函数参数大于等于两个,是不会产生隐式转换的。
#include
using namespace std;
class Test1
{
public :
Test1(int num):n(num){}
private:
int n;
};
class Test2
{
public :
explicit Test2(int num):n(num){}
private:
int n;
};
int main()
{
Test1 t1 = 12; //编译成功
Test2 t2(13);
Test2 t3 = 14; //编译报错
return 0;
}
35、constexpr关键字
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。
声明为constexpr的变量一定是个常量,且必须用常量表达式初始化。
constexpr函数指能用于常量表达式的函数。定义constexpr函数有几项约定:
函数的返回值类型及所有的类型都得是字面值类型(编译时就能得到结果的类型,具体包括算术类型、引用和指针)。
函数体中必须有且只有一条return语句。
36、智能指针
C++11 提供了 3 种智能指针类型,它们分别由 unique_ptr 类、shared_ptr 类和 weak_ptr 类定义,所以又分别称它们为独占指针、共享指针和弱指针。
unique_ ptr、shared_ ptr 和 weak_ ptr 类是在 memory 头文件中定义的。
智能指针背后的核心概念是动态分配内存的所有权。智能指针被称为可以拥有或管理它所指向的对象。当需要让单个指针拥有动态分配的对象时,可以使用独占指针。对象的所有权可以从一个独占指针转移到另一个指针,其转移方式为:对象始终只能有一个指针作为其所有者。当独占指针离开其作用域或将要拥有不同的对象时,它会自动释放自己所管理的对象。
共享指针将记录有多少个指针共同享有某个对象的所有权。当有更多指针被设置为指向该对象时,引用计数随之增加;当指针和对象分离时,则引用计数也相应减少。当引用计数降低至0时,该对象被删除。
37.C++中的接口
接口是一个共享框架,供两个系统之间交互使用。
38.内联函数
①用inline显式声明
② 函数定义位于类声明中的自动成为内联函数