C/C++语言知识
文章目录
变量和函数的存储类别
自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于其定义所在块(通常指函数)私有。寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器储存它们。不能获取寄存器变量的地址。
具有静态存储期的变量可以具有外部链接、内部链接或无链接。在一个文件中,函数外部声明的变量是外部变量,具有文件作用域、外部链接和静态存储期。如果在这种声明前面加上关键字static,那么其声明的变量具有文件作用域、内部链接和静态存储期。如果在函数中用 static 声明一个变量,则该变量具有块作用域、无链接、静态存储期。
具有自动存储期的变量,程序在进入该变量的声明所在块时才为其分配内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中是垃圾值。程序在编译时为具有静态存储期的变量分配内存,并在程序的运行过程中一直保留这块内存。如果未初始化,这样的变量会被设置为0,包括数组和结构体成员。
具有块作用域的变量是局部的,属于包含该声明的块私有。具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。具有内部链接的文件作用域变量,只能用于其声明所在的文件内。
示例
将下面两个文件(main1.cpp 和 main2.cpp)一起编译即可。
// main1.cpp
#include <stdio.h>
#pragma warning(disable:4996)
//外部函数原型如下,默认采用 extern 关键字,可以被其他文件访问
void report_count();
void accumulate(int k); //函数定义在其他地方
static void fun();// static 表示该函数为此文件私有,不可被外部访问。
int count; // 文件作用域,外部链接,全局变量默认初始化为 0
int main(void)
{
int value = 2; // 自动变量 初始化为随机值
register int i; // 寄存器变量(仅仅是建议编译器采用寄存器来存储) 初始化为随机值 不能对其取地址
for (i = value; i >= 0; i--)
accumulate(i);
report_count();
return 0;
}
void report_count()
{
printf("%d\n", count);
}
// main2.cpp
#include <stdio.h>
extern int count; // 引用式声明,外部链接
static int total ; // 静态定义,内部链接
void accumulate(int k); // 函数原型
void accumulate(int k)// k 具有块作用域,无链接
{
static int subtotal ; // 静态,无链接
if (k <= 0)
subtotal = 0;
else{
subtotal += k;
total += k;
}
count++;
printf("%d %d\n", subtotal, total);
}
函数参数和返回值
个人认为:函数参数均采用传值的方式,意味着传递的是副本(引用可等同于指针);函数的返回值,采用的也是传值的方式,一般情况下,会将函数的返回值赋值给某一个变量。
采用传指针/引用,和返回指针/引用 在传递/返回一个结构体这样的大对象时,避免了传递时生成该结构体副本/返回时生成该结构体副本。效率更高!
注意:不能返回一个局部变量的指针/引用,因为函数执行完毕之后,该内存不再存在。所以一般采用指针/引用都是应用在返回指向 new 生成的内存,
#ifndef 和 #pragma once
条件编译1 ,条件编译2 和 extern ‘C’
一、const 关键字
// cv-限定符
// 指的是 const 和 volatile
// 而 mutable 修饰符和 const 有关
struct MyStruct
{
// mutable 修饰符 表示即使该结构体的变量被声明为 const,a 的值依然可以被修改。
mutable int a;
double b;
};
int main() {
const MyStruct mys = { 4,5.6 };
mys.a++; // success
mys.b += 0.1;// error
return 0;
}
a、const修饰常量,表示其值不可改变,且必须在定义的时候必须初始化
b、const修饰函数参数,表示该参数的值在函数体内不能被修改
c、const修饰函数返回值
a、修饰普通返回值,如 const int f();由于该返回值是一个临时变量,随着函数调用结束后,其声明周期也就结束了,所以没有意义。
b、修饰指针,如
const int* f(); //函数 表示该返回值所指向的值不能被修改
int* res=f(); // 错误
const int* res=f(); //正确
c、修饰引用,如
const int& f1(int& a) { return a; }
int& f(int& a) { return a; }
int main() {
int a = 3;
f1() = 89;//错误 函数调用表达式不能作为左值。
f(a) = 90;//正确
cout << a << endl; // 90
return 0;
}
d、const 和 指针
int a = 3, b = 4;
int* p = &a;
p = &b;
*p = 20;
const int* p1 = &a; //修饰指针所指向的值,此指针可以修改,但是此指针指向的值不能被修改
p1 = &b;
//*p1 = 20;
int const* p2 = &a; // 同上 (可以认为此二者的const均修饰 *)
p2 = &a;
//*p2 = 20;
int* const p3 = &a; //仅修饰指针,指针不能修改,但是指针指向的值可以被修改
//p3 = &b;
*p3 = 20;
const int* const p4 = &a; // 均修饰,指针不能修改,且其指向的值也不能被修改
//p4 = &b;
//*p4 = 20;
e、C const 和 C++ const
const int len = 9;
int array[len]; // cpp 允许,而 c 不允许
二、printf
printf("%d\n");//打印出内存中的随机值
int a=90;
int b = printf("%d\n", a);
printf("%d\n", b); // b为3 它为printf打印出字符的个数! 负值表示出错。
三、数组、字符串
a、字符数组:存储 char 的数组;字符串:一系列 char 和 结尾的 ‘\0’ 构成
b、一维数组
char a[20];
printf("%d\n", a[0]); // 正确,打印出随机值
int b;
printf("%d\n", b); // 运行时错误,b 未初始化
//和普通变量一样,应该在声明时来初始化 const 数据,因为一旦声明为const,便不能再给它赋值。
const int m[4]={1,2,3,4};
int n[4]={1}; // n 中值为: 1 0 0 0
// 由于数组一旦有部分元素被初始化,其余元素就会被初始化为 0,所以:对一个数组初始化全为 0 的方式如下
int nn[4]={0};
int mm[]={3,4};// 数组大小为2;
int len=sizeof(mm)/sizeof(int);// 或者:sizeof(mm)/sizeof(mm[0]);
#define N 5 // c
constexpr auto N1 = 5; // cpp 常量表达式
int main() {
int i[N1]; // 初始化数组大小
int ii[3 * 7]; // 初始化数组大小
const int j = 8;
int k[j];// const 只能在cpp中 初始化数组大小
return 0;
}
c、二维数组
int sq[2][3] = { 5,6,7,8 }; //不带括号的从第一行顺序初始化
/* 5 6 7
* 8 0 0
*/
int s[2][3] = { {5,6},{7,8} };
/* 5 6 0
* 7 8 0
*/
四、数组和指针
a、一维数组
int a[7] = { 5,6,7,8 };
int* p = &a[0];
int* p1 = a;
printf("%d\n", *p);
printf("%d\n", *(p+1)); //指针加1,指针的值递增它所指向类型的大小。
printf("%d\n", p[1]);
p + 2 == &p[2]; // 相同的地址
*(p + 2) == p[2]; // 相同的值
*(p + 2); // p 第3个元素的值
*p + 2; // p 第1个元素的值加2 * 的运算符优先级比算术运算符要高。
a.a、 常见运算符优先级
b、二维数组
int zippo[3][2] = { 0,1,2,3,4,5 };
printf("%d\n", zippo[2][1]);
printf("%d\n", *(*(zippo + 2) + 1));
// *(zippo+2) zippo[2][0] 的地址
// *(zippo+2)+1 zippo[2][1] 的地址
//*(*(zippo+2)+1) zippo[2][1] 的值
int(* p)[2] = zippo; // 指向二维数组的指针 采用的括号的原因是因为 [] 比 * 的优先级高。
printf("%d\n", **p); // 等同于 zippo[0][0]
printf("%d\n", *(*(p + 2)+1)); // 等同于 zippo[2][1]
//int* p[2]; //此时 p 是一个存放 int* 的一维数组,数组长度为 2.
b.b、数组作为函数参数
// int array[8][4]; //将该数组作为函数参数
int fun1(int(*p)[4]);
int fun2(int p[][4]);
int fun1(int p[][]);// 错误的传参
int fun3(int* p);
int fun4(int p[]);
//一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
//第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。
//下面的 ar 表示其指向一个12×20×30的int数组。
int fun5(int ar[][12][20][30]);
int fun6(int(*ar)[12][20][30]);
c、野指针
//一、未初始化的指针
int* pp;
printf("%p\n", pp); //报错 pp未初始化
printf("%d\n", *pp); // 报错 pp未初始化
*pp=9; //报错 pp未初始化
/*
pp未被初始化,其值是一个随机值,所以不知道 9 将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。
要么设置它的值为NULL,要么让它指向已有变量/数组的地址,要么让它指向 malloc/new 分配的内存。
*/
//二、指针被释放之后,仅仅释放掉其指向的内存,指针本身并未被释放掉。
int* uu = new int;
delete uu;
uu = NULL; //建议 delete/free 之后,令指针值为 NULL
int* fun() {
int a = 9;
return &a;
}
int main() {
//三、指针操作超出了变量的作用范围,
int* res = fun(); // 此时 a 所在的内存已经被释放掉了。
return 0;
}
d、通用指针 void*
// 通用指针 void* : 可指向任何指针,但解引用时需要先进行转换。
int a = 3;
int* p;
void* v = &a;
printf("%d\n", *v);// 报错,不知道 v 指向内容的长度。
p = (int*)v;
printf("%d\n", *p);
五、简单的类型转换
Java 变量
double dd = 9.87;
int iii = dd;//错误
int i=(int)dd; //正确
C 变量
// 隐式类型转换
double dd = 9.87;
int iii = dd;
C 指针
// 指针只能显式类型转换
double* a = new double(8.98);
int* b = a; //报错,无法转换
int* b = (int*)a;
delete a;
a=NULL
1、c 语言: 字符串和其他类型的转换
#include<iostream>
#pragma warning(disable:4996)
using namespace std;
int main() {
// c 语言: 字符串和其他类型的转换
char* a = new char[30];
double b = 8.2385;
sprintf(a, "%.2lf", b); // printf中一般可以用%f代替%lf
//a 此时为一个字符串。
cout << a << endl;
double c;
sscanf(a, "%lf", &c); // scanf中%lf与%f是严格区分的
cout << c << endl;
delete[] a;
return 0;
}
2、cpp 初始化
//数组
//列表初始化:
int a[]{ 2,3,4 }; // 2 3 4
int b[6]{ 2,3,4 };// 2 3 4 0 0 0
int d[3]{};// 0 0 0
int c[3] = {};// 等同于 c 中的 int c[3]={0};
//结构体
struct MyStruct { int a; double b; };
MyStruct mys{ 4,5.6 };
MyStruct mm = { 4,5.7 };
//字符串初始化
//char* a = "jjjj";//错误
const char* a = "jjjj";//正确 a 是一个字符串常量
char b[] = "uuuuu"; // b 是一个字符串
b[0] = 'o';
cout<<sizeof(b)<<endl; // 6
// new 初始化
int* a = new int(9); //单值变量
// 数组/结构体的列表初始化
struct MyStruct { int a; double b; };
MyStruct* mys = new MyStruct{1, 3.4};//结构体
int* b = new int[5] {};
int* c = new int[] {2, 3};
int* d = new int[4]{ 1 };
3、函数
a、默认的函数参数
int fun(int node=4){
return node;
}
int main() {
int res = fun();
cout << res << endl; // 4
}
//参数有多个的时候,只能从右往左设置参数的默认值
b、函数模板
#include<iostream>
#pragma warning(disable:4996)
using namespace std;
//函数模板:
template<typename T>
void swap1(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 8, b = 9;
double aa = 8.1, bb = 9.1;
swap1(a, b); // 9 8
swap1(aa, bb);// 9.1 8.1
// std::swap 函数
swap(a, b); // 8 9
swap(aa, bb);// 8.1 9.1
return 0;
}
4、结构体,C字符串、string赋值
struct a {
char c[20];
int i;
double d;
};
int main() {
a aa = { "kjisf",9,2.34 };
a bbb = aa; // 结构体可以直接赋值
cout << bbb.c << " " << bbb.i << " " << bbb.d << endl;
string s = "lkjsf";
string ss = s;// string 可以直接赋值(重写赋值操作符)
// C字符串不可直接赋值,需要采用 strcpy 函数 或者 for循环依次赋值
char tmp[6];
strcpy(tmp, "12345");
char* b = new char[6];
strcpy(b, "12345");
delete[] b;
return 0;
}
5、类
类中的private: 可以省略,这是类默认的访问策略。
像一些简单的函数定义(方法体),如Java中的getter、setter,可以直接写在类声明文件中,位于类声明中的函数定义自动成为内联函数。
或者在类定义文件中,与其他函数的定义放在一起,此时需要在函数定义之前显式添加 inline 关键字,来表明其是一个内联函数。
a、初始化
类初始化如下:
class C {
int a, b;
double c;
public:
C(int a, int b, double c) {
this->a = a;
this->b = b;
this->c = c;
}
};
int main() {
C c = C(2, 3, 4.5);// 显式调用构造函数
C cc(1, 2, 8.9);// 隐式调用构造函数
C c1 = C{ 2, 3, 4.5 }; // 列表初始化
C cc1{1, 2, 8.9};
C* ccc = new C{ 1,2,3.4 };
C* cccc = new C(3, 4, 5.6);
return 0;
}
和Java一样,不提供自己的构造函数,程序只能使用默认的构造函数;如果提供了自己的构造函数,而且还想使用默认构造函数的话,就需要显式声明了。
class C {
int a, b;
double c;
public:
C(int a, int b, double c) {
this->a = a;
this->b = b;
this->c = c;
}
C() { // 实现默认的构造函数,提供隐式初始值
a = b = 0;
c = 0.0;
}
~C(){} // 默认的析构函数,如果构造函数采用了new,那么在析构函数中需要采用delete。
};
int main() {
C c = C(); //显式调用 默认构造函数
// 如果默认构造函数未作初始化,则 c 中的类成员为随机值。
C cc; //隐式调用
C* c1 = new C();
C* cc1 = new C;
return 0;
}
b、赋值
int main() {
C c = C(); //初始化 (可能会创建临时对象)
c=C(); //赋值,一定会创建临时对象,创建一个临时对象,然后赋值给变量 c,最后调用析构函数
return 0;
}
c、const 成员
a、const 成员函数
public:
void show() const; // 保证调用show方法时,不会修改调用者的成员变量。
void C::show() const{
cout<<"haha"<<endl;
}
b、const 成员变量
class C {
private:
const int a;
public:
C(int aa) :a(aa) {} 只能采用列表初始化来进行
};
5、string类
int main() {
string s = "jlsjl";
string s1("kskfj");
string ss = s;// 赋值操作
string sss = ss + s;// 拼接
string ssss;
getline(cin, ssss);// 从标准输入接收一行输入
return 0;
}