如何解决mainmain本地函数定义是非法的问题?

c++面向对象指针与引用* p++ *++p都是先进行指针的位移,然后在解引用取值 int a[6], *p, i;
p = a;
for (i = 0; i < 6; i++)
printf("%d ", *a++);//error
a[i] *p++ *(a+i) p[i] *(p+i) 正确*a++ 错误----a为地址常量不能被改变引用引用在使用过程中自始至终指向初始化时所指,一般使用过程中不会改变。引用一旦成为某个变量的别名,在程序运行过程中不能修改这种指针关系。引用必须有所指向,即声明的同时初始化,即不能有空引用程序结构编译过程在C++中,想要生成一个可执行文件(exe)或者动态链接库(dll),需要经过编译和链接两个步骤1、预处理:预处理源代码中的带有#号的语句,生成编译程序可处理的文本文件;2、编译过程:对每个预处理后源程序,编译并生成相应的二进制目标文件(object文件)。此过程中,要求编译器能够识别每个标识符,知道其各自的类型、含义,但不要求知道存放位置.3、链接过程:对在整个程序范围内,确定各标志符所代表的地址,如变量、函数入口,生成可执行文件。此过程中,要求编译器能够确定每个标识符所对应的含义或地址。C++的编译是以实现文件(cpp文件)为基本编译单位的;预编译、编译和链接过程,有时我们统称编译过程;各阶段均无错误,才能生成可执行文件;即使生成了可执行文件,不代表程序就正确;让编译过程通过,是相对简单的;真正困难的是调试逻辑错误主函数main
C/C++普通可执行程序(exe)的起始函数返回值: int 或 void , 缺省为int. (C++1z 要求int)参数部分:无 或 (int argc,char* argv[ ])
argc和argv参数在用命令行编译程序时有用。main( int argc, char* argv[], char **env ) 中
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。
第二个参数,char*型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0] 指向程序运行的全路径名
argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
argv[2] 指向执行程序名后的第二个字符串
argv[3] 指向执行程序名后的第三个字符串
argv[argc] 为NULL
头文件实现文件正确写法:从各自的c/cpp文件出发,将其向外部公开的变量、函数、结构等,放入对应的头文件前置声明包含警戒告诉编译器,一个标识符所代表的类型、含义等,但不必告知其具体位置#ifndef <标识>//判断<标识>是否被#define过,如果被定义过,直接转到#endif
#define <标识>
......
......
#endif
#ifndef和#define是一对条件编译预处理指令,它们通常一起使用来防止头文件的重复包含#ifndef用于判断标识符是否已经被定义过,如果没有被定义过,就继续执行下面的代码。如果已经被定义过,就跳过下面的代码,避免重复包含。判断<标识>是否被#define过,如果被定义过,直接转到#endif。#define用于定义标识符,通常用于定义宏。作用:允许同一个cpp文件,include多次同一个头文件.#pragma once
#pragma once与上面的#ifndef和#define作用相同类型自定义类型typedef
定义类型格式:typedef <已知类型> <新类型>
定义函数类型格式: typedef 返回类型 (*新类型)(参数列表);
枚举类型
enum 枚举类型名 {枚举值列表 };
枚举值必须是整数第一个枚举值,缺省为0后一个枚举值,若没有指定,则为前一个枚举值+1枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量constconst int 和int const是等价的const函数1、返回值用const修饰
const int& fun(int& a); //修饰返回值
多是修饰返回值是引用类型的情况下,为了避免返回值被修改的情况class A
{
private:
int data;
public:
A(int num):data(num){}
~A(){};
int& get_data()
{
return data;
}
};
int main()
{
A a(1);
a.get_data()=3;
cout<<a.get_data()<<endl; //data=3
return 0;
}
data被修改加上const就不能被修改2、const 修饰函数实参
int& fun(const int& a); //修饰形参
不希望改变实参的值,就要加上const关键字3、const成员函数—>常成员函数
int& fun(int& a) const{} //const成员函数
const修饰的是函数内部的this指针普通函数是不是不能用const修饰,只有成员函数可以常量对象常量对象只能调用常成员函数和静态成员函数非const对象可以调用const修饰的成员函数和非const修饰的成员函数。const对象只能调用const修饰的成员函数,不能调用非const修饰的成员函数指针常量和常量指针指向变量的常指针:p指向的对象的值不能被修改----》常量指针
const int *p1 = &a;
*p不可修改 *p = 8;(ERROR)—值p 可以修改 p = &b (OK)—指向的对象
int a = 10;
const int *p1 = &a; // 指向常量的普通指针
*p1 = 30;//error
指向常量的普通指针:p所指向的值不能够被修改----》指针常量int* const p=exp;//必须初始化
**p可以修改*p = 8;(OK)*–值只可以被修改p不可以修改 p++(ERROR)----指向对象不能被修改指向常量的常量指针:p指针的指向不能够修改,指向的值也不能被修改const int* const p=exp;//必须初始化
关于char* 和const char*const char * str1="12345";
char *str2="12345";
在c++中上面两个式子是等价的如果想要去创建可改变的字符指针char str[] = "Hello World"; // 正确:使用字符数组
char* ptr = str; // 指向字符数组的指针
*ptr='#';
cout<<ptr<<endl;
输出#ello World
变成了可以改变的字符数组string 和 const char *string str="123123";
str.c_str();//为该str的const char *类型
static作用全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能在该函数定义的文件或者包含该函数定义的文件中被调用访问(即改变了作用域)。函数声明
[extern] [调用约定] <返回类型> <函数名>(<参数列表>) [const] (异常说明);
判断两个函数是否为一个函数:返回类型、缺省值不能作为区分标志int f(){}
void f(){}//error:重复定义
调用约定调用约定简要说明栈的清理者参数压栈顺序函数名的表示(不同编译器有细微差别)**__**cdecl标准C/C++调用约定调用者从右至左**_**MyFuncName**__**stdcall新标准C/C++调用约定被调用者从右至左MyFuncName__pascallPASCAL风格调用被调用者从左到右MYFUNCNAME__fastcall前两个参数尽可能放入寄存器被调用者从左到右(VC6.0) 从右到左(BCB6.0)@MyFuncName**__**thiscall成员函数时,this指针放入ECX寄存器,其它默认同__cdecl调用者从右至左_MyFuncNameXXX参数列表int f(int b,int a);
int f(int a,int ){
}
定义中的参数,可以不起名声明和定义的形参名不一定要一样缺省参数,应在函数原型中提供;若无函数原型,才在定义中给出参数进栈顺序压栈顺序由调用约定确定但计算顺序是不确定的f(1,2,3);//3先压栈后2后1
int a=10;
f(++a,++a,++a);//等价于f(13,12,11);
缺省参数的匹配如果没有对应的参数,尝试类型转换,但只能转换一次。第一个带缺省值的右侧都必须带有缺省值int f(int a,int b,int c=10,int d);//error
int d必须也要有缺省值,或者将int d提前到int c=10之前值传递需要在栈区创建一个值类型的对象,有性能损耗int f(int a,A a1);//可以穿对象,无const的区别
int f(const int a,const A a1);//与上面的函数等价,不构成重载
指针传递int f(int *a,A *a1);
int f(const int *a,const A *a1);//构成重载
在调用的时候找最匹配的,如果没有,先满足const的函数然后将非const的对象做类型转换数组传递int a[3]={1,2,3};
void f(int []);
void f(int a[]);
void f(int a[3]);
void f(int a[5]);
void f(int *a);//上面的几种形式都与此式等价
在c++内部数组使用指针实现的
引用传递int a=1;
const int b=&a;
const int c=1;
void f(int &,int &);
f(a,a);
f(a,b);//error
f(a,c);//error
f(a,3);//error
返回类型按值返回默认为int类型返回默认给返回类型加const指针返回也给指针加const,但是是指针常量,不是常量指针T* f();----1
T* const f();----2
const T* f();----3
1与2等价,与3不等价
引用返回T & f( ); 不等价于 const T & f( );必须返回一个有效对象的引用int global =100; //全局变量
int & other(int &, const int &
);
int & f(int a, int & b,const int & c) {
int lVar = 88; //局部临时量
static int
sVar=99; //局部静态量
return
a;//error
return
b;
return
c;//error,T & f( ); 不等价于 const T & f( );
return global;
return global+8;//error ,global+8为临时量
return
lVar; //error,局部变量在函数结束后消除
return sVar;
return sVar+2;//error,sVar+2临时量
return sVar+=3;//正确,返回的还是sVar
return other(lVar,b);
return other(sVar+2,global);//错误sVar+2不能以引用方式作为函数形参
}
函数的默认值参数的默认值必须在函数声明中指定,不能在定义中指定,否则编译报错。void f(string, string, string);
int main()
{
f("nihai", "woshi");
return 0;
}
void f(string a, string b, string c = "")
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
例如这样,编译器会报错error: too few arguments to function 'void f(std::string, std::string, std::string)'正确的写法为void f(string a, string b, string c = "");
int main()
{
f("nihai", "woshi");
return 0;
}
void f(string a, string b, string c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
函数调用时使用了默认值,则后续参数必须使用默认值,即参数调用时参数匹配从左到右extern修饰全局变量extern int i=10;
直接声明并且定义,表示外部可以链接此时的int i;extern int i;
链接了外部文件的int i,在此文件中可以直接被使用在头文件1中声明int i=10在文件2中包含头文件1并 extern int i则在文件2中可以使用i变量修饰全局常量//file1
extern const int J = 10; //定义拥有外部链接的全局常量J
//file2
extern const int J; //声明全局常量J来自于其他翻译单元
修饰局部变量可以用extern 使用来 声明局部变量 来自其他单元是不能使用extern定义一个拥有外部链接的局部单元void Test()
{
extern int i; //表明i来自于其他翻译单元
extern int j = 20; //错误,因为局部变量的生命周期在退出函数的时候就结束了,所以不允许其建立外部链接
}
名字空间引入定义一个名字空间#ifndef qjh
#define qjh
int m_x = 10;
void f()
{
std::cout << m_x << std::endl;
}
#endif
std名字空间 当前名字空间 全局名字空间#include<iostream>
using namespace std;
void f(int a);
近似看成namespace std{
#include<iostream.h>
void f(int a);
}//c风格代码
::f();//全局名字空间
f();//当前名字空间
自定义名字空间namespace my{
int a=10;
namespace my1{
}//嵌套名字空间
}
名字空间的别名namespace my=me;
匿名名字空间
当声明命名空间时的名称为空时,则该命名空间为匿名命名空间
匿名的空间是C++用于替代使用static定义作用域为本编译单元的全局函数或全局变量的一种新的替代方式,匿名空间与命名的命名空间一样可以嵌套namespace {
char c;
int i;
double d;
}
匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称(即这里的__UNIQUE_NAME_)绑定在一起。还有一点很重要,就是这些名称具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。名字汇入using只在大括号内使用名字空间std{
using namespace std;
·····
}
在全局使用名字空间不加{}只引入名字空间下的一部分using std::cout;
cout << "hello";
函数重载函数重载的条件参数的个数、类型、顺序、const修饰、异常不完全相同void f(int a);
void f(int a, int b);//个数
void f(string a);//类型
void f(const int a);//const 修饰
void f(int a)throw;//抛出异常
1、const int / volatile int / int 不构成重载2、int * 和 int* const 不构成重载3、const int& 和 int 构成重载4、int& 和 int 不构成重载5、const int& 和 int& 成重载6、int * 和 const int* 构成重载合法的重载void f(int a)const;
void f(int a);
可以构成重载
const对象调用const函数void f(int a)const;,非const对象调用非常成员函数void f(int a);引用和指针类型参数,是否可以改变实参,可作为区分标志int f(int &a);
int f(int a);
//不构成重载
int f(int &a);此函数可以改变实参a
int f(const int &a);不能改变实参a
//可以改变实参-->构成重载
int f(int *);
int f(int *const );
//不构成重载
int f(int *a);此函数可以改变实参a
int f(const int *a);不能改变实参a
//可以改变实参-->构成重载
值类型参数的const和非const型不作为区分标志void f(int a);
void f(const int a);
//不构成重载
返回值不作为区分标志void f(int a);
int f(int a);
//当调用的时候
f(1);//编译器不知道调用哪个---->报错error
缺省参数不作为区分标志int f(int a, int b = 20);
int f(int a, int b = 10);
//报错,缺省参数的值不能作为区分标志
//下面两种不会报错
int f(int a, string b = "asdqw");
int f(int a, int b = 10);
int f(int a);
int f(int a, int b = 10);
禁止函数重载
利用extern “C”{}
extern "C"
{
int f(int a);
int f(int a, int b);//error 报错--不能进行函数重载
}
运算符的重载分为自由函数和成员函数eg:自由函数:
T operator+(const T& t1,const T& t2){
return T(t1.x+t2.x);
}
成员函数:
class T {
T operator(cosnt T& t){
return T(t.x+x);
}
};
成员函数涉及到在类外要使用类的私有属性时,可以用友元friend使重载的运算符函数能够使用类的私有属性语法格式
返回类型 operator 运算符(参数列表)
{函数体;}
(1)重载运算符时,运算符的运算顺序和优先级不变,操作数个数不变(2)不能创造新的运算符,只能重载C++中已有的运算符(只能重载一元,二元运算符),并且规定有6个运算符不能重载不能重载的运算符含义.类属关系运算符.*成员指针运算符::作用域运算符?:条件运算符#编译预处理符号sizeof取数据类型的长度在C++中重载运算符时,返回值和参数是否带有引用符号(&)和const关键字,以及是否将函数声明为const成员函数,以下是一些指导原则:返回值是否带有&:如果我们希望在重载运算符的实现中修改左值对象的状态(例如+=运算符),那么返回值应该带有&,以便实现“链式调用”(例如a += b += c)。否则,可以不带有&。参数是否带有&:如果我们希望在重载运算符的实现中修改传入的对象的状态(例如+=运算符),那么参数应该带有&。否则,可以不带有&。参数是否带有const:如果我们希望在重载运算符的实现中不修改传入的对象的状态(例如+运算符),那么参数应该带有const。否则,可以不带有const。是否将函数声明为const成员函数:如果我们希望在重载运算符的实现中不修改当前对象的状态(例如+=运算符的右值对象),那么应该将函数声明为const成员函数。否则,可以不声明为const。返回值优化A operator+(const A& lhs, const A& rhs){
A
result(0,0);
result.x = lhs.x+rhs.x;
result.y = lhs.y+rhs.y;
return result;
}
上面代码西安创建result对象然后再返回对象的时候又创建了一次对象,应该为下面代码A operator+(const A& lhs, const A& rhs){
return A(lhs.x+rhs.x,lhs.y+rhs.y);
}
创建一个类A的匿名对象再返回可以实现返回值优化一元运算符
++ –
++a:前置++A& A::operator ++()
{
this->x++;
return *this;
}
a++:后置++A A::operator ++(int)//参数不参与任何计算,只是作为区分重载的标志
{
A temp(*this);
this->x++;
return temp;
}
编译器通过++的参数来判断是否前后置,构成重载二元运算符
= +\+= - *
成对重载:+和+=+=号:成员函数A& operator+=(const A& a)
{
x=x + a.x;
return *this;
}
+号:一般用自由函数给出,为了不使用友元,可以如下A operator+(const A& a1,const A& a2)
{
return A(a1)+=a2;//调用A类构造函数创建一个临时匿名对象作为函数返回
}
好处:可以去掉friend的声明,便于修改成员数据=A& operator=(const A& a)const//重载=运算符的实现
{
return this->x=a.x;//调用A类构造函数创建一个临时匿名对象作为函数返回
}
[]号class A {
public:
int operator[ ](int index) const
{
return nums[index];
}
int& operator[ ](int index)
{
return nums[index];
}
private:
int
nums[100];
};
()号:仿函数class A {
public:
int operator() ( ) const { return 999; }
int operator() (int a,int b ) const
{
return a+b;
}
};
->号:函数operator->() 返回指针类型;函数operator->() 返回自定义类型,且该类型中重载了 operator->( ).总结:运算符重载形式所有的一元运算符 (操作数为自定义类型)成员(建议)= () [ ] -> ->* (左操作数/操作数为自定义类型)必须是成员+= 、-=、 *=、 /=、 ^=、&=、|=、%=、>>=、<<=(左操作数是自定义类型,但右操作数的类型可以不一致)成员(建议)所有其他二元运算符(左操作数不一定是自定义类型)<< >>非成员(建议)永远不要重载&&,
和 , 三种操作符类classthis指针
类型为T* const this----指针指向的对象不能被改变
this的作用域和生存期是在非静态成员函数的{ } 内是非静态成员函数隐含的第一个形参this指针永远指向当前对象类A的普通成员函数中有this指针,类型为 A *const访问控制除友元外,private成员只有该类自身的成员函数可以访问;protected成员只有该类的成员函数及其派生类的成员函数可以访问;public成员可被所有的函数访问。常成员函数
常成员函数格式:
<返回类型><函数名>(<参数表>) const;
类A的常成员函数中的this指针,类型为const A* const。说明const修饰的是函数的this指针class A{
public:
void f();
void f()const;
//上面两个构成重载,常对象优先调用const成员函数
}
常对象只能访问常成员函数,不能访问非常成员函数实例变(常)量和实例方法正常的非静态成员函数,非静态成员变量类变(常)量和类方法(static)静态成员变量普通成员变量每个对象各有一份,而静态成员变量只有一份,被所有同类对象共享。通过: 类名::成员名 的方式访问,不需要指明被访问的成员属于哪个对象或作用于哪个对象。因此,甚至可以在还没有任何对象生成时就访问类的一个静态成员。非静态成员的访问方式其实也适用于静态成员。使用sizeof运算符计算对象所占用的储存空间时,不会讲静态变量计算在内。静态成员变量本质上是全局变量。静态数据成员必须在类体外初始化,只有静态的常量整形数据成员才可以在类定义中初始化,如static const int c=2; 而常实型静态数据成员不可以class circle
{
int a; // 普通变量,不能在类中初始化
static int b; // 静态变量,不能在类中初始化
static const int c=2; // 静态整型常量,可以在类中初始化
static const double PI=3.1416;//error C2864: 只有静态常量整型数据成员才可以在类中初始化
} ;
静态成员函数普通成员函数一定是作用在某个对象上的,而静态成员函数并不具体作用在某个对象上。静态成员函数并不需要作用在 某个具体的对象上,因此本质上是全局函数。没有隐含的this指针, 也不能带const修饰静态成员函数不能访问非静态成员变量和非静态成员函数的原因是因为非静态成员变量和非静态成员函数都是属于对象的,而静态成员函数是没有this指针的。内联实现inline只在实现同时存在时,才有意义。建议放在头文件中,一般函数的实现要在.cpp文件中实现,而内联函数要在.h中实现//t.h
#ifndef TH
#define TH
#include <iostream>
using namespace std;
class T
{
public:
void f( )
{
cout<<"void f( ) { }"<<endl;
}
inline void g( )
{ cout<<"inline void g() {}"<<endl;
}
void h( );
inline void k( ); //这里的inline无实际意义
};
inline void T:h( )
//这里的inline有意义
{
}
#endif
对象的内存调用与非静态数据成员的个数、类型有关与成员函数的个数无关与静态数据成员的多少、类型无关与访问控制无关一定非0—就算里面什么也没有,编译器也会分配空间与是否含有虚函数有关与编译时字节对齐方式设置有关class A
{
public:
void f(); // 0
int v1;
// 4
private:
int g();
// 0
int v2;
// 4
char c;
// 1-----可能由于编译器的对齐方式(使编译速度更快)变为4
int va[2];
// 8
int *b;
// 4
Person person; // N
Car *pCar;
// 4
Person &obj; // 4--本质还是指针
static int num; // 在程序区,不算在A 中的存储空间
};
构造和析构函数缺省的构造函数只要用户提供了自定义的构造函数,那么编译器将不会自动提供默认的构造函数(无参构造函数),如果用户还希望A a;需要自己实现无参的构造函数构造函数的初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
构造函数初始化列表的构造顺序与初始化列表的顺序无关:如果有继承,先构造继承,然后按照类中数据成员的声明顺序进行构造class A
{
public:
A(int x) : m_x(x) { cout << "A!" << m_x << endl; }
A(const A &rhs) : m_x(rhs.m_x)
{
cout << "A kaobei!" << m_x << endl;
}
protected:
int m_x;
};
class B : public A
{
public:
B(const A &x) : A(1), obj(2), obj3(3), obj2(x) {}
private:
A obj;
A obj2;
A obj3;
};
int main()
{
A a(8);
B b1(a);
// B b2(b1);
return 0;
}
初始化只能初始化一次,而构造函数体内可以多次赋值。(初始化和赋值的区别)必须放在初始化列表位置进行初始化的情况1.引用成员变量:引用只能初始化一次,不能在构造函数体内再次赋值class MyClass {
public:
MyClass(int& a) : kRefMember(a) {} // 引用类型成员变量的初始化
private:
int& kRefMember;
};
2.const成员变量:常量成员变量必须在构造函数的初始化列表中进行初始化,因为它们不能在构造函数体内被修改。class MyClass {
public:
MyClass(int a) : kConstMember(a) {} // 常量成员变量的初始化
private:
const int kConstMember;
};
3.没有默认构造函数(无参构造函数)的类类型成员变量使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。class A{
public:
A(int v):p(v),b(v){}
private:
int p;
Base b;//类base
};
4.存在继承关系,派生类必须在其初始化列表中调用基类的构造函数(基类也无无参构造函数)class Base
{
public:
Base(int a) : val(a) {}
private:
int val;
};
class A : public Base
{
public:
A(int v) : p(v), Base(v) {}//给基类base赋初值
void print_val() { cout << "class A:" << p << endl;}
private:
int p;
};
int main()
{
int m = 45;
A b(m);
b.print_val();
}
如何创建一个只能构造一次的类对象class Name {
public:
static Name & create( ) {
static Name obj;
return obj;
}
private:
Name( );
};
class Name {
public:
static Name * create( ) {
static Name * pObj = nullptr;
if (pObj == nullptr) {
pObj = new Name;
}
return pObj;
}
private:
Name( );
};
错题构造函数可以被重载,析构函数不可以被重载拷贝构造函数只有一个参数,这个参数是本类的对象(不能是其他类的对象),而且采用对象的引用的形式(一般约定加const声明,使参数值不能改变,以免在调用此函数时因不慎而使对象值被修改)。如果参数不使用引用:采用传值的方式传进函数,则在函数传值或者返回值也是传值的时候,会自动调用类的拷贝构造函数,因此会无限的进入循环,是程序崩溃。如果用户自己未定义复制构造函数,则编译系统会自动提供一个默认的复制构造函数,其作用只是简单地复制类中每个数据成员。
类名(类名& 对象名);
class MyClass {
public:
MyClass(); // 默认构造函数
MyClass(const MyClass& other); // 拷贝构造函数
// ...
};
拷贝构造的必要性1、在函数值传递进参数时void f(A a){};
此时要发生隐式的拷贝构造函数2、以传值方式返回对象时A f(){
A temp;
return temp;
}
以为temp在函数体内部,函数结束时要销毁,则需要借用拷贝构造函数,复制一个一模一样的对象返回什么情况不需要显式定义拷贝构造函数当一个类包含的只是内置类型或者可以被默认拷贝构造的类类型时,它们的拷贝构造函数可以由编译器自动生成,而不需要显式定义。浅拷贝(位拷贝、浅复制)
简单的将所有数据成员(除了自定义类型成员变量)复制到一个新创建的对象中,自定义类型成员变量调用自身类的拷贝构造函数。所以不是所有的数据成员都是简单的位拷贝
浅拷贝就是只将另一个类对象的指针成员,引用成员的地址复制到本类对象中,而不是重新开辟一块地址创建新的,如果在原来的对象中更改指针或引用数据成员的值的时候,那么浅拷贝的对象的指针和引用的数据成员的值也会改变,而对象数据的值不会被修改
缺省的拷贝构造函数:用户没有自定义拷贝构造函数,编译器自动提供一个public的拷贝构造函数浅拷贝的不足:有可能会将构造函数new的对象释放两次----->深拷贝能够解决这种问题----浅拷贝只是复制指针,深拷贝赋值内存地址深拷贝只能通过用户自定义拷贝构造函数什么时候要使用深拷贝?1、按照深拷贝的特性2、禁止拷贝---->将拷贝构造函数放到private属性中去3、防止按值传递对象4、数据成员有指针类型或引用类型的成员class A{};
void f(A a);//A如果拷贝构造函数在私有属性中,值传递参数会报错
A f();//返回值传递也是一样
//定义在私有属性中------->不能向上述那样传值
浅拷贝只是复制指针,而不是指向的内存,因此它们和原始对象共享同一个内存地址。这就导致了当一个对象的指针和引用成员变量被修改时,另一个对象的成员变量也会被同步修改。相反,深拷贝会将指针所指向的内存也进行复制,从而避免了对象之间共享内存的问题。对象的赋值
根据已有的其它对象,修改当前对象。
缺省(默认)赋值函数:用户没自定义赋值函数,编译器自动创建public的赋值函数–为浅赋值如果自定义类型的数据成员中有引用的成员,禁止赋值浅赋值的不足(类似于浅拷贝)对象类型重新开辟空间,指针类型的数据成员只是简单的将指针的地址赋值给左侧对象,改变右侧对象指针的值时,左侧对象指针的值也会改变可能会将原来b1中的pa指针所指向的区域被抛弃自定义赋值(重载=运算符)一般写法class B {
public:
B( )
{
pch = new char;
}
~B( ) {
delete pch;
}
B& operator=(const B& rhs) {
delete pch;
pch = new char(*rhs.pch);
return *this;
}
private:
char * pch;
};
int main( ) {
B
b1;
B &
b2= b1;
b1 = b1;//掉用赋值函数时 const B& rhs中的pch已经被释放掉了,会引发错误
b1 = b2;//引用与上面的同理
return 0;
}
当调用赋值函数的两个对象是同一个对象时会发生错误(按上面的写法),所以需要进行自我赋值判定正确写法B& B::operator=(const B& rhs)
{
if ( &rhs != this )
{//自我赋值判定
delete pch;
pch = new char(*rhs.pch);
}
return *this;
}
有继承关系的赋值函数的写法class A
{
public:
A(int x) : val(x) {}
A(const A &rhs)
{
val = rhs.val;
}
A &operator=(const A &rhs)
{
val = rhs.val;
return *this;
}
private:
int val;
};
class B : public A
{
public:
B(int x, int y = 0) : val(x), A(y) {}
B(const B &rhs) : A(rhs)
{
val = rhs.val;
}
B &operator=(const B &rhs)
{
A::operator=(rhs);
val = rhs.val;
return *this;
}
private:
int val;
};
拷贝构造函数和重载赋值运算符=的区别(赋值和拷贝的区别)A a1;
A a2;
a1=a2;//赋值
A a1;
A a2(a1);//拷贝
复制:从无到有新建一个赋值:对象已存在,只是修改拷贝构造函数是用来创建一个新对象,并且将已有对象的值复制到新对象中。它通常用于以下情况:通过值传递参数给函数,需要创建一个新对象来保存传递进来的参数值。返回一个对象时,需要创建一个新对象来保存返回值。在对象初始化时,需要创建一个新对象来保存已有对象的值。重载赋值运算符=则是用来将一个已有对象的值赋给另一个已有对象。它通常用于以下情况:对象已经存在,需要将一个对象的值赋给另一个对象。对象需要多次赋值,例如循环中的对象赋值操作。另外,拷贝构造函数和重载赋值运算符=的语法略有不同。拷贝构造函数的语法是类名(const 类名 &),而重载赋值运算符=的语法是类名& operator=(const 类名 &)。存储空间
非静态成员变量的存放和类实例的空间有关,类如果实例化在堆上,也就是New开辟的空间,这个成员变量就是存放在堆上,如果类的实例化在栈上,这个成员变量就是在栈上如果类的实例化为全局变量,则这个类就在全局区上
1、全局数据区、常量数据区->静态内存管理存放全局对象(常量,变量)、静态对象(static变量)程序结束的时候释放2、栈区->静态内存管理局部自动对象(变量):结束后要自动清栈3、全局堆区->动态内存管理new来使用的区域静态存储的优点不足不足:栈区容量有限对象的生存期和作用域不够灵活例对象数组:例 MyClass objs[50];数组的大小必须是编译期常量数组中的各对象是相同类型,相同大小的通常需要无参构造函数创建各分量对象增大类间的编译器依赖和耦合度优点:不用担心对象的释放动态内存存储(new、delete)将数据存储在全局堆区动态分配单个对象
new T(参数列表 );T可以是内置类型、自定义类型(class)、指针类型等
T * p=new T (x);
过程:首先判断是否有足够的内存空间,如果有执行T的构造函数创建一个匿名对象,使指针p指向这个对象动态释放单个对象
delete p;
delete[] p;//如果new的是数组类型的对象
过程:首先判断p指针是否为空,若p==nullptr,直接退出,否则执行T的析构函数,然后释放堆中内存new和delete与构造析构函数间的关系使用new运算符为类对象分配内存空间时,如果类中定义了构造函数,new运算符会自动调用类的构造函数进行初始化。而在释放内存空间时,如果类中定义了析构函数,delete运算符会自动调用类的析构函数进行清理操作。说明了在new的时候,先是分配了内存,然后调用基类的构造函数,最后才调用子类的构造函数。有了内存才能做后面构造函数的初始化,而基类对象的初始化也必须发生在子类对象初始化的前面,也就是说子类初始化的时候已经有了一个完备的基类对象数据。delete的操作正好相反,先是要析构子类对象,释放资源,然后析构基类对象,最后将对象内存释放
使用new和delete关键字对类对象的时候自动调用其构造和析构函数
动态内存管理的问题1、void*类型指针void* p=new void ;
delete p;
首先判断p是否为空,然后不执行析构函数只释放空间2、悬浮指针(无效指针、野指针)T *p=new T;
delete p;
p->f();//p指向的对象已经被释放,error
3、内存泄漏new完的对象没有delete就结束程序会造成内存泄漏黑盒复用–水平关系
改变类A的具体实现,不会影响类B、C的实现–B,C看不到A内部的情况
关联强关联:A.h必须包含B.h才能编译成功,则A与B之间存在强关联。硬关联:A和B类间有双向关系,且B与A之间有强关联,那么A与B之间存在硬关联。弱关联:A.cpp中必须包含B.h才能编译通过,则A与B之间存在弱关联。软关联:A只使用了B的指针或引用,那么A与B之间存在软关联。自关联:类中存在本身类类型的对象
表示两个类之间有关联,类之间有影响。
B的生存期间一定知道有A类的对象在代码中两类是成员变量的关系聚合
整体和局部的关系,单独存在也是可以的,是一种很弱的耦合关系
B包含A,但B不负责A的生存和消亡。class A;
class B{
public:
B(A *pa):mpA(pa){}
private:
A *mpA;
};
组合
整体与局部的关系,更是强调具有相同的生命周期,具有很强的耦合关系
class A{
public:
A(int n);
};
class B{
public:
B(int n){
mpA=new A(n);//A的创建
}
~B(){
delete mpA;//A的消亡
}
private:
A *mpA;
};
B包含A,B负责A的生存和消亡。依赖
表达了使用关系,也就是一个类需要另外一个类作为参数,是一种临时性的关联。依赖关系尽量是单向的,不要双向的。
代码体现:一般指有局部变量(函数实现的时候使用到了A类的属性)、函数参数、返回值建立起来的不同对象之间的关系(B中的成员函数用到了A类的属性)class A;
class B{
public:
void f1(A * pa);//1、参数是A类型
A* f2();//2、返回值是A类型
void f3(){
A *pa=new A;
delete pa;
}//3、函数内部实现使用了A类型
private:
A *mpA;
};
继承(白盒复用)–垂直关系继承继承A产生的新类B,称B为派生类(或子类),被继承的类A称基类(或父类)。
class 新类的名字:继承方式 继承类的名字{};
继承的总结:1.基类private成员无论以什么方式继承到派生类中都是不可见的。派生类对象不管在类里面还是类外面都不能去访问基类private成员。2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。基类的析构、构造、拷贝、赋值不会被派生类自动继承,转换函数会被派生类继承多重继承class A:public B,protected C
{
};
构造函数的顺序:按照写的顺序先构造基类,先B后C后A析构的顺序与构造的顺序相反名字冲突问题class A{
public:
int a;
void f();
};
class B{
public:
int a;
void f();
};
在C类对象中,当我们想要去调用基类的数据成员时,会产生名字冲突问题class C:public A,public B{
void k(){
cout<<a<<endl;//error
f();//error
}
}
有三种方法解决1、作用域限定符class C:public A,public B{
void k(){
cout<<A::a<<endl;
cout<<B::f()<<endl;
}
}
2、using关键字class C:public A,public B{
public:
using A::a;
using B::f();
void k(){
cout<<a<<endl;
f();
}
}
3、虚基类虚基类
解决多重继承下的名字冲突问题
使用虚基类的目的是在多重派生中使用共有基类在派生类中只有一个拷贝从而解决有多个基类拷贝所产生的二义性问题。(多重继承中,使基类在派生类中只保留一个副本)
虚基类代码
class 派生类名:virtual 继承方式 基类名
class A
{…};
class B:virtual public A
{…};
class C:virtual public A
{…};
class D:public B,public C{};
虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式声明的缺点:要求B类及C类的作者,预知未来会被多重继承类型的向上转换困难友元
友元 friend 机制允许一个类授权其他的函数访问它的非公有成员。
友元关系是单向的
友元关系没有传递性,继承不能继承友元
1、外部函数友元在类A中声明普通成员函数f()为A的友元,则f()中可访问A的private或者protected类属性2、成员函数友元friend void B::f(A& a, B& b);
在类A中声明类B的成员函数f()为A的友元友元函数不是成员函数,他是声明另一个类的成员函数是友元函数嵌套类可以成为其外围类的友元3、类友元class B
{
public:
B(int b=0):m_b(b){}
friend class A;//类友元
private:
int m_b;
};
个类A作为另外一个类B的友元类,则A的所有的成员函数就可以访问B的私有数据成员或者保护友元的缺点破坏了数据隐藏和数据封装,导致程序的可维护性变差里氏替换原则–向上类型转换
需要基类的任何地方都可以使用派生类的对象来代替,程序的行为没有变化
子类可以扩展父类的功能,但不能改变父类原有的功能也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法(除非基类中的纯虚函数必须要重写,虚函数可以重写,普通函数不能或者尽量不要重写)class human {
public:
string name = "小明";
void print()
{
cout << name << endl;
}
};
class student :public human {
public:
string name = "小红";
void print(int x)
{
cout<<" "<<x<<endl;
cout << name << endl;
}
};
在上面的代码中,在主函数中创建student对象要去调用基类的无参函数print()时,程序会先在派生类中寻找同名函数,如果找到了就一定会去调用,否则才会向基类中寻找,例如本程序如果想调用基类对象的print()方法就会报错。除非使用using human::print();
语句protected和private继承下的向上类型转换无实际意义,不能通过指针和引用进行向上类型转换public继承下的向上类型转换有实际意义在逻辑上,父类是子类的泛化或一般化语言上,父类的public行为集被窄化子类对象可以转为父类指针、引用,也可转为父类对象,但是是经过裁剪的,产生了一个新对象与原来不同。都是安全的转换多态
接口的多种不同的实现方式即为多态
在C++中,多态是一种面向对象编程的特性,它允许通过基类的指针或引用调用派生类的方法,实现不同的行为,即同一个函数名可以有多种不同的实现方式。这种特性是基于C++中的虚函数机制实现的。
静态关联和动态关联**静态关联:**编译期间就决定了程序运行时将具体调用哪个函数体。即使没有主程序,也能知道程序中各个函数体之间的调用关系。**动态关联:**在运行期间,决定具体调用哪个函数体。动态编联:虚机制(使用虚拟函数和虚拟函数表)实现虚函数
virtual [类型] 函数名([参数表列])
在类外定义虚函数时, 不需再加 virtual.
成员函数被声明为虚函数后, 其派生类中覆盖函数自动称为虚函数
若虚函数在派生类中未重新定义,则派生类简单继承其直接基类的虚函数
指向基类的指针, 当指向派生类对象时, 可以使用派生类的方法 虚函数只能是类的成员函数, 而不能将类外的普通函数声明为虚函数.虚函数的作用是允许在派生类中对基类的虚函数重新定义 (函数覆盖), 只能用于类的继承层次结构中.虚函数能有效减少空间开销. 当一个类带有虚函数时, 编译系统会为该类构造一个虚函数表 (一个指针数组), 用于存放每个虚函数的入口地址.sizeof(A)可以检测出类A的空间大小,如果有虚函数,则可以检测到虚函数表的存在虚函数的访问虚函数中访问的非虚函数:静态编联,使用本地版本非虚函数中访问的虚函数:动态编联虚函数中访问的虚函数:动态编联构造函数和虚函数:构造函数不能为虚函数调用的虚函数采用静态编联,使用本地版本析构函数和虚函数:析构函数可以为虚函数若类中含有虚函数,那么析构函数也应为虚函数.调用的虚函数采用静态编联,使用本地版本class Base
{
public:
Base()
{
vf();
}
virtual ~Base() { vf(); }
virtual void vf()
{
cout <<“Base::vf()”<< endl;
}
virtual void vg()
{
cout <<“Base::vg()”<< endl;
vf();
nvh();
}
void nvh()
{
cout <<“Base::nvh()”<< endl;
vf();
}
};
class Derived : public Base
{
public:
Derived() { vf(); }
virtual ~Derived() { vf(); }
virtual void vf()
{
cout <<“Derived::vf()”<< endl;
}
void nvh()
{
cout <<“Derived::nvh” << endl;
vf();
}
virtual void My() {}
};
Base *p = new Derived;
p->vf();//1
p->vg();//2
p->nvh();//3
delete p;
1、p指针的静态类型为base,查看base中的vf()为虚函数,采用动态编联,调用Derived中的vf()2、p指针的静态类型为base,查看base中vg()为虚函数,采用动态编联,调用Derived中的vg(),Derived中没有vg函数,返回base中调用base的vg(),再调用vf(),采用动态编联,调用Derived的vf(),在调用nvh(),为非虚函数,采用静态编联,调用本地的nvh(),在调用vf(),为动态编联,调用Derived的vf()。3、p指针的静态类型为base,查看base中nvh()为非虚函数,静态编联,调用base的nvh()虚函数的工作机理每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类中,编译器秘密地置入一指针,称为vptr,指向这个对象的vtable。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化*;而**VPTR则是每个类对象都有独立一份**的,且在该类对象被构造时被初始化。*通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个vptr,并在vtable表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。什么时候应该使用虚函数:判断成员函数所在的类是不是基类, 非基类无需使用虚函数成员函数在类被继承后有没有可能被更改的功能, 如果希望修改成员函数功能, 一般在基类中将其声明为虚函数我们会通过对象名还是基类指针访问成员函数, 如果通过基类指针或引用去访问, 则应当声明为虚函数有时候在定义虚函数的时候, 我们无需定义其函数体. 它的作用只是定义了一个虚函数名, 具体的功能留给派生类去添加, 也就是纯虚函数.错题:虚函数可以访问虚函数和非虚函数,非虚函数的非静态函数可访问虚函数析构函数需要声明为虚函数
当类中有其他虚函数,那么析构函数也应为虚函数
当基类指针指向子类对象的时候,需要把基类的析构函数设置成虚析构,防止内存泄露不应该把所有类的析构函数都声明为虚函数,如果一些类不可能为父类,那么声明析构函数为虚函数只会增大无谓的开销
如果基类析构函数不定义为虚析构,当我们用基类指针指向子类对象使用new的时候,在delete时只会调用基类析构函数,并未调用子类的析构函数a *A = new a();
a *A1 = new b();
delete A;
delete A1;
输出结果a的析构函数
a的析构函数
并未调用子类b的析构函数但如果将a的析构函数设置为虚函数,将输出下列的结果a的析构函数
b的析构函数
a的析构函数
抽象类和纯虚函数
抽象类, 是一些不用来定义对象, 而只作为基类被继承的类. 由于抽象类常用作基类, 所以通常称为抽象基类
virtual 返回值类型 函数名(参数列表)=0
如果在抽象类所派生出的新类中对基类的所有纯虚数进行了定义. 那么这些函数就被赋予了具体的功能, 可以被调用. 这个派生类就不是抽象类, 而是可以用来定义对象的具体类。如果在派生类中没有对所有纯虚函数进行定义, 则此派生类仍然是抽象类, 仍然不能用来定义对象.总结 一个基类如果包含一个或一个以上纯虚函数, 就是抽象基类
在类的层次结构中, 顶层或最上面的几层可以是抽象基类. 抽象基类体现了类族各类的共性, 把各类中共有的成员函数集中在抽象基类中声明
抽象类是类族的公共接口
子类必须得重写重写抽象类中的纯虚函数,不然不能实例化对象。
静态成员函数、构造函数、拷贝构造函数不能设置为虚函数---->可以有,并且应该是public属性的虚函数是需要通过对象中的虚表指针来找到虚函数表,然后再调用相应虚函数的。如果构造函数是虚函数,说明构造函数也是需要用虚表指针来调用的,但是这样一来就必须要先有虚表指针才行,而虚表指针又是存在于对象中,而对象本身又是需要先通过构造函数建立的
抽象类不能实例化对象,但是抽象类的指针可以被使用 终结类
不能被继承的类
1、使用final关键字声明2、将类的构造函数声明为私有成员,就不可以被继承类型转换类之间的类型转换单参构造函数的隐式类型转换与explicit—内置类型和自定义类型的转换单参构造函数:构造函数中只有一个参数(初始化列表无所谓,主要是构造函数的参数只能有一个)A(int a) : _a(1)//只有一个参数
{
cout << "A()" << endl;
}
单参构造函数的隐式类型转换:只有单参数的构造函数可以编译通过int main(){
A a(10);//正常构造
A b=20;//发生了隐式类型转换
}
explicit关键字:在定义构造函数时,在函数名之前加上explicit关键字就可以避免发生这种情况explicit A(int a) : _a(1)//只有一个参数
{
cout << "A()" << endl;
}
类类型转换为内置类型
需要再类中定义转换函数
(explicit) operator type-name()[const];
explicit表名该转换函数的参数不能进行隐式转换
class A{
public:
operator int()
{
return a;
};
operator float()
{
return b;
};
private:
int a;
float b;
};
类类型之间的转换无派生关系的转换class myClassA
{
};
class myClassB
{
public:
myClassB(const myClassA &A){};
operator myClassA()
{
myClassA a;
return a;
}
};
int main()
{
myClassA A;
myClassB B = A;
// myClassA类到myClassB类的转换
myClassA C = myClassA(B); // myClassB类到myClassA类的转换
return 0;
}
派生类与基类之间的转换c++的强制类型转换来自C的强制类型转换
(类型)变量;//c风格
类型(变量);//c++函数风格
int a=10;
float b;
b=float(a);//c++风格
b=(float)a;//c风格
强制类型转换符castdynamic_cast动态运行转换
用于类继承层次间的指针或引用转换
dynamic_cast<T>(exp)--------->T只能为指针、引用、void*
A *q = new A;//A *q = new C---->指向成功
C *p = dynamic_cast<C *>(q);
if (p) //
此时p = = NULL;
cout << " 指向成功" << endl;
else
cout << "指向失败" << endl;
如果转换成功,则p指针不为空,但如果是引用的转换,若转换不成功,则会报错可以将指向C对象的A类型指针转为C类型,不能将指向其他对象的A类型指针转为C类型(指针会变为空指针)1.类的上行转换,子类的指针或引用转化为基类(安全)2.类的下行转换,基类的指针或引用转化为子类3.必须要有虚拟表才能使用(必须有虚函数)static_cast静态类型转换static_cast<T>(exp)--------其中 T 表示指针、引用、内置类型、枚举类型,但不能是对象
作用:基本类型之间的转换void指针转换为任意基本类型的指针用于有继承关系的子类与父类之间的指针或引用的转换reinterpret_castreinterpret_cast<type_name>(expression)
对表达式的类型作出重新解释,常用于解释函数int MyFunc( void * pt ){}
typedef void (* FuncType) (char *);
FuncType
f = reinterpret_cast<FuncType >(MyFunc);//对函数进行重新解释
char * buf = new char[256];
f(buf);
delete[ ] buf;
最暴力,最底层,最不安全它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针const_cast
去掉const或volatile
const_cast<type_name>(expression)
A a;
const A& va=a;
A& var=const_cast<A &>(va);//可以将va的const限定取消
I/O流流C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。C++标准IO流C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内存流向控制台(显示器)cin>>i; //------>cout.operator(i)----运算符重载+函数重载
1、ostream cout(stdout); 标准输出 默认屏幕cout.precision(2);
setprecision(3);//设置cout的小数点精度
2、istream cin(stdin); 标准输入 默认键盘cin>>:在遇到结束符(Space、Tab、Enter)就结束,且对于结束符,并不保存到变量中。注意:最后一个enter也在缓冲区。cin.get(字符数组名,接收长度,结束符):cin.getline(字符数组名,接收长度,结束符)cin.eof():如果读文件到达文件末尾,返回true3、ostream cerr(stderr); 标准出错 默认屏幕4、ostream **clog(**stdprn); 标准打印 默认打印机C++文件IO流标准库 #include < fstream >,它定义了三个数据类型:ofstream:该数据类型表示输出文件流,用于创建文件并向文件写入信息。ifstream:该数据类型表示输入文件流,用于从文件读取信息。fstream:该数据类型表示输入和输出文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。fstream stream
stream.open("demo.txt");
// 默认方式打开文件
stream.open("demo.txt", ios::out
ios::trunc);//当文件已经存在时,你希望对该文件进行截断操作
stream.open("demo.txt", ios::out
ios::app);//当文件已经存在时,你希望对该文件的末尾进行操作
stream.open("demo.txt", ios::in
ios::ate); //只是想对文件进行读取操作,而且想在文件尾部读取
//
is_open()//判断文件是否打开成功
stream.open("demo.txt");
// 判断文件是否打开成功
if (!stream.is_open()) {
cout << "文件打开失败!" << endl;
system("pause");
exit(-1);
}
//关闭文件
stream.close();
模式标志描述ios::in读方式打开文件ios::out写方式打开文件ios::trunc如果此文件已经存在, 就会打开文件之前把文件长度截断为0(如果文件存在,则先删除这个文件)ios::app尾部最加方式(在尾部写入)ios::ate文件打开后, 定位到文件尾ios::binary二进制方式(默认是文本方式)写文件<<fstream outfile;
outfile.open("text.txt", ios::out
ios::trunc);
cin>>age;
outfile << age << endl;// 将从键盘读取的数据写入文件中
读文件>> ifstream inFile;
string name;
int age;
inFile >> name;
inFile >> age;
getline()stream inFile;
string line;
inFile("text.txt");
// 从文件中读取一行数据,并将读取到的数据写入字符串变量line中
getline(inFile, line);
eof文件流对象的eof()函数来检查文件流是否已到达文件末尾。eof()函数是C++标准库中的一个成员函数,用于测试输入流是否已到达文件末尾。如果已经到达文件末尾,则返回非零值,否则返回零。相当于c语言文件操作的feof()函数异常处理C++的异常处理包括两个部分:抛出异常和捕获异常,如果抛出的异常被捕获,处理完之后程序会继续运行,如果抛出的异常未被捕获,将导致程序终止。throw
throw 异常(对象);
在函数声明中应该指定可能抛出的异常,应该在函数声明指定。void show();
//该函数可能抛出任何异常
void show()throw();
//该函数不抛出任何异常
void show()throw(char,int);
//该函数可能抛出char和int型异常
try…catchtry{
//可能抛出异常的代码
}catch(异常类型1 变量){//根据类型捕获异常
//处理异常类型1的代码
}catch(异常类型2 变量){//根据类型捕获异常
//处理异常类型2的代码
//throw 变量 ----- 继续向上一层抛出
}
①当异常被抛出后,首先检查throw本身是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。②如果当前函数栈没有匹配的catch则退出当前函数栈,继续在上一个调用函数栈中进行查找匹配的catch。找到匹配的catch子句并处理以后,会沿着catch子句后面继续执行,而不会跳回到原来抛异常的地方。③如果到达main函数的栈,依旧没有找到匹配的catch,则终止程序。#include <iostream>
using namespace std;
void func1()
{
throw string("这是一个异常");
}
void func2()
{
func1();
}
void func3()
{
func2();
}
int main()
{
try
{
func3();
}
catch (const string &s)
{
cout << "错误描述:" << s << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
当func1中的异常被抛出后:1.首先会检查throw本身是否在try块内部,这里由于throw不在try块内部,因此会退出func3所在的函数栈,继续在上一个调用函数栈中进行查找,即func2所在的函数栈。2.由于func2中也没有匹配的catch,因此会继续在上一个调用函数栈中进行查找,即func3所在的函数栈。3.func3中也没有匹配的catch,于是就会在main所在的函数栈中进行查找,最终在main函数栈中找到了匹配的catch。4.这时就会跳到main函数中对应的catch块中执行对应的代码块,执行完后继续执行该代码块后续的代码。系统预定义异常std::exception 该异常是所有标准C++异常的父类。std::bad_alloc 该异常可以通过new抛出。std::bad_cast 该异常可以通过dynamic_cast抛出。std::bad_exception 这在处理C++程序中无法预期的异常时非常有用。std::bad_typeid 该异常可以通过typeid抛出。std::logic_error 理论上可以通过读取代码来检测到的异常。std::domain_error 当使用了一个无效的数学域时,会抛出该异常。std::invalid_argument 当使用了无效的参数时,会抛出该异常。std::length_error 当创建了太长的std::string时,会抛出该异常。std::out_of_range 该异常可以通过方法抛出,例如std::vector和std::bitset<>::operator。std::runtime_error 理论上不可以通过读取代码来检测到的异常。std::overflow_error 当发生数学上溢时,会抛出该异常。std::range_error 当尝试存储超出范围的值时,会抛出该异常。std::underflow_error 当发生数学下溢时,会抛出该异常。用户自定义异常实验1、组合模式组合模式又称部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。优点: 1、高层模块调用简单。 2、节点自由增加。**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。**注意事项:**定义时为具体类。2、数据库XML常见库Algorithmsort()sort(numbers.begin(), numbers.end()); // 默认升序排序
reverse()reverse(numbers.begin(), numbers.end());
find()auto it = std::find(numbers.begin(), numbers.end(), 3);
binary_search() bool found = std::binary_search(numbers.begin(), numbers.end(), 3);
accumulate()int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
equal()bool st=equal(v1.begin(),v1.end(),v2.begin());
remove
template< class ForwardIt, class T >ForwardIt remove( ForwardIt first, ForwardIt last, const T& value );
remove函数会将[first, last)范围内所有等于value的值移动到尾部,返回一个指向新的最后一个元素之后的位置的迭代器remove还有跟文件有关的重载函数std::removeiomanipsetw()cctypetolowerio.haccess()direct.hmkdir()

我要回帖

更多关于 main本地函数定义是非法的 的文章

 

随机推荐