前言
本文主要写自己对C++基础的理解。
一、C++在C基础上的语法更新
1.程序设计的发展
(1)面向过程:程序自上而下,严格的结构化设计;
(2)面向对象:封装、继承、多态,把对象的属性和操作封装起来,形成一个类;
(3)C语言与C++的关系:
① C语言是在实践过程中逐步完善起来的,C语言的目标是高效;
② C语言 + 面向对象 = Objective-C/C++。
2.namespace的概念
(1)在C语言中,只有一个全局作用域;
(2)在C++中,std是C++标准命名空间,C++标准程序可中的所有标识符都被定义在std中,eg:类iostream、vector;
(3)当使用的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std,则std::cout;
(4)C++标准为了与C语言区分开,以及正确的使用命名空间,规定头文件不适用后缀.h;
(5)C++命名空间的定义:
namespace name
{
...
}
(6)namespace定义可以嵌套。
namespace NameSpaceA
{
int a = 0;
}
namespace NameSpaceB
{
int a = 1;
namespace NameSpaceC
{
struct Teacher
{
char name[20];
int age;
};
}
}
int main(void)
{
using namespace NameSpaceA;
using NameSpaceB::NameSpaceC::Teacher;
std::cout << "a = " << a << std::endl;
std::cout << "a = " << NameSpaceB::a << std::endl;
Teacher t = { "Tom", 3 };
std::cout << "t.name = " << t.name << std::endl;
std::cout << "t.age = " << t.age << std::endl;
system("pause");
return 0;
}
3.Register
(1)register是一个关键字,它请求编译器将变量直接放到寄存器中,速度快;
(2)在C语言中,register修饰的变量不能取地址,但是在C++中可以取得register的地址;
(3)C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效;
(4)早期的C语言编译器不会对代码进行优化,因此register变量是一个很好地补充。
4.语法检测的增强
(1)在C语言中,重复定义多个同名的全局变量是合法的;
(2)在C++中,不允许定义多个同名的全局变量;
(3)在C语言中,多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上;
int var;
int var = 1;
//C++中是不允许这种二义性的语法
int main()
{
printf("var = %d\n", var);
return 0;
}
5.struct类型加强
(1)在C语言中,struct定义了一组变量的集合,C编译器并不认为这是一种新的类型;
(2)在C++中,struct是一个新类型的定义声明;
struct Child
{
char name[20];
int age;
};
int main()
{
Child c1 = {"chensan", 10};
Child c2 = {"wanger", 7};
Child c3 = {"lisi", 8};
return 0;
}
6.在C++中所有的变量和函数都必须有类型
func1(i)
{
printf("i = %d\n", i);
}
func2()
{
return 1;
}
int main()
{
func1(10);
printf("func2() = %d\n", func2(1,2,3,4));
return 0;
}
在C语言中:
int func();//表示返回值为int,接受任意参数的函数
int func(void);//表示返回值为int的无参数函数
在C++中:
int func()和int func(void)具有相同的意义,都表示返回值为int的无参函数
即:C++强调是类型,任意的程序元素都必须显示指明类型。
7.bool类型
(1)C++在C语言的基础类型上增加了bool;
(2)C++中的bool取值为true(内部表示1)和false(内部表示0);
(3)一般情况下,bool只占用一个字节;
(4) 如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现;
(5)bool类型只有true(非0)和false(0)两个值;
(6)C++编译器会在赋值时将非0值转换为true,0值转换为false。
8.三目运算符
int main()
{
int a = 1;
int b = 2;
//返回一个最小数,并给最小数赋值为3
//三目运算符是一个表达式,表达式不可能做左值
(a < b ? a : b) = 30;
printf("a = %d, b = %d\n", a, b);
system("pause");
return 0;
}
(1)C语言是返回变量的值,C++是返回变量本身;
(2)C语言中的三目运算符返回的是变量值,不能为左值使用;
C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方;
(3)三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用;( a < b ? 1 : b) = 30;
(4)C语言是如何支持类似C++的特性?
变量的本质是内存空间的别名,是一个标号。
9.const
const int a;
int const b;
const int *c;
int * const d;
const int * const e ;
//第一个第二个意思一样 代表一个常整形数
//第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
//第四个 d是常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
//第五个 e是一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
结论:
(1)C语言中的const变量是只读变量,有自己的存储空间;
(2)C++中的const常量可能分配存储空间,也可能不分配存储空间;
当const常量为全局变量、并且需要在其他文件中使用,当使用&操作符取const常量的地址,符号表、编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)。
int main()
{
const int a = 1;
int *p = (int *)&a;
printf("a = %d\n", a);
*p = 2;
printf("a = %d\n", a);
system("pause");
return 0;
}
(3)C++中的const常量类似于宏定义
const int c = 5; // ⇒ #define c 5
(4)C++中的const常量在与宏定义不同:
① const常量是由编译器处理的,提供类型检查和作用域检查;
② 宏定义由预处理器处理,单纯的文本替换。
10.引用
(1)引用是C++的概念,属于C++对C的扩展;
int main()
{
int a = 0;
int &b = a; //int *const b = &a;
b = 11; //*b = 11;
return 0;
}
(2)普通引用是否有自己的空间:
struct Teacher
{
int &a;
int &b;
};
int main()
{
std::cout << "sizeof(Teacher) : " << sizeof(Teacher) << std::endl;
system("pause");
return 0;
}
(3)当实参传给形参引用的时候,只不过是C++编译器手动的帮程序员取了一个实参的地址,传给了形参引用(常指针)
void func(int &a)
{
a = 5;
}
====>
void func(int *const a)
{
*a = 5;
}
11.函数增强
(1)内敛函数
① 内敛函数在编译时直接将函数体插入函数调用的地方;
② inline只是一种请求,编译器不一定允许这种请求;
③ 内敛函数省去了普通函数调用时压栈,跳转和返回的开销。
(2)默认参数与占位参数
① 只有参数列表后面部分的参数才可以提供默认参数值;
② 若在一个函数调用中开始使用默认参数值,则这个函数后的所有参数都必须使用默认参数值;
③ 占位参数只有参数类型声明,而没有参数名声明;
④ 一般情况下,在函数体内部无法使用占位参数。
int func(int a, int b, int = 0)
{
return a + b;
}
(3)函数重载
定义:用同一个函数名定义不同的函数,当函数名与不同的参数搭配时,函数的含义不同。
函数重载应满足下面中的一个条件:
① 参数个数不同;
② 参数类型不同;
③ 参数顺序不同。
int func1(int a, int b, int c = 0)
{
return a * b * c;
}
int func1(int a, int b)
{
return a + b;
}
int main()
{
int c = 0;
c = func1(1, 2);//存在二义性,调用失败,编译不能通过
printf("c = %d\n", c);
return 0;
}
当函数重载与默认参数重合,则会发生编译错误。
(4)编译器调用重载函数的准则
函数重载编译器编译步骤:
① 将所有同名函数作为候补选择;
② 尝试寻找可行的候选函数;
③ 匹配实参;
④ 通过默认参数能够匹配实参;
⑤ 通过默认类型转换匹配实参;
⑥ 匹配失败;
⑦ 最终寻找到的可行候选函数不唯一、则出现二义性,编译失败;
⑧ 无法匹配所有候选者,函数未定义、编译失败。
⑨ 函数重载的注意事项:
a. 重载函数在本质上是相互独立的不同函数(静态联编);
b.重载函数的函数类型是不同的;
c.函数返回值不能作为函数重载的依据;
d.函数重载是由函数名和参数列表决定的。
(5)重载和函数指针结合
当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者的函数与函数指针的函数类型。
int func(int x)
{
return x;
}
int func(int a, int b)
{
return a + b;
}
int func(const char *s)
{
return strlen(s);
}
typedef int (*PFUNC)(int a);//int(int a)
int main()
{
int c = 0;
PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
return 0;
}
函数重载与函数指针:
当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者的类型与函数指针的函数类型。
二、构造函数和析构函数
1.基本概念
(1)构造函数总结:
① 构造函数是C++中用于初始化对象状态的特殊函数;
② 构造函数在对象创建时自动被调用(默认调用),隐身调用;
③ 构造函数和普通成员函数都遵循重载规则;
④ 拷贝构造函数是对象正确初始化的重要保证;
⑤ 必要的时候必须手工编写拷贝构造函数。
(2)构造函数的调用
① 自动调用:一般情况下C++编译器会自动调用构造函数;
② 手动调用:在一些情况下则需要手动调用构造函数。
(3)构造函数有三种:有参构造函数、默认构造函数、拷贝构造函数。
(4)有参构造函数调用的三种方法:
class Test
{
public:
//有参构造函数
Test(int a)
{
m_a = a;
}
//无参构造函数
Test()
{
m_a = 0;
}
//四种应用场景
//赋值构造函数 copy构造函数
Test(const Test &obj)
{
}
public:
void print()
{
cout << "m_a: " << m_a << endl;
}
private:
int m_a;
};
int main()
{
Test t1(10);//C++编译器自动调用这个类的有参构造函数
t1.print();
Test t2 = 20; //C++编译器自动调用这个类的有参构造函数
t2.print();
Test t3 = Test(30);//程序员手动的调用构造函数 进行对象初始化
t3.print();
system("pause");
return 0;
}
赋值构造函数(copy构造函数)和 = 操作是两个不同的概念。
(5)赋值构造函数(copy构造函数)的四个应用场景
① 第一种场景
void main()
{
Test t1;
t1.setA(10);
Test t3;
//1. 赋值构造函数和= 操作是两个不同的概念
//2. 赋值构造函数 copy构造函数也是构造函数
//在这个场景之下,t2被创建,并且自动的调用copy构造
//3.当我们没有编写copy构造函数(赋值构造函数)的时候,C++编译器默认给我们提供一个copy构造函数 执行的是浅copy
Test t2 = t1;//对象t2的初始化
cout << t2.getA() << endl
}
② 第二种场景
class Location
{
public:
Location(int x = 0, int y = 0)
{
X = x;
Y = y;
}
Location(const Location &p) //赋值构造函数
{
X = p.X;
Y = p.Y;
cout << "Copy constructor called.\n";
}
~Location()
{
cout << X << "," << Y << "Object destroyed.\n";
}
int getX()
{
return X;
}
int getY()
{
return Y;
}
private:
int X, Y;
};
void func2(Location p)
{
cout << "Function:" << p.getX() << ", " << p.getY() << endl;
}
void playObjMain()
{
Location A(1, 2);
func2(A);
}
Location func3()
{
Location A(1, 2);
return A;
}
void main()
{
Location B;
B = func3();
system("pause");
}
③ 第三种场景
void main()
{
Location B = func3();
system("pause");
}
④ 第四种场景
void main()
{
playObjMain();
system("pause");
}
(6)构造函数和析构函数的调用规则
规则总结:
① 当类中没有定义任何一个构造函数时,C++编译器会提供无参构造函数和拷贝构造函数;
② 当类中定义了任意的非拷贝构造函数(无参、有参),C++编译器不会提供无参构造函数;
③ 当类中定义了拷贝构造函数时,C++便一起去不会提供无参构造函数;
④ 默认拷贝构造函数成员变量简单赋值。
即:只要你写了构造函数那么你必须要用。
class ABCD
{
public:
ABCD(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c);
}
~ABCD()
{
printf("~ABCD() construct,a:%d,b:%d,c:%d \n", a, b, c);
}
int getA()
{
return a;
}
protected:
int a;
int b;
int c;
};
class MyE
{
public:
MyE() :abcd1(1, 2, 3), abcd2(4, 5, 6), m(100)
{
cout << "MyE()" << endl;
}
~MyE()
{
cout << "~MyE()" << endl;
}
MyE(const MyE & obj) :abcd1(7, 8, 9), abcd2(10, 11, 12), m(100)
{
printf("MyE(const MyE & obj)\n");
}
protected:
//private:
public:
ABCD abcd1; //c++编译器不知道如何构造abc1
ABCD abcd2;
const int m;
};
void doThing1(MyE mye)
{
cout << "dothing1 myE.abcd1.a" << mye.abcd1.getA() << endl;
}
void doThing2()
{
cout << "doThing2 start...\n";
MyE myE;
doThing1(myE);
cout << "doThing2 end...\n";
}
void doThing3()
{
cout << "doThing3 start...\n";
ABCD abcd = ABCD(10, 20, 30);
cout << "doThing3 end...\n";
}
int main()
{
doThing2();
doThing3();
system("pause");
return 0;
}
2.深拷贝与浅拷贝
(1)浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。eg: 张三的大名叫张三,小名叫傻蛋,又两个不同的称呼,但是本质上的人只是一个。
Code:
class String
{
public:
String(const char *str = "") : m_str(new char[strlen(str) + 1])
{
strncpy(m_str, str, strlen(str) + 1);
}
String(const String &str)
{
m_str = str.m_str;
}
String &operator=(const String &str)
{
if (this != &str)
{
m_str = str.m_str;
}
return *this;
}
~String()
{
cout << "~String()\n";
if (m_str)
{
delete[] m_str;
m_str = NULL;
}
}
void show()
{
cout << m_str << endl;
}
private:
char *m_str;
};
void main()
{
String str1 = "Hello World!";
String str2(str1);
str2.show();
system("pause");
}
以上程序的问题出在str1和str2指向了同一块内存,当程序执行完毕后,析构函数执行了两遍,即同一块内存重复释放,造成程序崩溃。
(2)深拷贝
拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。
String(const String &str)
{
m_str = new char[strlen(str.m_str) + 1];
strncpy(m_str, strlen(str.m_str) + 1, str.m_str);
}
String &operator=(const String &str)
{
if(this != &str)
{
delete [] m_str;
m_str = new char [strlen(str.m_str) + 1];
strncpy(m_str, str.m_str, strlen(str.m_str)+1);
}
return *this;
}
3.static
(1)C语言中的static
① 静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数。
int func()
{
static int i = 0;
i += 1;
return i;
}
void main()
{
printf("i=%d\n", func());
printf("i=%d\n", func());
printf("i=%d\n", func());
printf("i=%d\n", func());
printf("i=%d\n", func());
printf("i=%d\n", func());
system("pause");
}
② 静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见。
static int num = 1;
int func1()
{
num += 1;
}
③ 静态函数:静态函数与全局静态成员变量类似,作用域于本文件中,extern将对其无法生效。
(2)C++中的static关键字
① 静态数据成员:这种数据成员的生存期大于class的对象(实体instance)。静态数据成员是每个class一份,普通数据成员是每个instance一份。
class Circle
{
public:
Circle(float r)
{
m_r = r;
m_s += 3.14 * m_r * m_r;
}
double getMS()
{
return m_s;
}
private:
float m_r;
static float m_s;
};
float Circle::m_s = 0.0;
int main()
{
cout << "sizeof(Circle) = " << sizeof(Circle) << endl;
Circle *cle = new Circle(1);
cout << "m_s = "<< cle->getMS() << endl;
Circle *cle1 = new Circle(2);
cout << "m_s = " << cle1->getMS() << endl;
Circle *cle2 = new Circle(3);
cout << "m_s = " << cle2->getMS() << endl;
system("pause");
return 0;
}
静态成员变量并不占Circle内存空间,它的内存被分配到了全局数据区(静态数据区),且static只会被初始化一次,与实例化无关。
② 静态成员函数:与静态成员变量类似。
class Circle
{
public:
Circle(float r)
{
m_r = r;
m_s += 3.14 * m_r * m_r;
}
static double getMS()
{
return m_s;
}
private:
float m_r;
static float m_s;
};
float Circle::m_s = 0.0;
int main()
{
cout << "sizeof(Circle) = " << sizeof(Circle) << endl;
Circle *cle = new Circle(1);
cout << "m_s = " << cle->getMS() << endl;
Circle *cle1 = new Circle(2);
cout << "m_s = " << cle1->getMS() << endl;
Circle *cle2 = new Circle(3);
cout << "m_s = " << cle2->getMS() << endl;
system("pause");
return 0;
}
总结
由于时间原因C++基础就先写到这里。