C++基础
C++相对C新增:
1. 动态初始化
a. 想使用的时候才定义变量
b. for循环中定义变量:for(int i;😉
2. 数据类型
-
bool :
a. 相应新增true、false保留字
b. 输出0和1
-
class
-
&(引用)
3. 无类型指针(void *)与显示类型转化
- void指针可以指向任意类型的数据,即可以用任意类型的指针对void指针赋值。例如:
int *pint ;
void *pvoid ; //它没有类型,或者说这个类型不能判断出指向对象的长度
pvoid = pint ; //只获得变量/对象地址而不获得大小,即pvoid的指针类型还是没有转变
//但是不允许 pint=pvoid;
printf("%d",*pint); //正确
printf("%d",*pvoid); //错误,pvoid不是指向某类型的指针
printf("%d",*(int *)pvoid); //正确,同printf("%d",*pint);
-
在C语言中,可以把任何类型的指针(包括函数指针)赋值给void*指针而无须进行强制类型转换。
在C++中,对这个转换做了更严格的限制:只有非 常量指针才能赋值给void*指针。
C++示例代码:const char pp[10] = {0}; const char* str = "aaa"; //pp与str均为指向常量区的指针 void* pv = NULL; pv = pp;//错误,不能将常量指针赋值给void *指针 pv = str;//错误,不能将常量指针赋值给void *指针
-
如果要将pvoid赋给其他类型指针,则需要强制类型转换如:
pint = (int *)pvoid; //转换类型也就是获得指向变量/对象大小
-
void指针在强制转换成具体类型前,不知道指向对象的(字节)大小,不能解引用(即取内容的意思);
【Eg】 void *pvoid=pint;
*pvoid //错误;
-
void指针不能参与指针运算,除非进行转换。
只知道地址,不知道指针类型(大小),
即:参与运算的指针必须是确定知道其指向数据类型大小的。
-
void指针的应用
当进行纯粹的内存操作的时候,或者传递一个指向未定类型的指针时,可以使用void指针;
典型的如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest,constvoid * src,size_tlen);
void * memset(void * buffer,intc,size_tnum);
这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。
注意memcpy和memset函数返回的也是void*类型。
【注】:malloc函数的返回类型是void,**
int p =(int)malloc(100sizeof(int));
进行了强制性转化。
-
C++中 int(x)与(int)x意义相同,
而C中只有(int)x;
4. ::限定符和变量作用域
- 作用域小的变量覆盖作用域大的变量
- 用“::变量名”可以访问具有文件作用域的变量,但不能访问隐藏的局部变量
【Eg】:变量作用域:
#include <iostream>
using namespace std;
int x=1; //全局作用域
void func() {
int x=2; //局部变量,隐藏了全局变量
cout<<"局部变量"<<x<<endl;
{
int x=3; //复合语句中定义的局部变量
//它隐藏了局部和全局变量
cout<<"复合语句中定义的局部变量"<<x<<endl;
cout<<"可以通过::限定符访问全局变量"<<::x<<endl;
cout<<"但不能访问被隐藏的局部变量"<<endl;
}
cout<<"复合语句结束后又可以访问局部变量"<<x<<endl;
cout<<"的人还是可以通过::限定符访问全局变量"<<::x<<endl;
}
void main()
{
cout<<"全局变量"<<x<<endl;
cout<<"通过::限定符访问全局变量"<<::x<<endl;
func();
}
/*******************************************
程序输出结果如下:
全局变量1
通过::限定符访问全局变量1
局部变量2
复合语句中定义的局部变量3
可以通过::限定符访问全局变量1
但不能访问被隐藏的局部变量
复合语句结束后又可以访问局部变量2
的人还是可以通过::限定符访问全局变量1
********************************************/
5. 引用类型&
引用本质上是一个指针常量int *const p;(存放的地址不能变,即:不能修改引用的值)
const int &a=10; //允许
int &b=10; //不允许
在C++中值传递可以实现单向传递,但是引用传递可以实现双向传递。
引用类型
1. 引用(&)是标识符的别名
例如:int i, j ; int & ri = i; //定义int引用ri,并初始化为变量i的引用。
定义一个引用时,必须同时对他进行初始化,使它指向一个已存在的对象。
【PS】:初始化时指向的这个对象只能是变量,不能是常量。
j = 10
ri = j; //相当于是i=j
- 一旦一个引用被初始化后,就不能改为指向其他对象
- 可以取引用的地址
2. 用途:引用可以作为形参代替指针使用
-
若条件允许,就应将引用形参声明为const形参,这样可以避免无意间修改数据而导致编程错误。
-
若形参中无const修饰,则引用调用的实际参数只能是变量!(存在合理的地址空间)
形参有用const 修饰,则实参也可以是常量(N,‘A’等)。
即:使用const形参使得函数能够接受const和非const实参,否则只能处理非const数据。
void func1(int n) { //可以修改n,但值不会返回调用者} //可用值或变量调用
void func2(int &n){ //可以修改n,值会返回调用者} //只能用同类型的变量调用该函数
void func3(const int &n) { //根本就不允许修改n} //可用值或变量调用
- 返回引用类型:
- 返回引用必须保证返回的引用变量有合法的内存空间,并且不在函数的运行栈中。一般只能返回全局变量和静态变量或者函数传递的形式参数。
-
不能建立引用数组:
-
引用是需要初始化的,大数组初始化麻烦
-
破坏数组存放的连续性
-
int a[3];
int &b[3]=a; //错误,不能建立引用数组
- 不能建立引用的引用,不能建立引用的指针
6. 从键盘读入字符串
char s[80];
cin>>s; //1.遇到空白停止 2.输入可能超过80个字符
cin.getline(s,80); //最多输入79个字符
cout<<s;
7. const
7.1只读函数
int getMember() const;
表示不能改变对象的成员变量的值,也不能调用一个非const的成员函数(避免间接修改成员数据);
7.2 const与指针
- 指针不能指向常量(const 定义的常量)
#include<iostream>
using namespace std;
int main()
{
const int N=10;
int a[N]; //允许。在C++中,N是常量,而C中N是不能改变的变量
}
- const int *cp==int const *cp;
同为指向常量的指针(const int )**p,且cp可以指向其他普通变量,但是无法通过*cp来修改变量的值。
- int * const cp;
指针常量(这个指针是常量),不能指向其他地址,但是可以修改指向的内容。
-
在C中,下述语句是错误的:(N是不能改变的变量)
const int N=10; //必须改为:#define N 10
int a[N];但在C++中,上述语句是允许的。(N是常量)
8. 枚举、结构、联合类型
enum weekday {sun,mou,tue,wed,thu,fri,sat};
weekday a,b,c; //weekday也是一种类型 仅C++支持)
//C语言中需要enum weekday a,b,c;
结构与联合同上,可省去struct和union;
8.1枚举类型
void test()
{undefined
enum Week { Mon, Tue, Wed, Thi, Fri, Sat, Sun };
enum Other { One, Two, Three };
enum Week week = Mon;
C中:
-
允许非枚举值赋值给枚举类型, 允许其他枚举类型的值赋值给当前枚举类型
week = 100; //ok
week = One; //ok
-
枚举值具有外层作用域,容易造成名字冲突 int One = 100; //error
-
不同类型的枚举值可以直接比较
-
可以改变枚举元素的值
C++中:
-
C++ 只能允许赋值枚举值
// week = 100; //error
// week = One; //error
-
枚举元素会暴露在外部作用域,不同的两个枚举类型,若含有相同枚举元素,则会冲突
enum OtherWeek { Mon };
-
枚举元素是常量,不能改变
8.2结构
C语言结构体初始化,可以如下四种:
#include <stdio.h>
int main(int argc, const char * argv[]) {
//定义结构体类型
struct Person
{
char *name;
int age;
double heigth;
};
//初始化的4种方式
//1.定义的同时初始化
struct Person p1 = {"zhangsan",20,170};
//2.先定义再逐个初始化
struct Person p2;
p2.name = "ykd";
p2.age = 18;
p2.heigth = 180;
//3.先定义再一次性初始化
struct Person p3;
p3 = (struct Person){"lisi",18,180};
//注意:结构体和数组在这里的区别,数组不能先定义再进行一次性初始化
//结构体要明确的告诉系统{}中是一个结构体
//4.指定将数据赋值给指定的属性
struct Person p4 = {.heigth=1.77, .name="wangwu", .age=33};
C++结构体初始化会更加丰富,有内置函数来执行。
当然也可兼容C语言上述四种。除此还有以下两种:
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) //链表初始化
{
val = x;
next = NULL;
}
};
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) 😕/链表初始化
val(x), next(NULL) {}
};
以上都可以采用ListNode a(1),b(2),c(3);直接初始化。
9. 函数
函数声明:
如果你声明一个函数:int fun();
函数参数为空的时候,在c中的意思是函数参数不确定,而在c++中的意思是函数参数为空。
如果函数这样声明:int fun(void);,这时不论是在c中还是在c++中的意思都是函数参数为空。
默认参数
- 在函数声明处体现,定义中不能出现
- 默认参数只能放形参列表的最后
函数重载
- 同名函数才有重载
- 根据函数的参数类型,数量,顺序进行重载,返回值不行
//第十一题 (函数重载) 源代码1
#include <iostream>
using namespace std;
int func(int x)
{
cout<<"int x"<<endl;
}
int func(double x)
{
cout<<"double x"<<endl;
}
int func(float x)
{
cout<<"float x"<<endl;
}
int func(int x,double y)
{
cout<<"int x,double y"<<endl;
}
int func(double x,int y)
{
cout<<"double x,int y"<<endl;
}
//double func(double x); //声明报错,返回值不能作为唯一区分进行函数重载
int main() {
func(1); //输出int x
func(1.0); //输出double x
func(1.0f); //输出float x
func(1,2.0); //输出int x,double y
func(1.0,2); //输出double x,int y
func(10,10); //报错, call of overloaded 'func(int, int)' is ambiguous
}
3.关于函数重载时的自动类型转化
//第十一题 (函数重载) 源代码2
#include <iostream>
using namespace std;
int func1(double x) //关于函数重载时的自动类型转化
{
cout<<"double x"<<endl;
}
int func2(double x)
{
cout<<"double x"<<endl;
}
int func2(float x)
{
cout<<"float x"<<endl;
}
int main() {
func1(1); //输出double x,进行了自动类型转化
func1(1.0f); //输出double x,进行了自动类型转化
func2(1.0f); //输出float x;
func2(1); //同时存在double和float类型参数的函数,编译器不知道如何转化,报错
}
内联函数
//第九题 (内联函数) 源代码1
#include <iostream>
using namespace std;
inline int add(int x,int y)//内联函数的声明和定义,inline加在定义处才有用
{
return x+y;
}
inline void Foo(int x,int y);
int main()
{
}
void Foo (int x,int y)//仅在声明处出现inline,不构成内联函数
{
}
10.模板
-
函数模板参数表中指明的类型参数必须用于函数参数表,例如:
template T f1() {……} //错误
template void f2()
{ //错误 T a;
……
}
-
模板函数的声明和定义必须放在同一个cpp下,否则连接器无法通过模板函数的声明找到相对应的定义 (模板未被实例化的时候是没有生成二进制代码的)
11.抛出异常:
- 抛出异常类时,调用构造函数 (要加括号) //或者直接抛出该类的对象
#include <iostream>
using namespace std;
void func(void);
class Expt { //异常类
public:
Expt() {cout<<"执行异常类的构造函数...\n";}
Expt(const Expt &e) {
*this=e;
cout<<"执行异常类的拷贝构造函数...\n";
}
~Expt() {cout<<"执行异常类的析构函数...\n";}
const char *showReason() const {
return "Expt类异常!\n";
}
};
class Demo {
public:
Demo() {cout<<"构造Demo...\n";}
~Demo() {cout<<"析构Demo...\n";}
};
void func(void) {
Demo dObj;
cout<<"在函数func()中抛出Expt类异常!\n";
throw Expt(); //不能写成:throw Expt;
}