C. Didattica e programmazione [Quarta ed.] 9788871922195

Il testo di Al Kelley e Ira Pohl si conferma come una guida completa e aggiornata, per l'apprendimento di un lingua

448 121 142MB

Italian Pages 672 [669] Year 2004

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Scan-201105-0002_1L_risultato
Scan-201105-0002_2R_risultato
Scan-201105-0003_1L_risultato
Scan-201105-0003_2R_risultato
Scan-201105-0004_1L_risultato
Scan-201105-0004_2R_risultato
Scan-201105-0005_1L_risultato
Scan-201105-0005_2R_risultato
Scan-201105-0006_1L_risultato
Scan-201105-0006_2R_risultato
Scan-201105-0007_1L_risultato
Scan-201105-0007_2R_risultato
Scan-201105-0008_1L_risultato
Scan-201105-0008_2R_risultato
Scan-201105-0009_1L_risultato
Scan-201105-0009_2R_risultato
Scan-201105-0010_1L_risultato
Scan-201105-0010_2R_risultato
Scan-201105-0011_1L_risultato
Scan-201105-0011_2R_risultato
Scan-201105-0012_1L_risultato
Scan-201105-0012_2R_risultato
Scan-201105-0013_1L_risultato
Scan-201105-0013_2R_risultato
Scan-201105-0014_1L_risultato
Scan-201105-0014_2R_risultato
Scan-201105-0015_1L_risultato
Scan-201105-0015_2R_risultato
Scan-201105-0016_1L_risultato
Scan-201105-0016_2R_risultato
Scan-201105-0017_1L_risultato
Scan-201105-0017_2R_risultato
Scan-201105-0018_1L_risultato
Scan-201105-0018_2R_risultato
Scan-201105-0019_1L_risultato
Scan-201105-0019_2R_risultato
Scan-201105-0020_1L_risultato
Scan-201105-0020_2R_risultato
Scan-201105-0021_1L_risultato
Scan-201105-0021_2R_risultato
Scan-201105-0022_1L_risultato
Scan-201105-0022_2R_risultato
Scan-201105-0023_1L_risultato
Scan-201105-0023_2R_risultato
Scan-201105-0024_1L_risultato
Scan-201105-0024_2R_risultato
Scan-201105-0025_1L_risultato
Scan-201105-0025_2R_risultato
Scan-201105-0026_1L_risultato
Scan-201105-0026_2R_risultato
Scan-201105-0027_1L_risultato
Scan-201105-0027_2R_risultato
Scan-201105-0028_1L_risultato
Scan-201105-0028_2R_risultato
Scan-201105-0029_1L_risultato
Scan-201105-0029_2R_risultato
Scan-201105-0030_1L_risultato
Scan-201105-0030_2R_risultato
Scan-201105-0031_1L_risultato
Scan-201105-0031_2R_risultato
Scan-201105-0032_1L_risultato
Scan-201105-0032_2R_risultato
Scan-201105-0033_1L_risultato
Scan-201105-0033_2R_risultato
Scan-201105-0034_1L_risultato
Scan-201105-0034_2R_risultato
Scan-201105-0035_1L_risultato
Scan-201105-0035_2R_risultato
Scan-201105-0036_1L_risultato
Scan-201105-0036_2R_risultato
Scan-201105-0037_1L_risultato
Scan-201105-0037_2R_risultato
Scan-201105-0038_1L_risultato
Scan-201105-0038_2R_risultato
Scan-201105-0039_1L_risultato
Scan-201105-0039_2R_risultato
Scan-201105-0040_1L_risultato
Scan-201105-0040_2R_risultato
Scan-201105-0041_1L_risultato
Scan-201105-0041_2R_risultato
Scan-201105-0042_1L_risultato
Scan-201105-0042_2R_risultato
Scan-201105-0043_1L_risultato
Scan-201105-0043_2R_risultato
Scan-201105-0044_1L_risultato
Scan-201105-0044_2R_risultato
Scan-201105-0045_1L_risultato
Scan-201105-0045_2R_risultato
Scan-201105-0046_1L_risultato
Scan-201105-0046_2R_risultato
Scan-201105-0047_1L_risultato
Scan-201105-0047_2R_risultato
Scan-201105-0048_1L_risultato
Scan-201105-0048_2R_risultato
Scan-201105-0049_1L_risultato
Scan-201105-0049_2R_risultato
Scan-201105-0050_1L_risultato
Scan-201105-0050_2R_risultato
Scan-201105-0051_1L_risultato
Scan-201105-0051_2R_risultato
Scan-201105-0052_1L_risultato
Scan-201105-0052_2R_risultato
Scan-201105-0053_1L_risultato
Scan-201105-0053_2R_risultato
Scan-201105-0054_1L_risultato
Scan-201105-0054_2R_risultato
Scan-201105-0055_1L_risultato
Scan-201105-0055_2R_risultato
Scan-201105-0056_1L_risultato
Scan-201105-0056_2R_risultato
Scan-201105-0057_1L_risultato
Scan-201105-0057_2R_risultato
Scan-201105-0058_1L_risultato
Scan-201105-0058_2R_risultato
Scan-201105-0059_1L_risultato
Scan-201105-0059_2R_risultato
Scan-201105-0060_1L_risultato
Scan-201105-0060_2R_risultato
Scan-201105-0061_1L_risultato
Scan-201105-0061_2R_risultato
Scan-201105-0062_1L_risultato
Scan-201105-0062_2R_risultato
Scan-201105-0063_1L_risultato
Scan-201105-0063_2R_risultato
Scan-201105-0064_1L_risultato
Scan-201105-0064_2R_risultato
Scan-201105-0065_1L_risultato
Scan-201105-0065_2R_risultato
Scan-201105-0066_1L_risultato
Scan-201105-0066_2R_risultato
Scan-201105-0067_1L_risultato
Scan-201105-0067_2R_risultato
Scan-201105-0068_1L_risultato
Scan-201105-0068_2R_risultato
Scan-201105-0069_1L_risultato
Scan-201105-0069_2R_risultato
Scan-201105-0070_1L_risultato
Scan-201105-0070_2R_risultato
Scan-201105-0071_1L_risultato
Scan-201105-0071_2R_risultato
Scan-201105-0072_1L_risultato
Scan-201105-0072_2R_risultato
Scan-201105-0073_1L_risultato
Scan-201105-0073_2R_risultato
Scan-201105-0074_1L_risultato
Scan-201105-0074_2R_risultato
Scan-201105-0075_1L_risultato
Scan-201105-0075_2R_risultato
Scan-201105-0076_1L_risultato
Scan-201105-0076_2R_risultato
Scan-201105-0077_1L_risultato
Scan-201105-0077_2R_risultato
Scan-201105-0078_1L_risultato
Scan-201105-0078_2R_risultato
Scan-201105-0079_1L_risultato
Scan-201105-0079_2R_risultato
Scan-201105-0080_1L_risultato
Scan-201105-0080_2R_risultato
Scan-201105-0081_1L_risultato
Scan-201105-0081_2R_risultato
Scan-201105-0082_1L_risultato
Scan-201105-0082_2R_risultato
Scan-201105-0083_1L_risultato
Scan-201105-0083_2R_risultato
Scan-201105-0084_1L_risultato
Scan-201105-0084_2R_risultato
Scan-201105-0085_1L_risultato
Scan-201105-0085_2R_risultato
Scan-201105-0086_1L_risultato
Scan-201105-0086_2R_risultato
Scan-201105-0087_1L_risultato
Scan-201105-0087_2R_risultato
Scan-201105-0088_1L_risultato
Scan-201105-0088_2R_risultato
Scan-201105-0089_1L_risultato
Scan-201105-0089_2R_risultato
Scan-201105-0090_1L_risultato
Scan-201105-0090_2R_risultato
Scan-201105-0091_1L_risultato
Scan-201105-0091_2R_risultato
Scan-201105-0092_1L_risultato
Scan-201105-0092_2R_risultato
Scan-201105-0093_1L_risultato
Scan-201105-0093_2R_risultato
Scan-201105-0094_1L_risultato
Scan-201105-0094_2R_risultato
Scan-201105-0095_1L_risultato
Scan-201105-0095_2R_risultato
Scan-201105-0096_1L_risultato
Scan-201105-0096_2R_risultato
Scan-201105-0097_1L_risultato
Scan-201105-0097_2R_risultato
Scan-201105-0098_1L_risultato
Scan-201105-0098_2R_risultato
Scan-201105-0099_1L_risultato
Scan-201105-0099_2R_risultato
Scan-201105-0100_1L_risultato
Scan-201105-0100_2R_risultato
Scan-201105-0101_1L_risultato
Scan-201105-0101_2R_risultato
Scan-201105-0102_1L_risultato
Scan-201105-0102_2R_risultato
Scan-201105-0103_1L_risultato
Scan-201105-0103_2R_risultato
Scan-201105-0104_1L_risultato
Scan-201105-0104_2R_risultato
Scan-201105-0105_1L_risultato
Scan-201105-0105_2R_risultato
Scan-201105-0106_1L_risultato
Scan-201105-0106_2R_risultato
Scan-201105-0107_1L_risultato
Scan-201105-0107_2R_risultato
Scan-201105-0108_1L_risultato
Scan-201105-0108_2R_risultato
Scan-201105-0109_1L_risultato
Scan-201105-0109_2R_risultato
Scan-201105-0110_1L_risultato
Scan-201105-0110_2R_risultato
Scan-201105-0111_1L_risultato
Scan-201105-0111_2R_risultato
Scan-201105-0112_1L_risultato
Scan-201105-0112_2R_risultato
Scan-201105-0113_1L_risultato
Scan-201105-0113_2R_risultato
Scan-201105-0114_1L_risultato
Scan-201105-0114_2R_risultato
Scan-201105-0115_1L_risultato
Scan-201105-0115_2R_risultato
Scan-201105-0116_1L_risultato
Scan-201105-0116_2R_risultato
Scan-201105-0117_1L_risultato
Scan-201105-0117_2R_risultato
Scan-201105-0118_1L_risultato
Scan-201105-0118_2R_risultato
Scan-201105-0119_1L_risultato
Scan-201105-0119_2R_risultato
Scan-201105-0120_1L_risultato
Scan-201105-0120_2R_risultato
Scan-201105-0121_1L_risultato
Scan-201105-0121_2R_risultato
Scan-201105-0122_1L_risultato
Scan-201105-0122_2R_risultato
Scan-201105-0123_1L_risultato
Scan-201105-0123_2R_risultato
Scan-201105-0124_1L_risultato
Scan-201105-0124_2R_risultato
Scan-201105-0125_1L_risultato
Scan-201105-0125_2R_risultato
Scan-201105-0126_1L_risultato
Scan-201105-0126_2R_risultato
Scan-201105-0127_1L_risultato
Scan-201105-0127_2R_risultato
Scan-201105-0128_1L_risultato
Scan-201105-0128_2R_risultato
Scan-201105-0129_1L_risultato
Scan-201105-0129_2R_risultato
Scan-201105-0130_1L_risultato
Scan-201105-0130_2R_risultato
Scan-201105-0131_1L_risultato
Scan-201105-0131_2R_risultato
Scan-201105-0132_1L_risultato
Scan-201105-0132_2R_risultato
Scan-201105-0133_1L_risultato
Scan-201105-0133_2R_risultato
Scan-201105-0134_1L_risultato
Scan-201105-0134_2R_risultato
Scan-201105-0135_1L_risultato
Scan-201105-0135_2R_risultato
Scan-201105-0136_1L_risultato
Scan-201105-0136_2R_risultato
Scan-201105-0137_1L_risultato
Scan-201105-0137_2R_risultato
Scan-201105-0138_1L_risultato
Scan-201105-0138_2R_risultato
Scan-201105-0139_1L_risultato
Scan-201105-0139_2R_risultato
Scan-201105-0140_1L_risultato
Scan-201105-0140_2R_risultato
Scan-201105-0141_1L_risultato
Scan-201105-0141_2R_risultato
Scan-201105-0142_1L_risultato
Scan-201105-0142_2R_risultato
Scan-201105-0143_1L_risultato
Scan-201105-0143_2R_risultato
Scan-201105-0144_1L_risultato
Scan-201105-0144_2R_risultato
Scan-201105-0145_1L_risultato
Scan-201105-0145_2R_risultato
Scan-201105-0146_1L_risultato
Scan-201105-0146_2R_risultato
Scan-201105-0147_1L_risultato
Scan-201105-0147_2R_risultato
Scan-201105-0148_1L_risultato
Scan-201105-0148_2R_risultato
Scan-201105-0149_1L_risultato
Scan-201105-0149_2R_risultato
Scan-201105-0150_1L_risultato
Scan-201105-0150_2R_risultato
Scan-201105-0151_1L_risultato
Scan-201105-0151_2R_risultato
Scan-201105-0152_1L_risultato
Scan-201105-0152_2R_risultato
Scan-201105-0153_1L_risultato
Scan-201105-0153_2R_risultato
Scan-201105-0154_1L_risultato
Scan-201105-0154_2R_risultato
Scan-201105-0155_1L_risultato
Scan-201105-0155_2R_risultato
Scan-201105-0156_1L_risultato
Scan-201105-0156_2R_risultato
Scan-201105-0157_1L_risultato
Scan-201105-0157_2R_risultato
Scan-201105-0158_1L_risultato
Scan-201105-0158_2R_risultato
Scan-201105-0159_1L_risultato
Scan-201105-0159_2R_risultato
Scan-201105-0160_1L_risultato
Scan-201105-0160_2R_risultato
Scan-201105-0161_1L_risultato
Scan-201105-0161_2R_risultato
Scan-201105-0162_1L_risultato
Scan-201105-0162_2R_risultato
Scan-201105-0163_1L_risultato
Scan-201105-0163_2R_risultato
Scan-201105-0164_1L_risultato
Scan-201105-0164_2R_risultato
Scan-201105-0165_1L_risultato
Scan-201105-0165_2R_risultato
Scan-201105-0166_1L_risultato
Scan-201105-0166_2R_risultato
Scan-201105-0167_1L_risultato
Scan-201105-0167_2R_risultato
Scan-201105-0168_1L_risultato
Scan-201105-0168_2R_risultato
Scan-201105-0169_1L_risultato
Scan-201105-0169_2R_risultato
Scan-201105-0170_1L_risultato
Scan-201105-0170_2R_risultato
Scan-201105-0171_1L_risultato
Scan-201105-0171_2R_risultato
Scan-201105-0172_1L_risultato
Scan-201105-0172_2R_risultato
Scan-201105-0173_1L_risultato
Scan-201105-0173_2R_risultato
Scan-201105-0174_1L_risultato
Scan-201105-0174_2R_risultato
Scan-201105-0175_1L_risultato
Scan-201105-0175_2R_risultato
Scan-201105-0176_1L_risultato
Scan-201105-0176_2R_risultato
Scan-201105-0177_1L_risultato
Scan-201105-0177_2R_risultato
Scan-201105-0178_1L_risultato
Scan-201105-0178_2R_risultato
Scan-201105-0179_1L_risultato
Scan-201105-0179_2R_risultato
Scan-201105-0180_1L_risultato
Scan-201105-0180_2R_risultato
Scan-201105-0181_1L_risultato
Scan-201105-0181_2R_risultato
Scan-201105-0182_1L_risultato
Scan-201105-0182_2R_risultato
Scan-201105-0183_1L_risultato
Scan-201105-0183_2R_risultato
Scan-201105-0184_1L_risultato
Scan-201105-0184_2R_risultato
Scan-201105-0185_1L_risultato
Scan-201105-0185_2R_risultato
Scan-201105-0186_1L_risultato
Scan-201105-0186_2R_risultato
Scan-201105-0187_1L_risultato
Scan-201105-0187_2R_risultato
Scan-201105-0188_1L_risultato
Scan-201105-0188_2R_risultato
Scan-201105-0189_1L_risultato
Scan-201105-0189_2R_risultato
Scan-201105-0190_1L_risultato
Scan-201105-0190_2R_risultato
Scan-201105-0191_1L_risultato
Scan-201105-0191_2R_risultato
Scan-201105-0192_1L_risultato
Scan-201105-0192_2R_risultato
Scan-201105-0193_1L_risultato
Scan-201105-0193_2R_risultato
Scan-201105-0194_1L_risultato
Scan-201105-0194_2R_risultato
Scan-201105-0195_1L_risultato
Scan-201105-0195_2R_risultato
Scan-201105-0196_1L_risultato
Scan-201105-0196_2R_risultato
Scan-201105-0197_1L_risultato
Scan-201105-0197_2R_risultato
Scan-201105-0198_1L_risultato
Scan-201105-0198_2R_risultato
Scan-201105-0199_1L_risultato
Scan-201105-0199_2R_risultato
Scan-201105-0200_1L_risultato
Scan-201105-0200_2R_risultato
Scan-201105-0201_1L_risultato
Scan-201105-0201_2R_risultato
Scan-201105-0202_1L_risultato
Scan-201105-0202_2R_risultato
Scan-201105-0203_1L_risultato
Scan-201105-0203_2R_risultato
Scan-201105-0204_1L_risultato
Scan-201105-0204_2R_risultato
Scan-201105-0205_1L_risultato
Scan-201105-0205_2R_risultato
Scan-201105-0206_1L_risultato
Scan-201105-0206_2R_risultato
Scan-201105-0207_1L_risultato
Scan-201105-0207_2R_risultato
Scan-201105-0208_1L_risultato
Scan-201105-0208_2R_risultato
Scan-201105-0209_1L_risultato
Scan-201105-0209_2R_risultato
Scan-201105-0210_1L_risultato
Scan-201105-0210_2R_risultato
Scan-201105-0211_1L_risultato
Scan-201105-0211_2R_risultato
Scan-201105-0212_1L_risultato
Scan-201105-0212_2R_risultato
Scan-201105-0213_1L_risultato
Scan-201105-0213_2R_risultato
Scan-201105-0214_1L_risultato
Scan-201105-0214_2R_risultato
Scan-201105-0215_1L_risultato
Scan-201105-0215_2R_risultato
Scan-201105-0216_1L_risultato
Scan-201105-0216_2R_risultato
Scan-201105-0217_1L_risultato
Scan-201105-0217_2R_risultato
Scan-201105-0218_1L_risultato
Scan-201105-0218_2R_risultato
Scan-201105-0219_1L_risultato
Scan-201105-0219_2R_risultato
Scan-201105-0220_1L_risultato
Scan-201105-0220_2R_risultato
Scan-201105-0221_1L_risultato
Scan-201105-0221_2R_risultato
Scan-201105-0222_1L_risultato
Scan-201105-0222_2R_risultato
Scan-201105-0223_1L_risultato
Scan-201105-0223_2R_risultato
Scan-201105-0224_1L_risultato
Scan-201105-0224_2R_risultato
Scan-201105-0225_1L_risultato
Scan-201105-0225_2R_risultato
Scan-201105-0226_1L_risultato
Scan-201105-0226_2R_risultato
Scan-201105-0227_1L_risultato
Scan-201105-0227_2R_risultato
Scan-201105-0228_1L_risultato
Scan-201105-0228_2R_risultato
Scan-201105-0229_1L_risultato
Scan-201105-0229_2R_risultato
Scan-201105-0230_1L_risultato
Scan-201105-0230_2R_risultato
Scan-201105-0231_1L_risultato
Scan-201105-0231_2R_risultato
Scan-201105-0232_1L_risultato
Scan-201105-0232_2R_risultato
Scan-201105-0233_1L_risultato
Scan-201105-0233_2R_risultato
Scan-201105-0234_1L_risultato
Scan-201105-0234_2R_risultato
Scan-201105-0235_1L_risultato
Scan-201105-0235_2R_risultato
Scan-201105-0236_1L_risultato
Scan-201105-0236_2R_risultato
Scan-201105-0237_1L_risultato
Scan-201105-0237_2R_risultato
Scan-201105-0238_1L_risultato
Scan-201105-0238_2R_risultato
Scan-201105-0239_1L_risultato
Scan-201105-0239_2R_risultato
Scan-201105-0240_1L_risultato
Scan-201105-0240_2R_risultato
Scan-201105-0241_1L_risultato
Scan-201105-0241_2R_risultato
Scan-201105-0242_1L_risultato
Scan-201105-0242_2R_risultato
Scan-201105-0243_1L_risultato
Scan-201105-0243_2R_risultato
Scan-201105-0244_1L_risultato
Scan-201105-0244_2R_risultato
Scan-201105-0245_1L_risultato
Scan-201105-0245_2R_risultato
Scan-201105-0246_1L_risultato
Scan-201105-0246_2R_risultato
Scan-201105-0247_1L_risultato
Scan-201105-0247_2R_risultato
Scan-201105-0248_1L_risultato
Scan-201105-0248_2R_risultato
Scan-201105-0249_1L_risultato
Scan-201105-0249_2R_risultato
Scan-201105-0250_1L_risultato
Scan-201105-0250_2R_risultato
Scan-201105-0251_1L_risultato
Scan-201105-0251_2R_risultato
Scan-201105-0252_1L_risultato
Scan-201105-0252_2R_risultato
Scan-201105-0253_1L_risultato
Scan-201105-0253_2R_risultato
Scan-201105-0254_1L_risultato
Scan-201105-0254_2R_risultato
Scan-201105-0255_1L_risultato
Scan-201105-0255_2R_risultato
Scan-201105-0256_1L_risultato
Scan-201105-0256_2R_risultato
Scan-201105-0257_1L_risultato
Scan-201105-0257_2R_risultato
Scan-201105-0258_1L_risultato
Scan-201105-0258_2R_risultato
Scan-201105-0259_1L_risultato
Scan-201105-0259_2R_risultato
Scan-201105-0260_1L_risultato
Scan-201105-0260_2R_risultato
Scan-201105-0261_1L_risultato
Scan-201105-0261_2R_risultato
Scan-201105-0262_1L_risultato
Scan-201105-0262_2R_risultato
Scan-201105-0263_1L_risultato
Scan-201105-0263_2R_risultato
Scan-201105-0264_1L_risultato
Scan-201105-0264_2R_risultato
Scan-201105-0265_1L_risultato
Scan-201105-0265_2R_risultato
Scan-201105-0266_1L_risultato
Scan-201105-0266_2R_risultato
Scan-201105-0267_1L_risultato
Scan-201105-0267_2R_risultato
Scan-201105-0268_1L_risultato
Scan-201105-0268_2R_risultato
Scan-201105-0269_1L_risultato
Scan-201105-0269_2R_risultato
Scan-201105-0270_1L_risultato
Scan-201105-0270_2R_risultato
Scan-201105-0271_1L_risultato
Scan-201105-0271_2R_risultato
Scan-201105-0272_1L_risultato
Scan-201105-0272_2R_risultato
Scan-201105-0273_1L_risultato
Scan-201105-0273_2R_risultato
Scan-201105-0274_1L_risultato
Scan-201105-0274_2R_risultato
Scan-201105-0275_1L_risultato
Scan-201105-0275_2R_risultato
Scan-201105-0276_1L_risultato
Scan-201105-0276_2R_risultato
Scan-201105-0277_1L_risultato
Scan-201105-0277_2R_risultato
Scan-201105-0278_1L_risultato
Scan-201105-0278_2R_risultato
Scan-201105-0279_1L_risultato
Scan-201105-0279_2R_risultato
Scan-201105-0280_1L_risultato
Scan-201105-0280_2R_risultato
Scan-201105-0281_1L_risultato
Scan-201105-0281_2R_risultato
Scan-201105-0282_1L_risultato
Scan-201105-0282_2R_risultato
Scan-201105-0283_1L_risultato
Scan-201105-0283_2R_risultato
Scan-201105-0284_1L_risultato
Scan-201105-0284_2R_risultato
Scan-201105-0285_1L_risultato
Scan-201105-0285_2R_risultato
Scan-201105-0286_1L_risultato
Scan-201105-0286_2R_risultato
Scan-201105-0287_1L_risultato
Scan-201105-0287_2R_risultato
Scan-201105-0288_1L_risultato
Scan-201105-0288_2R_risultato
Scan-201105-0289_1L_risultato
Scan-201105-0289_2R_risultato
Scan-201105-0290_1L_risultato
Scan-201105-0290_2R_risultato
Scan-201105-0291_1L_risultato
Scan-201105-0291_2R_risultato
Scan-201105-0292_1L_risultato
Scan-201105-0292_2R_risultato
Scan-201105-0293_1L_risultato
Scan-201105-0293_2R_risultato
Scan-201105-0294_1L_risultato
Scan-201105-0294_2R_risultato
Scan-201105-0295_1L_risultato
Scan-201105-0295_2R_risultato
Scan-201105-0296_1L_risultato
Scan-201105-0296_2R_risultato
Scan-201105-0297_1L_risultato
Scan-201105-0297_2R_risultato
Scan-201105-0298_1L_risultato
Scan-201105-0298_2R_risultato
Scan-201105-0299_1L_risultato
Scan-201105-0299_2R_risultato
Scan-201105-0300_1L_risultato
Scan-201105-0300_2R_risultato
Scan-201105-0301_1L_risultato
Scan-201105-0301_2R_risultato
Scan-201105-0302_1L_risultato
Scan-201105-0302_2R_risultato
Scan-201105-0303_1L_risultato
Scan-201105-0303_2R_risultato
Scan-201105-0304_1L_risultato
Scan-201105-0304_2R_risultato
Scan-201105-0305_1L_risultato
Scan-201105-0305_2R_risultato
Scan-201105-0306_1L_risultato
Scan-201105-0306_2R_risultato
Scan-201105-0307_1L_risultato
Scan-201105-0307_2R_risultato
Scan-201105-0308_1L_risultato
Scan-201105-0308_2R_risultato
Scan-201105-0309_1L_risultato
Scan-201105-0309_2R_risultato
Scan-201105-0310_1L_risultato
Scan-201105-0310_2R_risultato
Scan-201105-0311_1L_risultato
Scan-201105-0311_2R_risultato
Scan-201105-0312_1L_risultato
Scan-201105-0312_2R_risultato
Scan-201105-0313_1L_risultato
Scan-201105-0313_2R_risultato
Scan-201105-0314_1L_risultato
Scan-201105-0314_2R_risultato
Scan-201105-0315_1L_risultato
Scan-201105-0315_2R_risultato
Scan-201105-0316_1L_risultato
Scan-201105-0316_2R_risultato
Scan-201105-0317_1L_risultato
Scan-201105-0317_2R_risultato
Scan-201105-0318_1L_risultato
Scan-201105-0318_2R_risultato
Scan-201105-0319_1L_risultato
Scan-201105-0319_2R_risultato
Scan-201105-0320_1L_risultato
Scan-201105-0320_2R_risultato
Scan-201105-0321_1L_risultato
Scan-201105-0321_2R_risultato
Scan-201105-0322_1L_risultato
Scan-201105-0322_2R_risultato
Scan-201105-0323_1L_risultato
Scan-201105-0323_2R_risultato
Scan-201105-0324_1L_risultato
Scan-201105-0324_2R_risultato
Scan-201105-0325_1L_risultato
Scan-201105-0325_2R_risultato
Scan-201105-0326_1L_risultato
Scan-201105-0326_2R_risultato
Scan-201105-0327_1L_risultato
Scan-201105-0327_2R_risultato
Scan-201105-0328_1L_risultato
Scan-201105-0328_2R_risultato
Scan-201105-0329_1L_risultato
Scan-201105-0329_2R_risultato
Scan-201105-0330_1L_risultato
Scan-201105-0330_2R_risultato
Scan-201105-0331_1L_risultato
Scan-201105-0331_2R_risultato
Scan-201105-0332_1L_risultato
Scan-201105-0332_2R_risultato
Scan-201105-0333_1L_risultato
Scan-201105-0333_2R_risultato
Scan-201105-0334_1L_risultato
Scan-201105-0334_2R_risultato
Scan-201105-0335_1L_risultato
Scan-201105-0335_2R_risultato
Scan-201105-0336_1L_risultato
Recommend Papers

C. Didattica e programmazione [Quarta ed.]
 9788871922195

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

c Didattica e programmazione

© 2004 Pearson Paravia Bruno Mondadori S.p.A.

Authoriud mmskttion .Fom the fnglish !tmguage edition, l~Jtitled· A Book on C: programmin8 in C- Fourth Edition by kelley, Al; Pohl. Ira, published by Pear.Hm Education, /ne, publislling as AddiJon We.\·ley Professi ono/, Copyri~ht © 1998. Al/ rights re.)·erved. No part of this book nuzy be reprodured or trammiued in ar~y jòrm or kY ar~y merms, t'lectronic or mechanical, including photocopying, ruording or by nny infornuuion storage rarieval system, withottt permission from Pearson Education, !ne. fttz!ian language edition published kY Pear.mn Paravia Bmno Mom/adori S.p.A., Copyright© 2004 Le informazioni contenute in quesro libro sono srare veritìcare c documentare con la massima cura possibile. Nessuna responsabilità derivante dal loro utilizzo porrà venire impurata agli Autori, a Pearson Paravia Bruno Mondadori S.p.A. o a ogni persona e società coinvolta nella creazione. produzione e distribuzione di questo testo.

l diritti di riproduzione e di memorizzazione elettronica totale e parziale con qualsiasi mezzo, compresi i microfilm e le copie fotostatiche, sono riservati per tutti i paesi. LA FOTOCOPIATURA DEl LIBRI È UN REATO

Le fotocopie per uso per onale del lettore possono essere effettuate nei limiti del 15% di ciascun volume dietro pagamento alla SIAE del compenso previsto dall'art. 68, commi 4 e 5, della legge 22 aprile 1941 n. 633. Le riproduzioni effettuate per finalit~t di carattere professionale, economico o commerciale o comunque per uso diverso da quello personale possono essere effettuate a seguito dì specifica autorizzazione rilasciata da Al DRO, corso di Porta Romana n. 108,20122 Milano. e-mail segreteria c aidro.org e sito web www.aidro.org. Traduzione: Giovanni Pighizzini Aggiornamento alla quarta edizione: Roberto Radicioni Revisione tecnica: Paolo Massazza Realizzazione editoriale: Shortcut, a cura di Giulia Maselli e Donatella Pepe Grafica di copertina: Gianni Gilardoni Stampa: Lalitotipo- Settimo Milanese (MI) Tuni i marchi citati nel testo sono di proprietà dei loro detentori. 978-88-7192-219-5 Prinred in ltaly 4a edizione italiana: settembre 2004 Ristampa 02 03 04

Anno 12

Ai nostri genitori

Sommario

xv

Prefazione Capitolo O

0.1 0.2 0.3 0.4 Capitolo l

1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 Capitolo 2

2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9

Partire da zero Perché il C? Standard ANSI C Dal C al C++ Dal C e C++ ajava

l

Panoramica sul C Programmazione e preparazione Output dei programmi Variabili, espressioni e assegnamenti Utilizzo di #define e #include Utilizzo di printf() e scanf() Flusso del controllo Funzioni Array, stringhe e puntatori File Considerazioni sui sistemi operativi Riepilogo Esercizi

5

Elementi lessicali, operatori e sistema C Caratteri ed elementi lessicali Regole sintattiche Commenti Parole chiave Identificatori Costanti Costanti stringa Operatori e simboli di interpunzione Priorità e associatività degli operatori

2 3 3 4

5 6 9 12 16 19 26 32 41 47 51 53 61

62 64

66 67 68

69 71 72 73

VII/

Sommario

2.10 2.11 2.12 2.13 2.14 2.15

Operatori di incr mento e d crem nto Op ratori di a gnam nto Un esempio: il calcolo d Il pot nz di 2 Il i tema C Ri pilogo E rcizi

Capitolo 3

Tipi di dati fondamentali Dichiarazioni, pr 1001 Tipi di dati fondamentali Caratteri tipo di dati char Tipo di dati int Tipi interi short, long e un ign d Tipi reali Utilizzo di typ d f Operatore iz of Utilizzo di g tchar() e putchar() Funzioni matematiche Conver ioni cast Co tanti esad cimali e ottati Riepilogo E rcizi

3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 Capitolo 4

4.1 4.2 4.3 4.4

4.5 4.6

4.7 4.8 4.9 4.10 4.11

4.12 4.13 4.14

4.15 4.16

4.17 4.18 4.19

Capitolo 5 5.1

5.2

Flusso del controllo Op ratori r }azionali, di uguaglianza logici E pre sioni op ratori r lazionali E pre ioni operatori di uguaglianza E pre ioni operatori logici I truzioni compo t i truzion vuota I truzione e pr ion I truzioni if if-el e I truzione while I truzione for Un m pio: variabili bool an Operator virgola I truzion do Un mpio: i num ri di Fibona ci I truzion goto ontinu I truzioni br ak I truzion wit h Op rator condizionai Ri pilogo E rcizi

74 76 78

79 84 85 95

95 97 99 102 104 105 108 108 109 112 115 118 121 122 131

131 132 135 137 140 141 141 144 148 149 151 152 154 157

158 160 161 162 163 175

175 178

Sommario

5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17

Prototipi di funzion Un s mpio: co truzion di una tab lla di potenz Dichiarazioni di funzion dal punto di vi ta d l compilator Uno tile alt rnativo per l'ordine di d finizione di funzioni Chiamata di funzione chiamata p r valor Co truzion di programmi di grandi dim nsioni Utilizzo di a rzioni R gole di vi ibilità Cla i di m morizzazion Variabili tatich tern Inizializzazion di default Ricor ion Un e empio: l torri di Hanoi Ri pilogo E rcizi

Capitolo 6

Array, puntatori e stringhe

6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 6.11 6.12 6.13 6.14 6.15 6.16 6.17

Array monodim nsionali Puntatori Chiamata p r indirizzo Relazione tra array e puntatori Aritm tica d i puntatori dimen ione degli elem nti Array com parametri di funzioni Un mpio: bubblesort Allocazione dinamica della memoria con calloc() malloc() Un esempio: merge e m rgesort Stringhe Funzioni d Ila libreria tandard p r la g tione di tringh Array multidim n ionali Array di puntatori Parametri di main () Array frastagliati Funzioni com parametri Un sempio: utilizzo d l m todo di bi ezione per determinar le radici di una funzione Array di puntatori a funzioni Qualificatori di tipo const e volatile Riepilogo E ercizi

6.18 6.19 6.20 6.21 Capitolo 7

7.1 7.2 7.3 7.4

7.5 7.6

Operatori orientati ai bit e tipi enumerativi Operatori ed espressioni orientati ai bit Maschere Strumenti software: stampa bit a bit di un in t Compattamento e scompattamento Tipi enumerativi Un esempio: il gioco della morra cinese

IX

179 181 182 183 184 186 189 190 193 197 199 199 204 208 210 219 219 222 225 227 229 229 230 232 235 241 243 247 253 259 260 261 264 269 274 275 277 295 295 300

301 303 306 309

X

Sommario

7.7 7.8

Ri pilogo rcizi

Capitolo 8

8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.

8.9 8.10 8.11 8.12 .13 .14 .15 8.16 .17 Capitolo 9

9.1 9.2 9.3 9.4

9.5 9.6 9.7

9.8

#pragma

unioni truttur A o ai m mbri di una truttura Priorità a o iatività d li op ratori: una panoramica final tilizzo di truttur on funzioni Inizializzazion di truttur n mpio: il gio o d l pok r ni ni ampi di bit

315 316 323 323 324 326 329 330 334 338 339 342 342 343 344 345 345 345 34 350 361 361 364 36 369 371 371 376 379

9.9

381

9.10 9.11 9.12

3 2

386 388 397

Capitolo IO

10.1 10.2 10.3 10.4

10.5 10.6 10.7 10.

10.9

di li t mpio: n tazion pola a

valutazion di uno tack

397 399 401 405 410 413 420 424 428

Sommario

Xl

10.10 10.11

Ri pilogo E rcizi

Capitolo 11

Input/output e sistema operativo

441

11.1 11.2 11.3 11.4 11.5 11.6 11.7 11.8 11.9 11.10 11.11 11.12 11.13 11.14 11.15 11.16 11.17 11.18 11.19 11.20 11.21

Funzion di output printf() Funzion di input canf() Funzioni fprintf(), f canf(), printf() canf() Funzioni fopen O fclo O Un mpio: doppia paziatura in un fil Utilizzo di fil t mporan i funzioni p r onalizzat Ace o dir tto ru fil lnput/output con d crittori di fil P rm i di ace o ai file E cuzion di comandi dall'int rno di un programma C Utilizzo di "pipe" da un programma C Variabili di ambi nt n compilator c Utilizzo d l profil r Libr ri Cronom trar il codic C Utilizzo di mak Utilizzo di touch Altri trum nti utiH Ri pilogo E rcizi

441 446 450 451 453 456 459 460 462 463 464 465 466 468 469 471 474 480 480 482 483 493 493 495 498 501 503 507 515 520 521

Dal C al C++ Output lnput Funzioni Cla i tipi di dati a tratti Ov rloading Co truttori e distruttori Programmazion ori ntata agE ogg tti d r ditari tà Polimorfi mo ~ mplat

527

Capitolo 12

12.1 12.2 12.3 12.4 12.5 12.6 12.7 12.8 12.9 Capitolo 13

13.1 13.2 13.3 13.4 13.5 13.6 13.7 13.8 13.9

432 433

527 529 531 533 535 537 539 540 543

Xli

Sommario

13.10 13.11 13.12 13.13

Eccezioni in C++ Vantaggi della programmazione orientata agli oggetti Riepilogo Esercizi

Capitolo 14

Dal C aJava Output Variabili e tipi Classi e tipi eli dati astratti Overloading Costruttori e distruttori di classi Programmazione orientata agli oggetti ed ereditarietà Polimorfismo e ridefinizione di metodi Applet Eccezioni in Java Vantaggi di java e della OOP Riepilogo Esercizi

14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9 14.10 14.11 14.12

Appendice A la libreria standard Diagnostica: Al Gestione di caratteri: A2 A3 Errori: Limiti in virgola mobile: A4 A5 Limiti interi: A6 Localizzazione: A7 Matematica: A8 Salti non locali: A9 Gestione dei segnali: AlO Parametri variabili: Al l Definizioni comuni: lnput/output: Al2 Al3 Utility generali: Gestione della memoria e delle stringhe: Al4 Al5 Data e ora: Altre funzioni Al6 Appendice B Sintassi del linguaggio C Programma 8.1 8.2 Definizioni di funzioni 8.3 Dichiarazioni 8.4 Istruzioni 8.5 Espressioni 8.6 Costanti 8.7 Stringhe letterali 8.8 Preprocessore

544 545 546 548 553 554 555 556 558 558 559 560 561 563 564 565 566 569 569 570 571 571 572 572 573 576 576 577 578 579 587 594 598 601 605 605 605 606

607 607 608 609 609

Sommario

Xlii

Appendice C Confronto tra ANSI C e C tradizionale Tipi C.1 C.2 Costanti C.3 Dichiarazioni Inizializzazioni C.4 Espressioni C.5 Funzioni C.6 C.7 Conversioni Puntatori ad array C.8 Strutture e unioni C.9 Preprocessore C.10 File d'intestazione C.11 Varie C.12

611

Appendice D Codifiche ASCU

619

Appendice E Priorità e associatività degli operatori

621

Riferimenti bibliografici

623

Indice analitico

627

611 612 613 613 613 614 615 615 616 616 617 617

Prefazione

U proposito del testo C- Didattica e Programmazione è quello di richiamare l'attenzion del lettore sull'elegante semplicità e sulla potenza del linguaggio di programmazion general-purpose C. In questo libro viene descritta la versione ANSI del linguaggio ttraverso programmi eseguibili interattivi legati a svariate aree applicative. Illinguagio viene presentato passo per passo, e il percorso didattico è integrato da molti proaromi completi e funzionanti. Ove opportuno vengono evidenziate le differenze tra C tradizionale e ANSI C (il C tradizionale continua a essere largamente utilizzato). Tutte le caratteristiche importanti l linguaggio vengono illustrate attraverso programmi di esempio, e le informazioni hiave vengono riassunte mediante apposite tabelle che rendono molto semplice il reJ rimento delle informazioni di riferimento. Ogni capitolo si conclude con un riepilogo, n l quale vengono riassunti i punti fondamentali trattati nel corso del capitolo stesso, e n una serie di esercizi che permettono di approfondire e integrare le conoscenze qui ite. Il linguaggio C viene presentato con le sue prerogative di linguaggio general-pur' pertanto questo testo è diretto sia agli studenti che ai professionisti; nel primo o esso si colloca come supporto principale a un primo o secondo corso di programazione, ma può risultare di grande interesse anche nei corsi di analisi comparata dei linguaggi di programmazione, linguistica computazionale, strutture dati, basi di dati, metria frattale, grafica, analisi numerica, sistemi operativi, ingegneria del software, pplicazioni scientifiche. È possibile scrivere in C delle applicazioni relative a tutte quet aree, e nel testo vengono presentate tutte le caratteristiche del linguaggio utili a tale po. Questo libro è adatto ai corsi di strutture dati in quanto vengono presentate le ratteristiche avanzate per la strutturazione dei dati come i tipi enumerativi, le unioni, trutture autoreferenzianti e gli array frastagliati. Per i corsi di sistemi operativi che trattino UNIX o Windows 95/NT, nel testo vengono presentati il trattamento dei file e le utine di sistema che permettono al programmatore C di creare nuove librerie e comr ndere il codice C con cui è scritto il sistema stesso. Per quanto riguarda la programmazione applicativa e scientifica vengono presentati dei metodi per la stesura di librerie di funzioni. Statistica, calcolo delle radici, ordinamento, manipolazione di testi, gestione d i file e giochi sono temi affrontati negli esempi presentati.

XVI Prefazione

Nuovo capitolo su Java

Il linguaggio C nella versione completa ANSI compi ta d l

Ambiente interattivo ll t to ' tato eone pito alla tr gua di un mod mo ambi nt int rattivo la p rim ntazion vi n timo lata continuam n t . L'input/ output m dian te ta ti ra eh ermo, con tutto ciò eh ne d riva, ' consid rato la norma: il te to ri ulta quindi util ia per gli ut nti di home e p r onal comput r che per gli ut nti di work tation u r ti. i uppon che il l ttor po a ace d r a un i t ma AN I C int rattivo. Durant la te ura d l libro, gli autori hanno utilizzato molti i t mi ompilatori differ nti: il compilator GNU gcc u div work tation prodott da D , GI, un altri; vari compilatori di Borland Micro oft u macchin P ntium I M ompatibili il compilator C pr nt sul supercomput r Cray di an Di go.

Codice funzionante L'approccio alla d rizion d l linguaggio pr v de un gran num rodi mpi, pi gaztom inta si, ovunqu vi n utilizzato odic funzionant . Per d riv re i punti tecnici importanti v ngono utilizzati empi br vi ma utili: br vi p r aum ntare la compren ibilità, utili perché la programmazion ' basata u una gerarchia di blocchi co-

Prefazione

XVII

truttivi , in d finitiva, ' pragmati a. Le funzioni i programmi d critti n l t to po ~ ono r utilizzati u i t mi r ali: la filo ofia d gli autori pr v d eh la rim ntazion d bba r div rt nt appa ionant .

Analisi l cor o d l libro ricorrono zioni di approfondim nto chiamat "anali i" d dicat a molti programmi funzioni. L'anali i ' uno trum nto p dagogico uni o inizialm nt viluppato dagli autori n l 1984 p r m tter in luc aratt ri tich hiav d 1 odic di lavoro. So tanzialm nt i tratta di un'anali i approfondita d l codic finalizzata a pi ar al l ttor i nuovi l m nti idiomi di programmazion in ontrati n l odic di lavoro.

Organizzazione flessibile

nlibro

organizzato in funzion di un utilizzo fl

mpi

pi gazioni.

Testo di riferimento

o ri ulti un util

XVIII Prefazione

Il linguaggio ANSI C completo Dal Capitolo 3 al Capitolo 10 ono pr ntate, una per una, l caratt ri tich del linguaggio ANSI C; v ngono di eu si anch molti argom nti avanzati eh , volendo, po ono e s re om i in una prima l ttura enza alcuna perdita di compren ione. I tipi numerativi, per e empio, ono r lativament nuovi nel linguaggio il loro utilizzo può e re ome o in un primo cor o di programmazione. Durant l'anali i delle caratteri tich dipendenti dalla macchina v ngono pr ntate delle con id razioni ulla dim n ion delle parole ulla rappr ntazione in virgola mobile; anch in que to ca o si tratta di dettagli che un principiante potrà approfondir in un secondo mom nto.

Il preprocessore ll Capitolo 8 ' d dicato interamente al pr proc s or , utilizzato per t nder la potenza e la notazion d l linguaggio C. P r gen rar codice in lin a ' po ibil utilizzar delle macro invec dell chiamat di funzioni, e ciò riduce il tempo di e cuzion d l programma. ll pr proc sore viene esaminato in modo dettagliato, con id rando l nuov caratteri tiche aggiunte dal comitato ANSI. Nel C tradizionale il preproce or varia in modo considerevole a econda d l compilator , mentre in ANSI C la funzionalità d l preproces ore è tata completam nte sp cificata.

Ricorsione e trattamento di liste Nel Capitolo 5 viene accuratam n t aminata la ricor ione, argomento pe o o tico al principiante. L'impi go della ricor ion vien ripr o n l Capitolo 8, pr ntando l'algoritmo quick ort, e n l Capitolo 10, con l tecnich di ba di trattam nto d 11 li t : una cono cenza compi ta di tali t cnich aria n i cor i avanzati di programmazion e trutture dati.

Connessione con il sistema operativo N l Capitolo 11 ono pr ntat l conne ioni con il i t ma operativo, vi n piegato com labo rare i fil v ngono ampiament analizzat le funzioni di input/ output d Ila libr ria tandard. Vi n anch pi gato com eguir un comando di i t ma dall'int mo di un programma C, impo tare i perme i di acce o ai fil utilizzar l variabili dell'ambi nte e terno . L'utilizzo d l pro.fil r, d l programma p r cr ar libr ri di make vi n illustrato attrav r o s mpi as ai chiari.

Applicazioni avanzate N l Capitolo 12 v ngono analizzate num ro applicazioni avanzate. Argom nti com la cr azione di proce si concorr nti, la ostituzion di un proc o, la comunicazion tra

Prefazione XIX

processi vengono presentati con il supporto di sempi di codice funzionante. Viene trattata inoltre l'allocazione dinamica di vettori matrici, utile a ingegneri e scienziati; è possibile affrontare questi argomenti avanzati in modo lettivo a econda d gli obiettivi d l corso, ed essi possono costituire la ba di un ccellente secondo cor o di programmazione. n testo può anche e ere utilizzato com upporto a corsi avanzati di informatica che utilizzino com linguaggio implementativo il C.

Tabelle, riepiloghi ed esercizi

n te

to è ricco di el nchi e tabelle che ria umono i concetti principali, facilitando e permettendo di verificare la comprensione del linguaggio. Per e empio, il C ' ricco di operatori che po sono es ere combinati praticamente in tutti i modi utili al programmatore; è fondamentale comprendere l'ordine di valutazione e associazione di tali operatori ia ingolarmente che nelle combinazioni, e que ti punti vengono illustrati n Ile tabel1 pr enti in tutto il te to. Inoltre, le tabelle risultano sempre un utile trumento di riferimento. Attraver o gli esercizi vengono verificate le caratteristiche elementari del linguaggio e analizzate le caratteristiche avanzate e le dipendenze dal sistema. Molti e ercizi ono orientati alla soluzione di problemi, mentre altri verificano la comprensione sin tattica e semantica del linguaggio. All'interno di alcuni e rcizi sono inclu e delle anali i che, partendo dagli argomenti del testo, introducono aspetti che possono essere particolarm nte interessanti per alcuni lettori. Gli e ercizi offrono al docente svariati livelli di difficoltà, permettendo di se gliere quelli più adatti al cor o.

XX

Prefazione

Ringraziamenti Sp ciali ringraziam nti a D bra Dol b rry, il principal ditor t cnico di qu to libro, r pon abile dell'u o di Fram Mak r per la cr azion d i fil Po tScript p r la tampa. Speciali ringraziam nti anch a Rob rt Fi Id, ParcPlace y tem , Mountain View, California, il principal r vi or t cnico d Ila prima dizion di qu to libro, la cui sperienza i cui ugg rim nti ci ono tati di grand aiuto. p ciali ringraziam nti anch a John d Pilli , Univ r ity of California, Riv r id , per i di gni "Le torri di Hanoi" d l Capitolo 5 "I cinqu filo ofi" d l Capitolo 12. D ideriamo anch ringraziar altr per on eh ci hanno fornito utili con igli: Murray Baumgart n, Univ r ity of California, Santa Cruz; Micha l B on, San Jo State Uruver ity, San Jo , California; Randolph B ntson, Colorado tat Univer ity, Ft. Collin ; Jim Bloom, Univ rsity of California, Berkeley; John Bowi , Hewlett-Packard Co., Inc.; Skona Brittain, Uruver ity of California, Santa Barbara; Tunothy Budd, Univ rity of Arizona, Tue on; Nick Burgoyn , Univ r ity of California, Santa Cruz; Jim Chrilock, Mindcraft, Inc.; Al Conrad, Univer ity of California, Santa Cruz; J ff Donn lly, Univer ity of lllinoi , Urbana; Dick Fritz, AT&T B 11 Laboratori ; R x Gant nb in, Univ r ity of Wyoming, Laramie; Harry Gav r, Rl International, G orgia; Leonard Garr tt, ~ mpl Univ r ity, Philadelphla; William Giles, San Jo Stat Univer ity, an Jo , California; usan Graham, Uruv r ity of California, B rk l y; Jorg Hankam r, Uruv r ity of California, anta Cruz; Rob rt Haxo, Au pex, Inc., an Jo , California; Mik John on, Or gon tat Univ r ity, C01valli ; K ith Jolly, Chabot Coli ge, San Leandro, California; Carol K ll y, Cabrillo Coli g , Apto , California; Clifford Layton, Rog r tate Univ r ity; Darr 11 Long, Univ r ity of California, anta Cruz; Charli McDowell, Univer ity of California, Santa Cruz; Andrew Pl zkun, Univ r ity of Colorado, Bould r; G offrey Pullum, Univ r ity of California, Santa Cruz; Pet r Ro ncrantz, The anta Cruz Operation, Inc.; Mik hoonov r, H wl tt-Packard Co., Inc.; Pet r Scott, Un iv r ity of California, Santa Cruz; Alan haw, Univ r ity of W a hington, atti ; Ttlly haw, Univ r ity of California, Santa Cruz; Matt tallmann, Univ r ity of D nv r. Inoltr , d id riamo ringraziar il no tro pon oring ditor Cart r hanklin p r l' n tu ia m o, il upporto l'incoraggiam nto; infin , d id riamo ringraziar Joh n Full r p r la cura da lui d dicata alla produzion di qu to libro ul C. Al Kelley Ira Pohl Univ r ity of California, anta Cruz

Capitolo O

Partire da zero

Z ro ' il punto di partenza naturale nel linguaggio di programmazion C. Il C conta da O. In C, O significa falso e non O significa vero, gli indici d gli array hanno com limit inferiore lo O, e le tringh utilizzano lo Ocome marcato re di fine stringa. In C i punta tori utilizzano lo Oper de ignar un valore nullo. Le variabili e teme e statiche del C v ngono automaticamente inizializzat a O. In questo libro vengono pi gate que t id vi n introdotti al piacer delJa programmazion in C. n c è un linguaggio di programmazione general-purpo progettato in origin da D nnis Ritchie dei Beli Laboratori , dov ' tato implementato nel1972 u un laborator PDP11. Esso fu inizialm nte utijizzato com linguaggio p r la programmazione d l i tema operativo UNIX; K n Thomp on, eh sviluppò UNIX, utilizzò un a emblator un linguaggio di nome B per produrr n l 1970 la prima v rsion di UNIX. n C fu inventato per sopp rire ali limitazioni del B. ll linguaggio di programmazione B ra ba ato u BCPL, un linguaggio privo di tipi viluppato da Martin Richard nel 1967 p r la programmazione di i t mi; qu to linguaggio av va come tipo di dati di ba la parola d Ila macchina, fac va p antem n t u o dei puntatori e dell'aritm tica d gli indirizzi: ciò ' contrario allo spirito d Ila programmazione strutturata, eh è inv c caratterizzata dall'utilizzo di linguaggi fort m nt tipati, come i linguaggi di tipo ALGOL. Il C rappr s nta un'evoluzion di B BCPL, incorpora l'utilizzo dei tipi. All'inizio degli anni Ottanta il linguaggio C i era voluto nel co idd tto "C tradizionale" mediante l'aggiunta del tipo void, dei tipi num rativi di altri miglioram nti. Alla fine degli anni Ottanta il comitato X3Jll dell'American National Standard In titut (ANSI) cominciò ad abbozzare lo tandard di quello che ora ' noto com "ANSI C" o "C tandard". In particolare vennero aggiunti il tipo voi d *, i prototipi di funzione, una nuova sintassi per la definizione di funzioni e ulteriori funzionalità del preproces ore; in n rale, venne fornita una definizione del linguaggio più preci a. Oggi ANSI C è un linguaggio general-purpose consolidato, ampiam nte di ponibile su molte macchine e i temi operativi. Esso è uno dei principali linguaggi utilizzati nelle industrie di tutto il mondo ed è diffuso ovunque nelle scuole uperiori e nelle università. ANSI C è inoltre

2

Capitolo

o

alla ba del C++, un linguaggio di programmazione che incorpora co trutti ori ntati agli ogg tti. In qu to libro vi n d critta la ver ion AN I d l linguaggio C, in iem ad alcuni a petti del C++ di Java.

0.1

Perché il C?

n linguaggio c \ piccolo,

n ]]'ambito d lla programmazione "piccolo è b Ilo". n c ha m no parol chiav d 1 linguaggio Pa cal, n l quale e ono dett parol ri rvate, ma ' più potente per la pr nza d Il trutture di controllo dei tipi di dati più ad guati, il cui utilizzo, e effettuato in modo n ato, non ha praticamente r trizioni. Grazi alla ua truttura minimale, il linguaggio può es ere appre o facilmente. n C è il linguaggio nativo di NIX, un i tema operativo interattivo molto important utilizzato u work tation, rv r mainfram . n C è inoltr il linguaggio di viluppo tandard p r per onal computer: gran parte di MS-DOS O /2 ' critta in C. Molti pacchetti che utilizzano fin tr , programmi per la ge tione di ba i di dati, libr ri grafiche e altri pacch tti applicativi di grand dimen ione sono critti in C. n C ' portabile; il codic ritto r una macchina può re facilm n te tra ferito su un'altra. C fornisc al programmator una libreria tandard di funzioni il cui comportamento è lo t o u ogni mac bina. lnoltr , il C contiene un pr proc ore utile al programmatore p r i olar l parti di odi dipendenti dalla macchina. n c pulito. n c ha un in i m di o ratori molto potenti, alcuni dei quali permettono di accedere alla macchina alliv Ilo d i bit L'operatore di increm nto ++, av n do un analogo immediato in molti linguaggi ma bina, ri ulta molto effici nte. L'indirizzamento indiretto e l'aritmetica d gli indirizzi po no r combinati in una ingoia i truzion o e pr ion anzich in mol om a cadr bb in altri linguaggi; per molti programmatori ciò ri ulta ia l gan h ffi i nt . Gli tudi ulla produttività del oftware mostrano che i programmatori rivono, in m dia, olo una piccola quantità al giorno di codice funzionante. Un linguaggio pulito amplifica in modo evid nte la produttività del programmator . n c è modular . n c upporta un olo tipo di routine, la funzione esterna, alla qual ' po ibil pa sar i param tri m diant la "chiamata per valor ".Non è po sibile innestar funzioni. Vien fornita una rta forma di privacy che consi te nell'impiego della da e di memorizzazion static n i fil . Que te po sibilità, in i me agli strumenti fomiti dal i tema operativo, upportano prontamente le librerie definite dall'utente e la programmazion modulare. n C ' alla ba e del C++ di ]ava. Ciò significa che molti costrutti e metodologi utilizzate abitualmente dal programmator C vengono utilizzate anche dal programmatar C++ e ]ava, l'apprendim nto d l C può quindi e sere con iderato come un primo pa o v r o l'apprendimento del C++ o di ]ava. n C ' efficiente ulla maggior parte delle macchine. Poiché alcuni co trutti del linguaggio dip ndono e plicitamente dalla macchina, il C può essere implementato in

n

Partire da zero

3

a, con il ri ultato di ott n re cono r l parti di codic

Tuttavia il C ' un linguaggio l gant non impon vin oli aJ programmator riP o aJI'acc o aJla macchina. È più fa il conviv r con l u imperi zioni eh on una p r:f zion ba ata u r trizioni. n C ' attra nt , grazi alla pot nza d i uoi op ratori alla ua natura lib ra. Un rogrammator C prop nd alla modularità funzionai al minimali mo pr zza la perim ntazion l'int razion eh p rvadono qu to libro.

0.2

Standard ANSI C

L'acronimo AN I ta per "Am rican National tandard In titut ". Qu to i tituto fi a li tandard p r variati tipi di i t mi, tra ui i linguaggi di programmazion . In parti olar , il comitato AN I X3Jll ' r pon abil d llo tandard p r il linguaggio di programmazion C. Com già a c nnato, alla fin d gli anni Ottanta il omitato iniziò ad abbozzar gli tandard p r qu Ilo eh ora ' noto com "AN I C" o "C tandard". N l 1990, dopo il t rmin d i lavori d l comitato, l'Int mational tandardization Organization (l 0) approvò lo tandard AN I C; dunqu AN I C, o AN III O , ' uno tandard ri ono iuto liv Uo int rnazional . Lo tandard p cifi a la forma d i programmi ritti in C tabili programmi d bbano r int rpr tati. Lo opo d Ilo tandard on i t n l promuov r portabilità, affidabilità, manut nibilità d ffici nza di uzion d i programmi in linguaggio C u una grand vari tà di ma hin . Oggi qua i tutti i ompilatori guon lo tandard AN I C.

0.3

Dal C al C++ mainfram in tutto il mondo. Con-

4

Capitolo O

oggetti sullo stile di Smalltalk, e altre varianti di C permettono di fruttare il parallelismo sui supercomputer. Di notevole importanza è il C++, un linguaggio orientato agli oggetti largamente utilizzato. Essendo un'esten ione del C, è possibile utilizzare in progetti software di notevole dimensione sia codic C che codice C++. n C++ può e sere appre o facilmente dai programmatori C (vedi Capitolo 13).

0.4

Dal C e C++ a Java

Java è stato progettato p r lavorare in ambito Internet. Permette a un programmatore di scrivere programmi sicuri e portabili che possono ess re scaricati da Internet ed eseguiti localmente sulla propria macchina. Que to nuovo linguaggio di programmazione prende in prestito molti aspetti sia dal C che dal C++ e li esporta in un contesto di lavoro indipendente sia dalla macchina che dal sistema. La sua semantica è definita in termini di una macchina virtuale: ciò comporta che Java sia per definizione portabile e si comporti nello stesso modo su piattaforme div r e, come un PC con sistema operativo Windows 95 o una workstation con una qualunque versione di UNIX. Le sue caratteristiche lo rendono particolarmente utile per implementare applicazioni (applet) es guibili da pagine web tramite browser. Tali applicazioni solitamente forniscono anche un'interfaccia grafica. Infine, }ava può essere appreso velocemente da un programmatore C, essendo un'estensione dei linguaggi C e C++ (vedi Capitolo 14).

Capitolo 1

Panoramica sul C

In qu to capitolo è presentata una panoramica ul linguaggio di programmazion C. no illu trati variati programmi, ognuno dei qualj viene e aminato in dettaglio. In t tto il testo viene data particolare importanza alla sperimentazione e all'interazion . In u to capitolo si presta attenzion principalmente all'utilizzo delle funzioni elementari di input/output del C. Si os rvi che tutto il codice C pr entato è valido anche com dic C++, e che tutte le ide discu s in riferimento al C valgono anche per il C++. aturalmente, il programmatore C++ di pone di un più ricco in i me di strumenti t n iche utili p r la co truzione dei programmi (v di Capitolo 13). Un generico lettor è in grado di aminare tutto il materiale pre entato in qu sto pitolo, eccetto i Paragrafi 1.8 e 1.9. Coloro eh hanno già esperienza con vettori (arr y), puntatori e archivi (file) in altri linguaggi pos ono leggere tutti i paragrafi di queto capitolo per av re una panoramica più completa ul C, m ntr gli altri potranno aminare queste parti succe ivament , quando i ntiranno suffici nt ment pronti. In ntrambi i ca i è sottinte o eh dettagli tecnici ult riori pi gazioni v ngono forniti n i capitoli successivi.

1.1

Programmazione e preparazione

u ogni macchina risied un insieme di programmi particolari, detto sistema operativo; mpi di sistemi operativi largamente diffu i sono MS-DOS, OS/2 UNIX. Un si tema p rativo gestisce le risorse d Ua macchina, forni ce programmi (software) p r l'ut nte volge funzioni di interfaccia tra l'utente e la macchina (hardware). Tra i numerosi pacchetti oftware forniti dal si tema op rativo vi ono il compilator C vari programmi p r la crittura di testi (text editor); il principale text ditor fornito con il i t ma IX si chiama vi. Alcuni sistemi, come C++ di Borland, integrano text editor e compilatore. Si presume che il lettore abbia familiarità con un text editor che gli p rmetta di r are file contenenti codice C. Tali file, detti file sorgente, vengono compilati sulla

6

Capitolo 1

maggior parte d i istemi UNIX con il comando cc, eh invoca il compilatore C. In altre parole, un compilatore traduc il codice orgente in codice oggetto, eseguibile direttamente dalla macchina. Sui sist mi UNIX tal codice compilato viene posto in un file chiamato a.out, creato automaticam nt . ui istemi MS-DOS il codice ottenuto dalla compilazione viene po to in un fil con lo t o nome del file sorgente .c, ma con l'estensione .exe al posto di .c. Alla fin di qu to capitolo, nel Paragrafo 1.10, sono presentati in dettaglio i pa i n ari per criver un programma con un text editor, compilarlo ed eseguirlo.

1.2

Output dei programmi

Per essere utili, i programmi devono poter comunicare. Come primo esempio viene presentato un programma che stampa sullo schermo la frase "from sea to shining C". n programma completo è il seguente:

#include int main(void) {

printf(•from sea to shining C\n"); return 0; }

Si scriva questo programma, utilizzando un text editor, in un file il cui nome termini con .c. La scelta del nome dovrebbe essere mnemonica; se è stato scelto il nome sea.c, il programma può e sere compilato con il comando:

cc sea.c Se il codice è privo di errori, il comando crea il file eseguibile a.out. A questo punto, il comando

a.out e egue il programma e stampa sullo schermo:

from sea to shining C

ANALISI DEL PROGRAMMA sea •

#include All'interno del compilatore C si trova un preprocessore. Quando viene dato il comando di compilazione, prima della compilazione vera e propria viene richiamato tale preprocessore. Le righ di codice che iniziano con il carattere # contengono

Panoramica sul C

7

comandi per il preprocessore. In questo caso, trovando la riga #include, il preproce ore include il file d'inte tazion (header) stdio.h in questo punto del codice; tal file è fornito dal i tema C. I simboli "" che racchiudono il nom d l file indicano che esso si trova al " alito po to", che dipende dal istema. n fil stdio.h è tato inclu o in quanto contiene informazioni riguardanti la funzion printf ( ). •

int main(void) Questa la prima riga d Ha definizione di funzione per ma in ().Le par nte i tonde dopo il nome mai n ono scritte per ricordar ali ttore eh i tratta di una funzione. Le parole Ù1t e void ono parole chiav , o ris rvate, hanno uno p cial ignificato per il compilator . N l Capitolo 2 si vedrà eh il C conti n 32 parol ris rvat , tra cui appunto int



int main(void) {

Ogni programma conti n una funzion chiamata mai n ( ) ; l' cuzione di un programma comincia sempre con que ta funzione. La prima riga dovr bb e r l tta come '4 main () ' una funzione priva di parametri eh r stitui ce un valor int"; la parola chiav in t indica al compilatore eh qu ta funzion r titui c un valor di tipo in t. La parola in t sta per "intero" (integer), ma non ' po ibile utilizzare l parole intero o integer. Le parent i tonde dopo main indicano al compilatore che mai n è una funzione: questa idea in un primo momento può confonder , in quanto ciò che segue mai n è (v o id), ma solo le par n tesi () costituì cono un operator che indica al compilatore che main è una funzion . La parola chiav void serve per indicar al compilatore che la funzion non richi d alcun parametro. Come nella maggior parte dei libri di te to, quando vi n illu trato un eone tto concernente una funzion , come p r mpio ma in () o printf (), il nome della funzione ' eguito d il par nte i. {

Le parent si graffe racchiudono il corpo d lla d finizion di funzion , e vengono inoltre utilizzate p r raggruppar più i truzioni. printf() 11 istema C contiene una libreria tandard di funzioni utilizzabili nei programmi, e questa ' una funzione della libreria che tampa sullo schermo. È stato incluso il file d'intestazione stdio.h in quanto es o fornisce al compilatore alcune informazioni ulla funzione printf () (vedi Esercizio 14). "from sea to shining C\n" In C, una co tante di tipo tringa è una s rie di caratt ri racchiusa tra doppi apici. Questa stringa è un parametro della funzione printf () e controlla ciò che dev sere stampato. I due caratteri \n alla fine della tringa (si legga "barra inversa

8

Capitolo 1

n") rappresentano un singolo carattere chiamato newline: esso è un carattere non stampabile che fa avanzare il cursore all'inizio della riga successiva. •

printf("from sea to shining C\n") Questa è una chiamata della funzion printf ().In un programma, il nome della funzione seguito dalle parent i fa i eh la funzione venga chiamata o invocata. Tra le parentesi po ono comparir v ntuali parametri. In questo caso la funzione printf (),quando vi n invocata, tampa ullo chermo il suo parametro, una costante di tipo tringa.



printf("from sea to shining C\n"); Questa è un'istruzione (statement). Molte i truzioni C terminano con punto e virgola.



return 0; Questa è un'istruzione return. Essa fa sì che venga restituito il valore zero al sistema operativo, che può eventualmente, ma non necessariamente, utilizzarlo per qualche scopo (per un'ulteriore analisi vedi Paragrafo 12.7). L'utilizzo di tale istruzione return "fa contento il compilatore"; non utilizzandola, infatti, il compilatore "si lamenterebbe", ovvero segnalerebbe un problema (vedi Esercizio 4). Una delle principali regole della programmazione è quella di "far contento il proprio compilatore".

.

} Questa parentesi graffa chiude-la precedente parentesi graffa aperta e conclude la definizione della funzione mai n ().

La funzione printf () stampa sullo schermo da sinistra verso destra. Incontrando il carattere newline, essa sposta il cursore all'inizio di una nuova riga. Lo schermo è bidimensionale, e su di e so la tampa avviene da sinistra ver o destra e dall'alto verso il basso. Per essere leggibil , l'output dev e sere opportunamente spaziato. n nostro primo programma può re ri critto come segue:

#include int main(void) {

printf("from sea to "); printf("shining C"); printf("\n " ); return 0; }

Sebbene differente dalla prima versione, esso fornirà il medesimo output. A ogni chiamata di printf (),la stampa riparte dalla posizione successiva all'ultima utilizzata dalla precedente chiamata di printf (). Per stampare invece la nostra frase su tre righe è possibile utilizzare i caratteri newline.

Panoramica sul C

9

#include int main(void) {

printf("from sea\n"); printf("to shining\nC\n"); return 0; }

Al momento dell'esecuzione, questo programma stamp rà quanto egue:

from sea to shining

c

Vìene presentata ora un'ulteriore variazione di questo programma, nella quale la fra e è circondata da un rettangolo di asterischi. In questo e empio è mostrato come ogni carattere, compresi gli pazi e i caratteri n wline, sia significativo; inoltre, l'esecuzione del programma dà un'idea concreta delle proporzioni dello chermo.

Nel file sea2.c: #include int main(void) {

printf("\n\n\n\n\n\n\n\n\n\n"); printf(" ***********************\n"); printf(" * from sea *\n"); printf(" * to shining C *\n"); printf(" ***********************\n"); printf( "\n\n\n\n\n\n\n\n\n\n"); return 0; }

1.3

Variabili, espressioni e assegnamenti

In questo paragrafo viene presentato un programma per convertire la lunghezza di una maratona da miglia e yarde in chilometri. In unità inglesi, una maratona è definita come 26 miglia e 385 yarde; questi numeri sono interi. Per convertire le miglia in chilometri è n cessarlo moltiplicare per un fattore di conversione 1,609 (1.609, utilizzando il punto d cimale), ossia un numero reale. La rappresentazione interna utilizzata dai computer p r gli interi e per i reali è different . Per convertire le yarde in miglia, si divide per 1760,0 (1760.0); come si vedrà successivamente, è fondamentale rappresentare questo numero come reale invece che come intero.

1O

Capitolo 1

ll programma di conversione utilizza variabili in grado di contenere valori interi e reali. In C ogni variabil deve e sere dichiarata all'inizio del programma. n nome di una variabile, detto anche identificatore, è una sequenza di lettere, cifre e imboli di ottolineatura (underscore), ma non può cominciare con una cifra. Gli identificatori dovrebbero es ere scelti in modo da riflettere il loro utilizzo nel programma, risultando utili come documentazione e rendendo il programma più leggibile.

Nel file marathon.c: l* La lunghezza in chilometri di una maratona. * l

#include int main(void) {

in t float

miles, yards; kilometers;

miles 26; yards 385; kilometers = 1.609 * (miles + yards 1 1760.0); printf("\nA marathon is %f kilometers.\n\n " , kilometers); return 0; }

L'output d l programma è il eguente: A marathon is 42.185970

ki~ometers.

ANALISI DEL PROGRAMMA

marathon



l* La lunghezza i n chilometri di una maratona. */ Tutto ciò che i trova tra i caratt ri 1* * 1 ' un commento, e vie(le ignorato dal compilatore: tutti i programmi di qu to libro eh iniziano con un commento ono elencati nell'indice.



int miles, yards; Questa è una dichiarazione; le dichiarazioni e l i truzioni terminano con un punto e virgola. int è una parola riservata, d è uno dei tipi base del linguaggio: es a indica al compilatore che le variabili che las guono sono di tipo int , e assumono quindi valori interi. Quindi, le variabili miles e yards di questo programma sono di tipo int.



float kilometers; Questa è una dichiarazione; f lo a t è una parola ris rvata, ed è uno dei tipi ba e d l linguaggio: essa informa il compilatore che le variabili che la seguono ono di tipo float, e pertanto assumono valori reali. Quindi la variabile kilometers in questo programma è di tipo f loat.

Panoramica sul C

11



miles = 26; yards = 385; Queste sono i truzioni di a segnam nto. Il gno di ugual è un operatore di a egnamento, i due numeri 26 e 385 ono co tanti inter . Alla variabile miles vi n a egnato il valore 26, alla variabil yards il valore 385.



kilometers = 1.609 * (miles + yards l 1760.0); Qu ta ' un'i truzion di as gnam nto. n valor dell' pr ion scritta a d tra d l segno di uguale viene as egnato alla variabile kilometers. Gli operatori *, + 1 rappresentano rispettivam nte le operazioni di moltiplicazione, addizion divisione; vengono es guite per prime 1 operazioni all'interno d ll parentesi. Poiché la divi ione ha priorità più alta d Il' addizione (v di Capitolo 3), la valutazion inizia calcolando il valore della otto pr ion : yards l 1760.0 Tal valore vien aggiunto al valor della variabile miles, il risultato così ott nuto viene quincli moltiplicato per 1 . 609 e il prodotto di questa operazione viene infine a egnato alla variabile kilometers.



printf("\nA marathon is %f kilometers.\n\n", kilometers); Que ta è un'istruzion che invoca, o chiama, la funzione printf ().Tale funzion mpr una trinpuò aver un numero variabile di parametri, il primo dei quali ga, detta stringa di controllo. In qu sto e empio la tringa di controllo è la segu nte: "\nA marathon is %f kilometers.\n\n" Es a è il primo argom nto della funzione printf (). In qu sta tringa ' con t nuta la pecifica di conver ione, o formato, %f. I formati in una tringa di controllo, pr nti, v ngono a ociati, secondo l'ordine con cui appaiono, ai r tanti param tri di printf (). In que to caso, %f corri pond al param tro kilometers: ciò ignifica eh il valore d Ila variabile kilometers dev e r tampato come numero reale inserito nella quenza in uscita al posto d l formato %f.

Alcun parol , dette parole chiave, sono riservate non po ono e er utilizzat dal programmatore come nomi di variabili. Es mpi di paro l chiav ono in t, f lo a t double; un elenco d 11 parole chiav è pr s ntato nel Paragrafo 2.4. Esistono altri nomi, noti al si t ma C, eh non dovrebbero es ere ridefiniti dal programmator . Un mpio è printf; poiché printf è il nom di una funzione d lla libreria tandard, g neralmente e o non viene utilizzato com nome di variabil . Un punto d cimale in un numero indica che esso è una co tante reale e non una o tante intera, quindi i numeri 37 e 37.0 doVrebbero es ere trattati differentemente in un programma. Sebb n sistano tr tipi per i num ri reali, cioè float, double e long,

12

Capitolo 1

kilometers

= 1.609

* (miles + yards 1 1760.0);

d l programma pr ced nt con l'i truzion : kilometers

= 1.609

* (miles

+ yards l 1760);

Qu to provoca un malfunzionam nto d l programma. Infatti, poich la variabil yards ' di tipo int ha valor 3 5, il valor d Il' pr ion yards l 1760

è anch'

o di tipo int , più pr isam n double, qu to malfunzionam nto vi n

1.4

1760.0,ditipo

Utilizzo di #define e #include

All'int mo d l compilatore C vi ' un pr pro or ; l righ di codic che iniziano con il caratt re # rappr ntano direttive al preprocessore. n l fil eh d v r compilato i trovano l righ #define #define

LIMIT PI

100 3.14159

il pr proc or o titui c tutt l occorr nz d ll'identificator LIMIT con 100 tutt l occorr nz dell'identificatore PI con 3. 14159, a cc zion d 11 occorr nz all'int rno di tringh racchiu tra doppi api i d i comm nti. Gli id ntificatori LI MIT PI vengono chjamati costanti simboliche. Una riga #def i ne può apparir in qualunqu punto n l programma ha eff tto olam nt ull righ ucc iv a a.

Panoramica sul C

13

printf( "PI = %f\n " , PI);

#define

C

299792.458

/* velocità della luce in km / sec */

utilizzando C p r rappr ntar la co tante 299792. 458 in un programma di migliaia li righ , ri ult rà più facil modificar il codic n l ca o in cui, in futuro, i fi ici rid finian il valor di . Infatti, p r aggiornar tutto il codic arà uffici nt cambiar la tant n Ila riga eh inizia con #def i ne. In un programma, una riga om #include "my_file.h "

#include ì h una copia d l fil tandard d'int tazion stdio.h ia inclu a n l odic durant mpilazion . In ANSI C, quando i utilizzano l funzioni printf () o scanf (), dor bb r inclu o il fil tandard di int tazion stdio.h. Tal fil conti n l dichialZÌ ni o, più p ificam nt , i prototipi di qu t funzioni (p r un'ul rior dj eu ion di Paragrafo l. 7). campu d ll'Univ r ity of California a anta Cruz domina dall'alto la baia di Monr y ull'Oc ano Pa iii o part d ll'oc ano a nord-ov t d Ila baia. Qu ta part di · ano vi ibil dal campu vi n chiamata n 1 guito "Pacific a" (Mar Pacifico).

n

14

Capitolo 1

Allo scopo di chiarire il funzionamento della direttiva #include, viene presentato un programma che stampa, in diver e unità di misura, l'area del Pacific Sea. Prima di tutto viene creato un file d'intestazione contenente le seguenti righe. Nel file pacific_sea.h: #include #define #define #define #define #define

AREA Sa MILES PER Sa KILOMETER sa- FEET PER sa MILE sa- INCHES PER sa FOOT ACRES_PER=Sa_MILE

2337 0.3861021585424458 (5280 * 5280) 144 640

In un file .c viene poi inserita la funzione main () . Nel file pacific_sea.c: / * Calcolo della dimensione del Mare Pacifico. * /

#include "pacific_sea.h " int main(void) {

const int double

pacific_sea = AREA; / * in chilometri quadrati */ acres, sq_miles, sq_feet, sq_inches;

printf( "\nThe Pacific Sea covers an area "); printf( " of %d square kilometers.\n", pacific_sea); sq_miles = Sa_MILES_PER_Sa_KILOMETER * pacific_sea; sq_feet = sa_FEET_PER_Sa_MILE * sq_miles; sq_inches = SQ_INCHES_PER_SQ_FOOT * sq_feet; acres = ACRES_PER_SQ_MILE * sq_miles; printf( "In other units of measure this is: \ n\ n"); printf( "%22.7e acres\n ", acres); printf( %22.7e square miles\n", sq_miles); printf( %22.7e square feet\n", sq_feet); printf( "%22.7e square inches\n\n M, sq_inches); return 0; 11 11

}

Il programma è quindi suddiviso in due file, che hanno rispettivamente le estensioni .h e .c. L'output prodotto è il seguente.

The Pacific Sea covers an area of 2337 square kilometers. In other units of measure this is: 5.7748528e+05 acres 9.0232074e+02 square mi les 2.5155259e+10 square feet 3.6223572e+12 square inches Di seguito sono presentate le idee alla base di questo programma.

Panoramica sul C

15

ANALISI DEL PROGRAMMA pacific_sea •

#include "pacific_sea.h" Que ta riga #include è una direttiva p r il preprocessore. Essa provoca l'in erimento, durante la compilazione, di una copia del file pacijic_sea.h. Tal file contiene a sua volta la riga #include che viene espansa dal preproc ssore includendo una copia d l file d'int stazion n file pacific_sea.h contiene poi la definizione di cinque costanti simboliche.

stdio.h, nece ario per l'utilizzo della funzione printf (). •

#define AREA 2337 Questa riga #def in e è una clirettiva per il preprocessore. Essa provoca la o tituzione di tutte le occorrenze dell'identificatore AREA con 2337 nel resto del file. P r convenzione, gli identificatori che devono essere sostituiti dal preprocessore vengono scritti in lettere maiuscole. Se in futuro la mappa del Pacific Sea venisse ridisegnata, ottenendo di conseguenza un'area differente, il programma potrebbe sere aggiornato modificando esclusivamente questa riga.



#define SQ_ MILES_ PER_SQ_KILOMETER 0.3861021585424458 la costante reale 0 . 3861021585424458 rappresenta un fattore di conversione. L'impiego di un nome simbolico per tale co tante rende più leggibile il programma.



#define

SQ_ FEET_ PER_SQ_MILE

(5280 * 5280)

n preprocessore sostituisce le occorrenze della prima sequenza eli caratteri con la seconda. Chiunque sappia che un miglio equivale a 5280 piedi può facilmente comprendere, leggendo il programma, che questa riga di codice è corretta; al posto di ( 5280 * 5280) sar bbe possibile scriv re 27878400; l' ffici nza in esecuzione non viene persa, dato che i compilatori C espandono espressioni co tanti durant la compilazion . Si noti che, sebbene le parente i non siano nece sarie, il loro utilizzo è considerato una buona pratica di programmazione. Per ragioni tecniche (vedi Paragrafo 8.3), le espres ioni simboliche devono esser spes o racchiuse tra parentesi.



const int pacific_sea = AREA; /* in chilometri quadrati */ Al momento della compilazion , il preprocessore sostituisce AREA con 2337, e in seguito il compilatore interpreta questa riga come una dichiarazione dell'identificatore paci f ic_sea. La variabile è dichiarata di tipo in t e inizializzata al valore 2337. la parola chiave const è un qualificatore di tipo introdotto in ANSI C, e significa che la variabile associata può e sere inizializzata, ma ucce ivamente non è più po ibile modificame il valore (vedi Esercizio 18). Su alcuni sistemi ciò significa che la variabile potrebbe essere memorizzata in una ROM (memoria di ola lettura).

16

Capitolo



double acres, sq_miles, sq_feet, sq_inches; Queste variabili sono definite per essere di tipo double. Nel C tradizionale, i tipi per rappresentare valori reali sono float, double e long double; long double non esiste nel C tradizionale. Ognuno di questi tipi viene utilizzato per contenere valori reali; generalmente float può contenere 6 cifre significative, mentre double può contenerne 15. long double può contenere almeno tante cifre significative quante ne contiene double (vedi Paragrafo 3.6).



printf("%22.7e acres\n", acres); Questa istruzione stampa la riga:

1

5.7748528e+05 acres

n numero, scritto in notazione scientifica, è da interpretarsi come 5.7748528 x105• Questo formato di scrittura dei numeri è chiamato anche formato e. La specifica di conversione %e indica al sistema di stampare un'espressione reale utilizzando una spaziatura standard. Un formato del tipo %m.ne, dove m e n sono interi positivi, provoca la stampa di un'espressione reale in un formato e che occupa in totale m posizioni, di cui n a destra del punto decimale (vedi Paragrafo 11.1).

1.5

Utilizzo di printf() e scanf()

La funzione printf () è utilizzata per l'output e, in maniera analoga, la funzione scanf () per l'input; la lettera f alla fine dei nomi delle due funzioni sta per "formatted". Tecnicamente queste funzioni non fanno parte del linguaggio C, ma piuttosto appartengono al sistema C; esse si trovano in una libreria e sono disponibili per l'utilizzo ovunque vi sia un sistema C. Sebbene il codic oggetto per le funzioni di libreria sia fornito dal sistema C, il programmatore deve dichiarar le funzioni che intende utilizzare. ANSI C ha introdotto un tipo nuovo e migliorato di dichiarazione di funzione, chiamato prototipo di funzione: si tratta di una delle modifiche più importanti introdotte nel linguaggio dallo standard ANSI. I prototipi delle funzioni di libreria sono disponibili nei file d'intestazione standard; in particolare, i prototipi di printf () e scanf () si trovano in stdio.h, quindi tale file d'intestazione dovrebbe e sere incluso ogniqualvolta vengano utilizzate le funzioni printf () e scanf ();per un'ulteriore discussione riguardo ai prototipi di funzione vedi Paragrafo l. 7. Sia printf () che scanf () ricevono un elenco di parametri che può essere pen ato come

stringa di controllo

e

altri parametri

Panoramica sul C

17

dove stringa di controllo è una stringa che può contenere delle specifiche di conversione, o formati. Una specifica di conversione inizia con il carattere % e termina con un carattere di conversione; per esempio, nel formato %d la lettera d è il carattere di conversione. Come già osservato, questo formato è utilizzato per stampare il valore di un'espresione come un intero in notazione decimale. Per stampare sullo schermo le lettere abc è possibile utilizzare l'istruzione printf("abc"); oppure l'istruzione: printf("%s", "abc");

n formato %s indica che il parametro "ab c" deve essere stampato nel formato delle stringhe. Lo stesso risultato può essere ottenuto con l'istruzione: printf ( "%c%c%c", 'a', 'b', 'c'); Gli apici singoli vengono utilizzati per individuare costanti formate da singoli caratteri. Per esempio, ' a ' è la costante carattere che corrisponde alla lettera a minuscola. n formato %c stampa il valore di un'espressione come carattere; si noti che anche le costanti da sole sono considerate espressioni. printf ()

c

carattere intero in notazione decimale numero reale in notazione cientifica numero reale il più breve tra il formato e e il formato f tringa

d

e f

g

s

La posizione in cui viene stampato un parametro è chiamata campo, e il numero di caratteri del campo è chiamato ampiezza del campo; tale ampiezza può essere specificata nel formato da un intero posto tra il carattere %e il carattere di conversione. L'istruzione printf( "%c%3c%5c\n", 'A', 'B', 'C ' ); tampa quindi: A

B

C

18

Capitolo 1

La funzion scanf () è analoga alla funzion printf (),ma vien utilizzata per la l ttura inv c che per la crittura. n uo primo parametro ' una tringa di controllo cont n nt formati che corri pondono ai vari modi con cui i caratt ri in input po sono s ere interpr tati. Gli altri param tri ono indirizzi. Si con id ri, per mpio, l'i truzion

scanf( "%d", &x);

Il formato %d vi n a ociato all' pr ion &x, fac ndo ì che scanf () int rpr ti i caratteri in input com un in t ro in notazione d cimai n m morizzi il valore all'indirizzo di x. Poiché il imbolo & ' l'operator di indirizzo, l' pr ion &x dev l tta com "l'indirizzo di x". i valori di input v ngono fomiti a un programma m diante la ta tiera, il programma riceve la s qu nza di caratteri eh vengono digitati, d tta anch input str am. Digitando 1337 si potrebbe p nsar a un int ro in notazione d cimal , ma il programma ric v tale equenza com equ nza di caratt ri. La funzion scanf () può sere utilizzata per convertire una tringa di cifr d cimali in un valor int ro memorizzarlo in un'appropriata locazion di m moria. La funzione scanf () restituisce come valor di tipo int il num rodi conv r ioni op rate con ucces o o un valor end-of-valu d finito a livello d l istema. La funzione printf () restitui c come int il num rodi caratteri tampati o, in ca o di errore, un valor negativo. scanf()

c d f

lf o LF

s I dettagli riguardanti printf () scanf () ono presentati nei Paragrafi 11.1 e 11.2. Le informazioni pr c d nt m nt fomit risultano sufficienti per far si che i programmi comunichino con l' t mo in mani ra accettabile. n s guente programma legge tre caratt ri e tr numeri, poi li tampa. i osservi che i caratteri vengono m morizzati in variabili di tipo char.

Nel file echo c: #include int main(void) {

Panoramica sul C

char in t float double

19

c1, c2, c3; i;

x; y;

printf ( "\n%s \ n%s " , "Input t h ree characters, " "an int, a float, and a double: ") ; scanf("%c%c%c%d%f%lf", &c1, &c2, &c3, &i, &x, &y); printf("\nHere is the data that you typed in:\n"); printf("%3c%3c%3c%5d%17e%17e\n\n", c1, c2, c3, i, x, y); return 0; }

mpilando il programma, gu ndolo e forn ndo com input ABC 3 55 77. 7, ullo h rmo appar quanto gue: Input three characters, an int, a float, and a double: ABC 3 55 77.7 Here is the data that you typed in: A B C 3 5.500000e+01 7.770000e+01 gg ndo d i num ri, scanf () alta gli pazi bianchi ( paziatur , newlin tabulazioni), ma leggendo dei caratteri gli pazi bianchi non vengono ignorati; quindi il programma non può funzionar corr ttam n t con l'input AB C 3 55 77. 7. Infatti il t rzo aratt r letto ' uno pazio, eh quindi non provoca probl mi, ma il caratter ucc iv · C, che dovr bb r l tto da scanf ( ) come int ro d cimai , co a eh cau a probl mi.

1.6

Flusso del controllo

i truzioni di un programma v ngono normalm nt guit in qu nza, ma la mag.or parte dei programmi richi d una modifica del n ormai flu o qu nziale d l controllo. Le i truzioni if if - else p rm ttono di c gli re tra azioni alt mativ, i truzioni while for forni cono m ccani mi di it razione. Di olito qu ti co trutti richi dono la valutazion di pr ioni logich , cio ' spr ioni eh po ono a umeer rappr ntato da ogni valor r valor true (v ro) o false (falso). In C, true può non nullo, mentr false dal solo valor z ro. La forma g nerale di un'istruzione i f · la guent : if

(expr) statement

expr ha valore diver o da zero (true), statement vi n eseguita, altrim n ti no. È important oss rvar eh un'istruzion i f, bb n con t nga un'altra istruzion , · di p r un'i truzion ingoia.

20

Capitolo 1

Si consideri il seguente codice: a= 1; if (b == 3)

a = 5;

printf ("%d", a);

I simboli== rappresentano l'operatore "è uguale a". Viene effettuato un test per controllare che il valore di b sia uguale a 3; in caso positivo, alla variabile a viene assegnato il valore 5 e il controllo passa quindi all'istruzione printf () che stampa 5. D'altra parte, se il valore di b è diverso da 3 l'istruzione

a

= 5;

non viene eseguita e il controllo passa direttamente a printf (), che in questo caso stampa 1. In C, le espressioni logiche possono assumere uno dei due valori di tipo int O oppure l. Si consideri l'espressione logica: b == 3

Se b ha valore 3 questa espressione ha valore int l (true), altrimenti essa ha valore int

O (false). Un gruppo di istruzioni racchiuso tra parentesi graffe forma un'istruzione composta; sintatticamente un'istruzione composta è a sua volta un'istruzione, e può essere usata ovunque sia possibile utilizzare un'istruzione. Nel prossimo esempio, per controllare più azioni viene utilizzata un'istruzione composta al posto di un'istruzione semplice: if (a b

--

3) {

5;

c = 7•

'

}

In questo caso, se a contiene il valore 3 vengono eseguite due istruzioni di assegnamento, in caso contrario esse vengono saltate. Un'istruzione i f- else ha la seguente forma:

(expr) statementl else statement2

if

È importante osservare che l'intero costrutto, sebbene contenga due istruzioni, è esso stesso una singola istruzione. Se expr è diversa da zero (true) viene eseguita statementl, altrimenti viene eseguita statement2. Come esempio, si consideri il seguente codice: if (cnt

a

==

= 2;

0) {

Panoramica sul C

b

c

21

3;

= 5;

}

else {

a b

c

=

-1; -2; -3;

}

printf("%d", a+ b +c); l caso in cui cnt abbia valore O viene stampato 10, altrimenti viene stampato -6. I meccanismi iterativi sono fondamentali in quanto permettono azioni ripetitive; m diante il seguente programma viene illustrato l'utilizzo del ciclo while:

Nel file consecutive_sums.c: #include in t mai n (v o id) {

int

i= 1, sum

0;

while (i i; --j) if (score[j-1] < score[j]) { /*controlla l'ordine*/ tmp = score[j-1]; score[j-1] = score[j]; score[j] = tmp; }

printf("\nOrdered scores:\n\n"); for (i = 0; i < CLASS_SIZE; ++i) printf(" score[%d] =%5d\n", i, score[i]); printf("\n%18d%s\n%18.1f%s\n\n", sum, " is the sum of all the scores", (double) sum l CLASS_SIZE, " is the class average"); return 0; }

Eseguendo il programma e inserendo al prompt i punteggi 63, 88, 97, 53, 77, sullo schermo appaiono le seguenti righe:

Input 5 scores:

63

88

97

53

77

Ordered scores: score[0] score[1] score[2] score[3] = score[4] =

97 88 77 63 53 378 is the sum of all the scores 75.6 is the class average

n programma ordina i punteggi mediante un algoritmo detto bubblesort; esso si basa sull'utilizzo di due cicli for innestati, ove nel corpo del ciclo interno viene eseguito un test che controlla l'ordine di una coppia di elementi e, se necessario, ne scambia i valori. Lo scambio viene effettuato mediante le seguenti istruzioni.

34

Capitolo 1

tmp = score[j-1]; score[j-1] = score[j]; score[j) = tmp; La variabile tmp della prima istruzione viene utilizzata per memorizzare temporaneamente il valore di sco re [ j -1 1. Nell'istruzione successiva, il valore di sco re [ j -1 1 presente in memoria viene sostituito con il valore di sco re [ j]. Infine, nell'ultima istruzione, il valore di sco re [ j 1 viene sostituito con il valore originale di sco re [ j -1 ] , contenuto in tmp. n lettore può comprendere, simulando manualmente il programma sui dati di esempio, come il bubblesort possa ottenere, con questi due cicli for innestati, un vettore ordinato. n nome "bubblesort" (ordinamento a bolle) è dovuto al fatto che a ogni passo del ciclo esterno il valore più piccolo tra quelli ancora da ordinare viene portato nella posizione finale, così come le bolle più leggere in un liquido risalgono prima verso l'alto. Seppur facile da scrivere, bubblesort non è molto efficiente; altre tecniche di ordinamento risultano più veloci. L'efficienza è estremamente importante quando si debbano ordinare spesso grandi quantità di numeri. L'espressione

(double) sum l CLASS_SIZE utilizzata come parametro nell'ultima chiamata di printf () contiene un operatore di cast, o di conversione. suo effetto è quello di convertire il valore in t di sum in double. Poiché la priorità di un operatore di cast è più alta rispetto a quella dell'operatore di divisione, la conversione viene effettuata prima della divisione (vedi Paragrafo 2.9). Quando un double viene diviso da un int si ottiene una cosiddetta "espressione mista", che provoca una conversione automatica. valore di tipo in t viene convertito in double e il risultato dell'operazione è di tipo double. Non utilizzando il cast si sarebbe ottenuta una divisione intera, con perdita dell'eventuale parte decimale, e inoltre il risultato sarebbe stato di tipo int, con conseguente errore di formato nell'esecuzione di printf ().

n

n

Stringhe In C, una stringa è un array di caratteri. In questo paragrafo, oltre all'utilizzo delle stringhe, vengono introdotte le macro getchar () e putchar () definite in stdio.h. Nonostante esistano differenze tecniche (vedi Paragrafo 8. 7), l'uso delle macro è uguale a quello delle funzioni. Le macro getchar () e putchar () sono utilizzate, rispettivamente, per leggere caratteri dalla tastiera e scrivere caratteri sullo schermo. Nel prossimo programma, in un array di caratteri (cioè una stringa) viene memorizzata una riga inserita dall'utente, che viene poi stampata sullo schermo al contrario. Nel programma viene mostrato come in C sia possibile trattare i caratteri come interi di piccola dimensione.

Nel file nice_day.c: l* Buona giornata! *l #include

Panoramica sul C

35

#include #define MAXSTRING

100

int main(void) {

char int

c, name[MAXSTRING]; i, sum = 0;

printf("\nHil What is your name? "); for (i=0; (c=getchar()) l= '\n'; ++i) { name[i] = c; i f ( isalpha (c)) sum += c; }

name [i] = ' \0 ' ; printf("\n%s%s%s\n%s", "Niceto meet you ", name, ".", "Your name spelled backwards is "); for (-- i; i >= 0; - -i) putchar(name[i]); printf("\n%s%d%s\n\n%s\n", "and the letters in your name sum to ", sum, "Have anice day!"); return 0;

. ,

}

Eseguendo il programma e inserendo, quando appare il prompt, il nome Alice B. Carole, si ottengono sullo schermo le seguenti righe: Hil

What is your name?

Alice B. Carole

Nice to meet you Alice B. Carole. Your name spelled backwards is eloraC .B ecilA and the letters in your name sum to 1142. Have a nice day!

ANALISI DEL PROGRAMMA nice_day •

#include #include n file standard di intestazione stdio.h contiene il prototipo della funzione printf (). Esso contiene inoltre le definizioni delle macro getchar () e putchar (),utilizzate nel programma, rispettivamente, per leggere caratteri dalla tastiera e scrivere caratteri sullo schermo. nfile standard di intestazione ctype.h contiene la definizione della macro isalpha ( ) , utilizzata per determinare se un carattere sia alfabetico, cioè se sia una lettera maiuscola o minuscola.

36

Capitolo 1



#define MAXSTRING 100 La costante simboHca MAXSTRING viene utiHzzata per fissare la dimensione del vettore name. n programma è stato scritto supponendo che l'utente non in erisca più di 99 caratteri. Perché 99 caratteri? Perché il sistema aggiungerà ' \0\ ' come carattere marcatore che termina la stringa.



char c, name[MAXSTRINGl; int i, sum = 0; La variabile c è di tipo char. L'identificatore name è di tipo "array di char" e ha dimensione MAXSTR ING. In C gli indici degli array cominciano da O, dunque gli elementi di questo array sono name [ 0 l , name [ 1 l , ... , n a me [ MAXSTRING - 1 l. Le variabiH i e sum sono di tipo int; sum è inizializzata a O.



printf("\nHil What is your name? "); Questo messaggio chiede all'utente di inserire i dati: il programma attende l'inserimento eli un nome seguito dal carattere newline (Invio o Retum sulla tastiera).



(c= getchar()) l= '\n' Questa espressione è formata da due parti. A sinistra appare: (c = getchar () )

A differenza di altri linguaggi, in C l'assegnamento è un operatore (vedi Paragrafo 2.11). In questo caso, getchar() viene utilizzata per leggere un carattere dalla tastiera e per assegnarlo a c. valore complessivo dell'espressione corrisponde a qualsiasi valore assegnato a c. Le parentesi sono necessarie in quanto la priorità dell'operatore =è minore di quella dell'operatore l=. Dunque

n

c= getchar() l= '\n' è equivalente a c= (getchar() != '\n') che è sintatticamente corretto, ma differente da quanto desiderato. Nel Paragrafo 2.9 verranno analizzati approfonditamente i concetti di precedenza e associativà degH operatori. •

for (i = 0; (c = getchar()) l= '\n'; ++i) { name[i] = c; if (isalpha(c)) sum += c; }

Alla variabile i viene assegnato inizialmente il valore O. Quindi getchar () riceve un carattere dalla tastiera, lo assegna a c e controlla se si tratta eli un carattere newline; in caso negativo l'esecuzione prosegue con il corpo del ciclo for.

Panoramica sul C

37

Per prima cosa, il valore di c viene as egnato all'elemento name [i 1 dell'array; poi, utilizzando la macro isalpha (),viene determinato se c sia una lettera (minuscola o maiuscola): in caso affermativo sum viene incrementata del valore di c. Come mostrato nel Paragrafo 3.3, in C un carattere ha un valore int ro corrispondente alla sua codifica ASCII. Per esempio, a ha valore 97, b ha valore 98 e cosi via. Alla fine dell'esecuzione del corpo del ciclo for la variabile i viene incrementata. n ciclo for viene eseguito ripetutamente fino al momento in cui viene ricevuto un carattere newline. l



l

l

l

name [i 1 = \0 Dopo la fine del ciclo for, a name [i] viene assegnato il carattere nullo, denotato con i simboli \0. Per convenzione, ogni stringa termina con un carattere nullo; le funzioni che elaborano stringhe, come printf (),utilizzano il carattere nullo come marcatore, o sentinella, di fine stringa. È possibile pensare alla rappresentazione in memoria dell'array name nel modo illustrato di seguito. l

l

;

l A Il l i l c l e l l B l . l l C l a l r l o Il l e l\ol *l O

l

2

3

4

S

6

7

8

9

10 Il

12 13

14

IS 16

·~ 99



printf("\n%s%s%s\n%s", "Nice to meet you ", name, " . " , "Your name spelled backwards is "); Si osservi che per stampare l'array di caratteri name viene utilizzato il formato %s. Gli elementi dell'array vengono stampati uno dopo l'altro fino a raggiungere il marcatore di fine stringa \0.



for (-- i; i >= 0; -- i) putchar(name[i1); Supponendo che sia stata digitata come input la sequenza di caratteri Alice B. Carole seguita da un carattere newline, il valore della variabile i all'inizio di questo ciclo for è 15 (non si dimentichi che il conteggio avviene da O e non da 1). Dopo che i è stata decrementata, il uo valore corri ponde all'indice dell'ultimo carattere del nome inserito in input Dunque, l'effetto di questo ciclo for è quello di stampare tale nome al contrario sullo schermo.



printf("\n%s%d%s\n\n%s\n", "and the letters in your name sum to " sum, ". " , "Have anice day!"); Viene stampata la somma delle lettere che costituiscono il nome inserito in input, seguita da un messaggio finale. Dopo la stampa della somma delle lettere del nome, viene stampato un punto. Per creare uno spazio bianco prima della stampa del messaggio finale vengono usati due nuovi caratteri newline. Si noti che lo stile di questa printf () consente di visualizzare facilmente ciò che appare sullo schermo.

38

Capitolo 1

Puntatori Un puntatore è l'indirizzo di un oggetto in memoria. Poiché anche i nomi di array sono puntatori, l'utilizzo di array e l'utilizzo di puntatori sono strettamente legati. Nel seguente programma sono illustrate alcune di queste relazioni:

Nel file abc.c: #include #include #define MAXSTRING int main(void)

100

{

char

c= 'a', *p, s[MAXSTRING];

p = &c; printf("%c%c%c ", *p, *p+ 1, *p+ 2); strcpy(s, "ABC"); printf("%s %c%c%s\n", s, *s + 6, *s + 7, s + 1); strcpy(s, "she sells sea shells by the seashore"); p =

s + 14;

for (;*p!= '\0'; ++p) { if (*p== 'e') *p = , E,; if (*p== l ' ) *p= '\n'; }

printf("%s\n", s); return 0; }

L'output del programma è: abc ABC GHBC she sells sea shElls by thE sEashorE

ANALISI DEL PROGRAMMA abc •

#include La libreria standard contiene svariate funzioni per la gestione delle stringhe (vedi Paragrafo 6.11); nel file d'intestazione string.h sono contenuti i prototipi di tali funzioni. In questo programma viene utilizzata strcpy () per copiare una stringa.



char c= 'a', *p, s[MAXSTRING]; La variabile c è di tipo char; essa viene inizializzata con il valore 'a'. La variabile p è di tipo puntatore a char. La stringa s ha dimensione MAXSTRING.

Panoramica sul C



39

P = &c;

n simbolo & è l'operatore di indirizzo; il valore dell'espressione &c è l'indirizzo di memoria della variabile c. L'indirizzo di c viene assegnato a p: si può dunque pensare che p "punti a c".



printf( "%c%c%c

", *p, *p + 1, *p + 2);

nformato %c viene utilizzato per stampare come carattere il valore di un'espressione. n simbolo* costituisce l'operatore di dereferenziazione, o indirizzamento indiretto. L'espressione *p assume il valore di ciò a cui punta p; poiché p punta a c e c ha valore a a è il valore dell'espressione *p; dunque viene stampato il carattere a. L'espressione *p + 1 ha come valore uno più il valore dell'espressione *p; perciò viene stampato il carattere b. Infine, l'espressione *p + 2 ha come valore 2 più il valore dell'espressione *p; perciò viene stampato il carattere c. l



M

l,

l

l

ABC"

Una costante stringa viene memorizzata sotto forma di vettore di caratteri, l'ultimo dei quali è il carattere nullo \0. Pertanto la dimensione della costante stringa "ABC" è 4 e non 3. Anche la stringa vuota " " contiene un carattere, cioè \0. È importante comprendere che le costanti stringa sono di tipo "array di char". Un nome di array a sé stante viene trattato come un puntatore, e ciò vale anche nel caso di una costante di tipo stringa. •

strcpy(5, "ABC"); La funzione strcpy () riceve due parametri, entrambi di tipo puntatore a char, che possono essere visti come stringhe. La stringa a cui punta il secondo parametro viene copiata in memoria a partire dalla locazione a cui punta il primo parametro. Tutti i caratteri fino al carattere nullo compreso vengono ricopiati, con il risultato di copiare una stringa in un'altra. Il programmatore deve far sì che vi sia spazio sufficiente, a partire dalla locazione a cui punta il primo parametro, per memorizzare tutti i caratteri che devono essere copiati. La memoria utilizzata da s può essere schematizzata, dopo l'esecuzione di questa istruzione, come illustrato nella figura seguente.

l A l B l C l\ol * l · · · ~ 01234



printf("%5

99

%c%c%5\n", 5, *5 + 6, *5 + 7, s + 1);

n nome del vettore 5, da solo, è un puntatore. Si può pensare che 5 punti a 5 [ 0]

o, in altre parole, che s sia l'indirizzo base dell'array, cioè l'indirizzo di 5 [ 0] . Stampando 5 come stringa viene stampato ABC. L'espressione *5 ha il valore di ciò a cui punta 5, cioè di 5 [ 0], quindi il valore di questa espressione è A. Poiché la sesta lettera successiva ad A è G e la settima è H, le espressioni *5 + 6 e *5 + 7 stampate con il formato di un carattere produrranno in output rispettivamente i caratteri G e H. L'espressione s + 1 è un esempio di aritmetica dei puntatori; il suo valore è un puntatore che punta a 5 [ 1 ] , il carattere successivo nell'array. Dunque la stampa sotto forma di stringa di s + 1 produce in output BC.

40

Capitolo 1



strcpy ( s, "she sells se a shells by the seashore"); Una nuova stringa viene copiata in s; il valore precedentemente memorizzato in s viene sovrascritto.



p = s + 14; Viene assegnato a p il valore del puntatore s + 14. Un'istruzione equivalente è: p = &s[ 14]; Contando accuratamente si può osservare che ora p punta alla prima lettera della parola "shells". È opportuno notare che sebbene s sia un puntatore esso non è una variabile, ma un puntatore costante. Un'istruzione come

P

=

s;

è consentita in quanto p è una variabile puntatore, mentre l'istruzione

s

= p;

provocherebbe un errore sintattico. Nonostante il valore a cui spunta possa essere moclificato, non è possibile modificare il valore di s stesso. •

for (;*p l= '\0'; ++p) { if (*p== 'e') *p = , E,; i f (*p == l l) *p= '\n'; }

n corpo del ciclo for viene eseguito fintanto che il valore a cui punta p è diverso dal carattere nullo. Se il valore a cui punta p è uguale a ' e ' , allora il valore in memoria viene modificato in E se il valore a cui punta p è uguale allo spazio ' ' , allora il valore in memoria è sostituito con ' \n ' . Alla fine del corpo del ciclo f or, la variabile p viene incrementata; in questo modo p va a puntare al carattere successivo della stringa. l



l ;

printf("%s\n", s);

La variabile s viene stampata con il formato di stringa, seguita da un carattere newline. Poiché il ciclo for precedente ha modificato i valori di alcuni elementi dell'array, l'output prodotto è il seguente. she sells sea shElls by

thE sEashorE

Panoramica sul C

41

In C, array, stringhe e puntatori sono strettamente legati. Si consideri per esempio la dichiarazione: char

*p, s[1001;

E sa crea l'identificatore p di tipo puntatore a char e l'identificatore s di tipo vettore di 100 elementi di tipo char. Poiché il nome di un array è un puntatore, sia p che s risultano puntatori a char. Tuttavia, mentre p è una variabile, s è una costante che punta a s [ 01. Si osservi che l'espressione ++p può essere utilizzata per incrementare p, ma d'altra parte, essendo s una costante, l'espressione ++s è scorretta: il valore di s non può essere modificato. È fondamentale il fatto che le due espressioni e

s[i1

*(S +

i)

iano equivalenti. L'espressione s [i 1 ha come valore l'i-esimo (partendo da O) elemento dell'array, mentre * ( s + i) è l'indirizzamento indiretto di s + i, espressione con puntatori che punta i caratteri dopo s. Analogamente, le due espressioni *(p + i)

e

p[i1

risultano equivalenti.

1.9

File

L'utilizzo dei file in C è semplice; il seguente codice può essere utilizzato per aprire un file di nome my_file:

Nel file read_it.c: #include int main(void) {



in t

' *ifp;

FILE

ifp

=

fopen( .. my_file .. , .. r .. );

Nella seconda riga nel corpo di main () viene dichiarato un puntatore a FILE di nome i fp (abbreviazione di "infile pointer"). La funzione fopen () si trova nella libreria standard, e il suo prototipo in stdio.h. n tipo FILE è definito, come struttura particolare, in stdio.h. Questo costrutto può essere utilizzato senza conoscerne i dettagli; tuttavia, prima di qualunque riferimento a FILE è necessario avere incluso il file d'intestazione mediante la direttiva #include. La funzione fopen () riceve come parametri due strin-

42

Capitolo 1

ghe e restituisce un puntatore a FILE; il primo argomento è il nome del file, il secondo indica la modalità di apertura del file stesso.

"r" "w" "a"

per la lettura (read) per la scrittura (write) per l'aggiunta (append)

L'apertura per la scrittura di un file non esistente determina la creazione di un nuovo file, mentr se tale file è esistente il suo contenuto viene distrutto e la scrittura ha luogo a partire dall'inizio del file stesso. Se per qualche ragione il file non risulta accessibile, fopen () restituisce il puntatore NULL. Un file, dopo essere stato aperto, può essere individuato per mezzo del relativo puntatore. Al termine dell'esecuzione di un programma il sistema C chiude automaticamente tutti i file aperq; ciascun sistema C prevede un limite, generalmente di 20 o 64, al numero di file aperti simultaneamente. Nel caso vengano utilizzati parecchi file, il programmatore dovrebbe chiudere esplicitamente tutti i file non correntemente in uso: a tale scopo è disponibile la funzione di libreria fclose (). Esaminiamo ora l'utilizzo dei file. Dato un testo, è facile analizzare il numero di occorrenze delle lettere e delle parole che lo compongono; analisi di questo tipo si sono dimostrate utili in svariate discipline, dallo studio dei geroglifici a quello di Shakespeare. Per semplicità, il programma presentato nel seguito si limita a contare le occorrenze delle lettere maiuscole. Per esemplificare l'impiego del programma viene utilizzato un file di nome chapterl, contenente la versione inglese di questo capitolo. Il programma, di nome cnt_letters, effettua l'analisi di tale testo utilizzando file in lettura e scrittura. L'operazione ha inizio fornendo il comando

cnt_letters chapter1 data1 dove chapter l e data l sono i parametri per il programma. La presentazione è preceduta da una breve discussione sull'accesso dall'interno di un programma ai parametri forniti su una riga di comando. n linguaggio C fornisce un collegamento con i parametri presenti sulla riga nella quale viene impartito il comando; tale collegamento può essere utilizzato mediante il seguente codice: #include int main(int argc, char *argv[]) {

Fino a questo punto la funzione mai n ( ) è stata utilizzata senza parametri; in realtà essa può assumere un numero variabile di parametri. n parametro argc ("argument count'') assume come valore il numero di argomenti presenti sulla riga con la quale è stato

Panoramica sul C

43

mpartito il comando di esecuzione del programma. Il parametro argv ("argument va'ahi ") è un array di puntatori a char, che può essere pensato come un array di strinh ; i suoi elementi puntano, secondo l'ordine, alle parole presenti sulla riga di comand impiegata per eseguire il programma, pertanto argv [ 01 è un puntatore al nome t o del comando. Come esempio di utilizzo di questa possibilità, si supponga di avecritto il programma e di avere posto il relativo codice eseguibile nel file cnt_letters. Lo scopo della riga di comando

cnt_letters chapter1 data1 quello di richiamare il programma cnt_letters con i due nomi di file chapter l e data l m argomenti. Il programma dovrebbe leggere il file chapterl e scrivere nel file datal. • rnendo un comando differente, come

cnt_ letters chapter2 data2 il programma dovrebbe leggere il file chapter2 e scrivere nel file data2. Le tre parole pr senti sulla riga di comando risultano accessibili al programma mediante i tre puntal ri argv[0], argv[1] e argv[21. Di seguito è riportato il testo del programma.

Nel file cnt_letters.c: /* Conta le lettere maiuscole in un file. */ #include #include int main(int argc, char *argv[]) {

int FILE

c, i, letter[26]; *ifp, *ofp;

if (argc l= 3) { printf("\n%s%s%s\n\n%s\n%s\n\n", "Usage: ", argv[01, " infile outfile " , "The uppercase letters in infile will be counted. " , "The results will be written in outfile."); exit(1); }

ifp = fopen(argv[11, "r"); ofp = fopen ( argv [ 21 , "w"); for (i= 0; i< 26; ++i)

/* inizializza l'array a zero */

letter[i1 = 0; while ((c= getc(ifp)) != EOF) if (c>= 'A' && c= 0. 0) printf("\n%15s%22.15e\n%15s%22.15e\n%15s%22.15e\n\n•, "x = •, x, •sqrt(x) = •, sqrt(x), "pow ( x , x ) = .. , pow ( x , x ) ) ; else { printf("\nSorry, your number must be nonnegative.\n"); break; } }

printf("\nByel\n\n"); return 0; }

Eseguendo il programma e inserendo 2 al prompt, sullo schermo appaiono le seguenti righe:

The following will be computed: the square root of x x raised to the power x Input x:

2 x

sqrt(x) pow(x, x)

2.000000000000000e+00 1.414213562373095e+00 4.0000000000000009+00

Input x:

ANALISI DEL PROGRAMMA sqrt_pow •

#include #include Questi file d'intestazione contengono prototipi di funzione. In particolare, math.h contiene i prototipi delle funzioni della libreria matematica. Nonostante non sia consigliato farlo, anziché includere math.h è possibile inserire direttamente i due prototipi: double

sqrt(double), pow(double, double);

114

Capitolo 3

Questa dichiarazione dovrebbe e ere posta nel file subito prima di ma in (); alcuni compilatori segnalano un problema se il prototipo viene posto nel corpo t o dimain(). •

whi1e ( 1) { Poiché ogni valore diverso da O è considerato vero, l'espressione 1 cr a un ciclo whi1e infinito. Useremo un'istruzione break per u eire dal ciclo.



scanf( "%1f",&x) viene utilizzato n lla stringa di controllo in quanto x un doub1e. Un errore frequente è quello di utilizzar %f al po to di %1 f. Si o servi che nell'esempio di e ecuzione presentato sopra · tato in rito 2, ma arebb tato possibile inserire 2. 0, 2e0 oppure 0. 2e1 ott n ndo lo tesso risultato. La chiamata di funzione scanf ( "%1 f", &x) avrebbe infatti convertito ognuna di queste equenze di caratteri nello stesso valore doub1e. Nel codice org nte C, 2 2. 0 ono differenti: il primo è un int e il secondo è un doub1e. La sequenza di input letta da scanf () non è codice sorgente, dunque non valgono le regole a sso relativ . Quando scanf () legge un doub1e, il numero 2 è equivalente a 2. 0 (v di Paragrafo 11.2).



if (scanf( "%1f", &x) l= 1) break; La funzione scanf () restituisce il numero di conversioni riu cite. P r u cir dal ciclo whi1e si può digitare "quit" o qualsiasi altra stringa che scanf () non può convertire in un doub1e. Se il valore restituito da scanf () non è l, vien e guito il comando break, che causa l'uscita dal ciclo whi1e. Di cuteremo l'i truzion break in modo approfondito nel Capitolo 4.



if (X >= 0.0)

n formato %1 f

La radice quadrata è definita solo per valori non negativi, e lo scopo di questo test consiste quindi nel verificare che x non contenga un valore negativo. Una chiamata come sq rt ( -1 . 0) provoca un errore di esecuzione (vedi Esercizio 20). •

printf("\n%15s%22.15e\n%15s%22.15e\n%15s%22.15e\n\n", "x= ", x, "sqrt(x) = ", sqrt(x), "pow (x , x) = " , pow ( x , x) ) ; Vengono stampati valori doub1e utilizzando il formato %22. 15e. Si hanno così l po to a sinistra e 15 posti a destra del punto decimale, per un totale di 16 cifre ignificative. Sulla macchina di cui disponiamo solo n posizioni ono corrette, dove n ' un numero tra 15 e 16 Oa frazione dipende dalla conversione da binario a decimai ) . Sebbene sia possibile, è inutile chiedere un numero maggiore di cifre in quanto e non avranno alcun significato.

Tipi di dati fondamentali 115

Utilizzo di abs() e fabs() In molti linguaggi la funzione abs ( ) re tituisce il valor a oluto del uo param tro reale. Sotto questo aspetto il C differente: in C infatti la funzione abs ( ) ricev un parametro di tipo int e restituisce un risultato di tipo int. n suo prototipo i trova in stdlib.h. Per produrre codice mat matico il programmator C dovr bb utilizzare fabs (), che riceve un argomento di tipo double e restituisce un ri ultato di tipo double (v di E erdzio 25). n prototipo di que ta funzione i trova in math.h; il no m f abs ta per "floating absolute value" (valore as oluto reale). l

UNIX e la libreria matematica In ANSI C la libreria matematica fa logicamente parte di quella tandard. Ciò ignifica che non è neces aria alcuna operazione particolar per ace der alle funzioni matematiche. Tuttavia, utilizzando vecchie ver ioni del istema UNIX, pes o ciò non avviene. Si supponga di scrivere un programma che utilizzi la funzione sqrt () in un fil di nome pgm.c. Il programma dovrebbe essere compilato utilizzando il seguente comando:

cc pgm.c Se il sistema non

l

configurato propriamente è possibile ottenere un messaggio come:

Undefined symbol: _sqrt

n significato di questo messaggio è che illoader, esaminando le librerie a sua dispo izione, non è riuscito a trovare un file .o contenent il codice oggetto di sqrt (); per un'ulteriore discu sione ulle librerie v di Paragrafo 11.15. In rendo il comando cc pgm.c

-lm

viene caricata anche la libreria matematica, all'int mo d Ila qual il loader può trovare il file .o nec s ario. In -lm la lettera l sta per "libreria" e la lettera m sta per "matematica". Nelle nuove ver ioni di UNIX la nece sità dell'opzione -lm è scomparsa.

3.11

Conversioni e cast

Un'espressione aritmetica come x + y ha un valore e un tipo. Se sia x che y ono di tipo int anche l'espressione x + y è di tipo int, ma se x e y sono di tipo short, x + y rimane di tipo int, e non short; questo è dovuto al fatto che nelle espressioni i valori short vengono convertiti in int. In questo paragrafo vengono presentate con precisione le regole di conversione.

116

Capitolo 3

Promozioni a intero Un char o short, sia signed che unsigned, o un tipo enumerativo (vedi Paragrafo 7.5) possono essere usati in qualunque espressione nella quale possano essere utilizzati in t 0 unsigned int. Se tutti i valori del tipo originale possono essere rappresentati come int il valore è convertito in int, altrimenti è convertito in unsigned int: questa conver~ione è chiamata promozione a intero. Un esempio è il seguente:

char c = l Al; p r i nt f ( %c \ n " , c ) ; N

La variabile c di tipo char è utilizzata come parametro di printf ();tuttavia, essendovi

una promozione a intero, il tipo dell'espressione c è int anziché char.

Conversioni aritmetiche usuali Quando gli operandi di un operatore binario vengono valutati, possono essere eseguite alcune conversioni aritmetiche. Se, per esempio, i è un in t e f è un f loat, allora l'operando i nell'espressione i + f viene promosso a f loat e l'espressione i + f stessa è di tipo float. Le regole alla base di queste conversioni, illustrate di seguito, sono dette conversioni aritmetiche usuali. Se uno dei due operandi è di tipo long double, l'altro operando viene convertito in long double. Altrimenti, se uno dei due operandi è double, l'altro operando viene convertito in double. Altrimenti, se uno dei due operandi è f loat, l'altro operando viene convertito in f loat. Altrimenti viene applicata la conversione a intero su entrambi gli operandi e vengono applicate le seguenti regole: Se uno dei due operandi è un unsigned long, l'altro operando viene convertito in unsigned long. Altrimenti, se uno dei due operandi è di tipo long e l'altro è di tipo unsigned, si hanno due possibilità: Se il tipo long può rappresentare tutti i valori di un unsigned, l'operando di tipo unsigned viene convertito in long. Se il tipo long non può rappresentare tutti i valori di un unsigned, entrambi gli operandi vengono convertiti in unsigned long. Altrimenti, se uno degli operandi è di tipo long, l'altro operando viene convertito in long. Altrimenti, se uno degli operandi è di tipo unsigned, l'altro operando viene convertito in unsigned. Altrimenti, entrambi gli operandi sono di tipo int.

Tipi di dati fondamentali 117

n processo di conversione è noto con diversi nomi: • • • • •

conversione automatica; conversione implicita; coercizione; promozione; ampliamento.

La seguente tabella illustra l'idea di conversione automatica. Dichiarazioni char c; long l; float f;

int i; unsigned long ul; long double ld;

short s; unsigned u; double d;

Espressione

Tipo

Espressione

Tipo

c - s l i u * 2.0 -

in t double in t double double long

u * 7 -

unsigned float unsigned long long double unsigned long dipendente dal sistema

c

+ 3

c + 5.0 d + s 2 * i l l

i

* 7 - i 7 * s * ul ld + c u - ul

f

u -

l

Le conversioni automatiche, oltre che nelle espressioni miste, possono verificarsi anche eseguendo degli assegnamenti. Per esempio, d

=i

provoca la conversione del valore di i, di tipo int, al tipo double; il valore cosi rappresentato viene assegnato alla variabile d di tipo double, e double diventa il tipo dell'intera espressione. Una promozione o ampliamento come d = i non provoca problemi, mentre una retrocessione o restrizione come i = d può causare una perdita di informazione: in questo caso viene persa la parte decimale di d. Se la parte intera rimanente non rientra in un int, ciò che accade esattamente in ogni caso dipende dal sistema.

Cast Oltre alle conversioni implicite, che possono avvenire durante gli assegnamenti o nelle espressioni miste, sono possibili anche delle conversioni esplicite chiamate cast. Se i è un int, allora (double) i

118 Capitolo 3

converte il valore di i in modo che l'espres ione ia di tipo double. La variabile i tuttavia non viene modificata. I cast possono essere applicati alle espr ssioni; alcuni e empi ono (long) ( A + 1 . 0) = (t lo a t) ( (in t) d + 1 ) d = (double) i l 3 (double) (x = 77) l

l

t

ma non: (double) x

= 77

l* equivalente a ((double) x)

= 77,

errore */

L'operatore di cast (type) è un operatore unario che ha la stessa priorità degli altri operatori unari e la stessa associatività da d stra ver o sinistra. Dunque l'e pressione (float) i + 3 è equivalente a ( (float) i) + 3 in quanto l'operatore di ca t (type) ha priorità più alta rispetto a+.

3.12

Costanti esadecimali e ottali

Un numero rappresentato in base 16 mediante la notazione posizionale è detto numero esadecimale. Esistono 16 cifre esadecimali.

Cifra esadecimale: Valore decimale:

O O

l l

9

A

B

C

D

E

F

9

10

11

12

13

14

15

Un intero positivo scritto in notazione esadecimale è una stringa di cifre esadecimali della forma

h,h,_1 •

••

htz 1h0

dov ogni h; è una cifra esad cimale. Il valore di tale intero è h,

x

P r

1611 + h,_1 x 16,_1 + ... + h 2

x

162 + h 1 x 16 1 + h0

x

16°

empio:

AOF3C = A x 164 +O x 163 + F x 162 + 3 x 16 1 +C x 16° "" l o x 164 + o x 163 + 15 x 162 + 3 x 161 + 12 x 16° - 659260

Tipi di dati fondamentali

119

Nella eguent tab Ila ono riportati alcuni num ri e ad cimali con gli quival nti decimali.

~---·--~~~~~~~~~ 2A

B3 113

o

2 x 16 +A • 2 x 16 + 10 • 42 B x 162+ 3 • 11 x 16 + 3 • 179 l )( 16 + l )( 16 + 3 - 275

2 3

00000000 00000001 00000010 00000011

00 01 02 03

000 001 002 003

31 32

00011111 00100000

1F 20

037 040

188

10111100

BC

274

254 255

11111110 11111111

FE FF

376 377

l

È utile considerare un'altra int rpretazione di questa corrispond nza. Un nibble è definito come una quenza di quattro bit; un byt ' quindi costituito da due nibble. Ogni nibble ha un'unica rappresentazione come ingoia cifra e ad cimai 2 nibble, cioè un byte, ono rappresentabili con due cifre e adecimali. Per e empio, 1011 1100

corrisponde a

BC

Si osservi la stes a corri pondenza anche nella tabella; queste osservazioni risultano utili durante la manipolaz·ion delle variabili a livello di bit. Le cifre ottali sono O, l, 2, . . . , 7. Un intero positivo scritto in notazione ottale è una stringa di cifre della forma

120 Capitolo 3

dove ogni o; è una cifra ottale.

o,.

x

n valore di tale intero è:

. . .

+

o2 x 82 + o1 x gt

+

83 + 3

x

g2 +Q

gO

g" + o,._1 x g"-1+

o0 x g0

Per esempio, 75301 ""7

x

g4 + 5

x

x

gl

+

l

x

Su macchine con parole di 24 o 48 bit risulta naturale organizzare le parole in "byte" di 6 bit, ognuno dei quali è costituito da 2 '4 nibble" di 3 bit. In questo caso un "nibble" ha un'unica rappresentazione come singola cifra ottale e un "byte", a sua volta, ha un'unica rappresentazione mediante due cifre ottali Nel codice sorgente C, le costanti intere positive precedute da uno 0 rappresentano interi in notazione ottale; le costanti intere positive precedute da 0x o da 0X rappresentano interi in notazione esadecimale. Come le costanti in notazione decimale, anche le costanti ottati ed esadecimali possono avere dei suffissi che ne specificano il tipo. Le lettere da A a F e da a a f vengono usate per codificare le cifre esadecimali.

hexadecimal_integer_constant :: = h_integer_constant { i_suffix h_integer_constant : : = { 0x l 0X }1 { hexadecimal_digit }1• hexadecimal_digit : : = 0 l 1 l . . . l 9 l a l A l . . . l f l F octal_integer_constant : : = o_integer_consta nt { i_suffix l oPI o_integer_constant : : = 0 { octal_digit l t+ octal_digit : : = 0 l 1 l . . . l 7 i_suffix :: = integer_suffix : : = { u l U }opi { l l L }oPI

l opt

Nel programma successivo vengono mostrati i concetti appena discussi. L'output di ogni istruzione printf () è scritto nel relativo commento.

l* Conversioni decimale, esadecimale, ottale. *l #include int main(void) {

printf ("%d %x %o \ n " , 19 , 19 , 19) ; printf ("%d %x %o\n", 0x1c, 0x1c, 0x1c); printf ("%d %x %o \ n " , 01 7 , 01 7 , 01 7 ) ; p r i nt f ( "%d \ n " , 11 + 0x 11 + 011 ) ; printf( "%x\n", 2097151); printf( "%d\n", 0x1 FfFFf); return 0;

l* l* l* l* l* l*

19 13 23 *l 28 1c 34 *l 15 f 17 *l 37 *l 1fffff *l 2097151 *l

}

u macchine con int di 2 byte gli ultimi due formati devono essere sostituiti rispettivam nt con %lx e %ld. Le funzioni printf () e scanf () utilizzano i caratteri di converion d, x o nelle specifiche di conversione per indicare rispettivamente decimale,

Tipi di dati fondamentali 121

esadecimale e ottale. I formati %x e %o

  • > b

    a -

    b < 0

    ' quivalent a:

    (a -

    b) < 0

    u molte macchin , un' pr ione com a < b viene impl m ntata m dian l' pr quival nt a - b < 0. N Ila valutazion di espr ionj r !azionali i applicano l u uali pr ioru aritm tich arbitrari . N lla gu nt tab Ila vi n ili el - e2 d termini i valori dell ioni r }azionali.

    positivo zero negativo

    o o l

    l

    o o

    o l l

    l l

    o

    La tab lla d lla pagina pr ciatività p r la valutazion di pr ioni r }azionali. Du forni cono ri ultati orpr nd nti in quanto non conformi ali eh . In mat mati a,

    3 = 90) printf("Congratulationsl\n•); printf(•vour grade is %d.\n", grade); viene stampato un messaggio di congratulazioni solo quando il valore di grade è maggiore o uguale a 90. La seconda chiamata di printf ()viene sempre eseguita. La sintassi di un'istruzione i f è data da:

    if_statement ::= i f (expr) statement

    142

    Capitolo 4

    Di solito l'espressione in un'istruzione i f è relazionale, di uguaglianza o logica, ma, come mostrato dalla sintassi, sono possibili istruzioni di qualunque dominio. Altri esempi di istruzione i f sono

    if (y l= 0.0) x /= y; if

    (c == l

    l) {

    ++blank_cnt; printf(•found another blank\n"); }

    ma non:

    if

    b == a area = a * a;

    l* omesse le parentesi */

    Le istruzioni composte dovrebbero essere utilizzate opportunamente per raggruppare sequenze di istruzioni sotto il controllo di una singola espressione i f. Il codice seguente è formato da due istruzioni i f:

    if (j < k) min = j; if (j < k) printf(•j is smaller than k\n•); Questo codice può essere reso più efficiente e leggibile utilizzando un'unica istruzione i f con un'istruzione composta come corpo.

    if (j < k) { min = j; printf("j is smaller than k\n•); }

    L'istruzione i f-else è strettamente imparentata con l'istruzione i f. La sua forma generale è:

    if (expr)

    statementl else

    statement2 Se expr è diversa da zero viene eseguita statementl e statement2 viene saltata; se expr è zero viene saltata statementl ed eseguita statement2. In entrambi i casi il controllo passa poi all'istruzione successiva. Si consideri il seguente codice:

    if

    (X < y)

    min else

    min

    = x; = y;

    Flusso del controllo

    143

    Se x < y risulta vera a min viene assegnato il valore di x; se risulta .falsa a min viene assegnato il valore di y. La sintassi è data da:

    if_else_statement

    ::·

    i f (expr) statement else statement

    Un esempio è

    if (c>= 'a' && c i; -- j ) if (a[j - 1] > a[j]) swap(&a[j - 11, &a[j1); }

    In base all'aritmetica dei puntatori, le espre sioni a + i e &a [i] risultano equivalenti; pertanto sarebbe stato possibile scrivere la chiamata a swap ( ) nel modo seguente: swap(a + i, a + j); Viene descritto ora il funzionamento di bubblesort. Si supponga che mai n () contenga le s guenti righe: in t

    a[1

    = {7,

    bubble(a, 8)

    3, 66, 3, - 5, 22, 77, 2};

    j

    Nella s guente tabella sono mostrati gli elementi di a [ 1 dopo ogni iterazione del ciclo più esterno della funzione bubble (): Dati non ordinati: Prima iterazione: Seconda iterazione: T rza iterazione: Quarta iterazione: Quinta iterazione: ta it razion : ttima iterazion :

    7 -77 -77 -77 -77 -77 -77 -77

    22 -5

    3

    66

    3

    -5

    7 -5 -5 -5 -5 -5 -5

    3

    66

    3

    7 2 2 2 2 2

    3

    66

    3

    7

    3

    66

    3

    3 3 3 3

    7

    3

    66

    2 2 22 22 22

    3 3 3

    7 7 7

    22 22 22

    66 66 66

    -77 22 2

    All'inizio della prima iterazione vengono confrontati a [ 61 a [ 71; i loro valori sono ordinati, quindi non vengono scambiati. Vengono quindi confrontati a [51 a [ 6]; e si non sono in ordine, quindi vengono scambiati. Si pa sa poi al confronto di a [ 41 e a [51 , e così via, e gli elementi adiacenti non ordinati v ngono cambiati. L'effetto di questa prima iterazione è quello di far migrare il valore più piccolo d l vettor nella po izione a [ 01. Nella seconda iterazione a [ 0] non viene modificato, vengono confrontati a [ 61 e a [ 7 1, e così via. Dopo la seconda iterazione il secondo val or più piccolo è in a [ 1 1. Poiché ogni iterazione fa migrare alla posizione appropriata il ucces ivo elemento più piccolo, dopo n-l iterazioni dell'algoritmo gli elementi risultano ordinati. Si o servi che in questo esempio gli elementi sono già ordinati dopo 5 iterazioni. È possibile modificare l'algoritmo in modo che esso termini prima, aggiungendo una variabile che controlli che non siano stati effettuati scambi in una data iterazione. Si effettui questa modifica a titolo di esercizio.

    232 Capitolo 6

    n bubblesort è molto inefficiente; per array di lunghezza n il numero di confronti effettuato è proporzionale a n2• L'algoritmo mergesort discusso nel Paragrafo 6.9 risulta più efficiente, utilizzando n log n confronti.

    6.8

    Allocazione dinamica della memoria con calloc() e malloc()

    l1 C fornisce le funzioni calloc ( ) e malloc ( ) nella libreria standard; i prototipi si tro-vano in stdlib.h. n nome calloc sta per "allocazione contigua" (contiguous allocation), mentre il nome malloc sta per "allocazione di memoria" (memory allocation). n programmatore utilizza calloc () e malloc () per creare dinamicamente spazio per array, strutture e unioni (strutture e unioni vengono discusse nei Capitoli 9 e 10). Nel seguente esempio viene mostrato l'utilizzo di calloc () per creare dinamicamente spazio per un array: #include #include int main(void) {

    in t in t

    *a;



    '

    l* da utilizzare come array *l l* lunghezza dell'array */ l* riceve in qualche modo, magari interattivamente dall'utente, il valore di n *l

    a= calloc(n, sizeof(int));

    l* crea spazio per a *l l* utilizza a come array */

    La funzione calloc () riceve due parametri, entrambi di tipo size_t. In ANSI C, size_ t deve essere un tipo intero privo di segno. Di solito, in stdlib.h viene utilizzata una typedef per definire size_ t come equivalente al tipo unsigned in t. Una chiamata di funzione della forma calloc (n, el_size) alloca in memoria spazio contiguo per un array di n elementi, ognuno dei quali di el_size byte. Tutti i bit all'interno di questo spazio vengono posti a zero. Se la chiamata ha successo viene restituito un puntatore di tipo void * che punta alla base dell'array in memoria, altrimenti viene restituito NULL. Si noti che l'uso dell'operatore sizeof rende il codice utilizzabile su macchine con parole di 2 e di 4 byte. L'impiego di malloc ( ) è simile. Questa funzione riceve un unico argomento di tipo size_t. Se la chiamata ha successo viene restituito un puntatore di tipo vo id *che punta allo spazio richiesto in memoria, altrimenti viene restituito NULL.

    Array, puntatori e stringhe 233

    Invece di scrivere a= calloc(n, sizeof(int)); sarebbe stato possibile scrivere: a= malloc(n * sizeof(int)); Contrariamente a calloc (),la funzione malloc () non inizializza lo spazio di memoria da essa reso disponibile. Se non è necessario inizializzare a zero l'array è possibile utilizzare sia calloc () che malloc (); in un programma di grande dimensione malloc () può richiedere meno tempo. Lo spazio allocato dinamicamente mediante calloc () e malloc () non viene restituito al sistema all'uscita dalle funzioni; esso va restituito esplicitamente mediante la funzione free (). Una chiamata della forma free(ptr) provoca la deallocazione dello spazio in memoria a cui punta pt r. Se pt r è NULL la funzione non ha alcun effetto; se ptr non è NULL, esso deve essere l'indirizzo base di uno spazio, precedentemente allocato con una chiamata a calloc ( ) , malloc ( ) o realloc ( ) , che non sia stato ancora deallocato mediante free () o realloc (). Negli altri casi la chiamata provoca un errore il cui effetto dipende dal sistema. Viene ora presentato un breve programma nel quale sono illustrati i concetti esposti in questo paragrafo. Nella prima istruzione printf () del programma viene spiegato all'utente che cosa faccia esattamente il programma. #include #include #include void int void

    fill_array(int *a, int n); sum_array(int *a, int n); wrt_array(int •a, int n);

    int main(void) {

    int

    •a, n;

    srand(time(NULL));

    l* inizializza il generatore di numeri casuali */

    printf ( • \n%s\n", "This program does the following repeatedly:\n" "\n" "

    1 2

    3 4

    create space for an array of size n\n" fill the array with randomly distributed digits\n" print the array and the sum of its element\n" release the space\n");

    234

    Capitolo 6

    for ( ; ; ) { printf("Input n: "); i f ( scanf ( "%d • , &n) l= 1 Il n < 1 ) break; putchar{'\n'); a= calloc(n, sizeof(int)); /* crea spazio per a[] */ fill_array(a, n); wrt_array(a, n); printf("sum = %d\n\n", sum_array(a, n)); free(a); }

    printf("\nByel\n\n"); return 0; }

    void fill_array(int •a, int n) {

    in t

    i•

    '

    for (i = 0; i < n; ++i) a[i] = rand() % 19 - 9; }

    int sum_array(int •a, int n) {

    int

    i, sum = 0;

    for (i = 0; i < n; ++i) sum += a[i]; return sum; }

    void wrt_array(int •a, int n) {

    int

    i;

    printf("a = ["); for (i = 0; i < n; ++i) printf("%d%s", a[i], ((i< n- 1) ? " }

    Spostamento del puntatore Nel caso di array (vettori) interi per un utilizzo matematico, spesso si desidera che gli indici partano da l anziché da O. In un programma breve ciò può essere realizzato mediante il seguente schema: int double

    n; •a;

    a= calloc(n + 1, sizeof(double));

    Array, puntatori e stringhe 235

    Poiché la lunghezza di a supera n di uno, si può ignorare a [ 01 e utilizzare a [ 1 1,... ,a [ n1 come desiderato. Questo schema è certamente accettabile, ma si consideri il seguente schema alternativo. Prima di tutto si scriva:

    a

    =

    calloc(n, sizeof(double));

    A questo punto in memoria è presente quanto segue:

    ~~l-r-1--r-1~~------,.----,,

    Si scriva poi:

    --a;

    l* spostamento del puntatore */

    La rappresentazione di ciò che è presente in memoria ora è quanto segue:

    0 1

    2

    n

    L'area ombreggiata indica memoria non disponibile per il programmatore. Dunque non si dovrebbe accedere ad a [ 0 1, né in lettura né in scrittura, mentre gli elementi a [ 1 1, ... , a [ n 1 risultano ora accessibili al programmatore. Lo spazio può essere deallocato scrivendo free (a + 1). Nel Paragrafol2.6, trattando le matrici matematiche, vengono approfondite le idee presentate in questo paragrafo.

    6.9

    Un esempio: merge e mergesort

    Si considerino due array ordinati d'interi a [ 1 e b [ 1. Esiste un semplice algoritmo per fondere (merge) i due array in un altro array ordinato c [ 1. Per prima cosa vengono confrontati a [ 0 1 e b [ 0 1, e il più piccolo dei due, supponiamo b [ 0 1, viene posto in c [ 0 1. Vengono poi confrontati a [ 0 1 e b [ 1 1, e il più piccolo dei due, supponiamo b [ 1 1, viene posto in c [ 1 1. Vengono quindi confrontati a [ 01 e b [ 21, e il più piccolo dei due, supponiamo a [ 0 1, viene posto in c [ 2 1. Vengono confrontati poi a [ 1 1 e b [ 2 1, e così via. A un certo punto uno dei due array a [ 1 o b [ 1 sarà esaurito: gli elementi rimanenti dell'altro array verranno copiati in c []. La funzione seguente effettua queste operazio-

    236 Capitolo 6

    ni. Insieme a essa viene definito un file d'intestazione che verrà usato in un programma che verifica le nostre funzioni.

    Nel file mergesort.h: #include #include #include void void void

    merge(int a[], int b[], int c[], int m, int n); mergesort(int key[], int n); wrt(int key[], int sz);

    Nel file merge. c: l* Effettua la fusione tra a[] di lunghezza m e b[1 di

    lunghezza n con risultato in c[]. */ #include "mergesort.h" void merge(int a[], int b[], int c[], int m, int n) {

    int

    i

    = 0,

    j

    = 0,

    k

    while (i < m && j < n) if (a[i] < b[j]) c [ k++] = a [i++ 1 ; else c[k++J = b[ j++1; while (i < m) c[k++) = a[i++1; while (j < n) c[k++) = b[j++);

    = 0;

    /* trasferisce la parte restante */

    }

    Si presume che l'array c [ ] contenga spazio sufficiente per memorizzare sia a [ 1 che b [ 1; il programmatore deve assicurarsi che i limiti di c [ 1 non vengono superati. Si osservi che entrambe le ultime due istruzioni while, o solo una di esse, usate per trasferire la parte restante non vengono eseguite in quanto almeno una delle due condizioni i < m e j < n non sarà vera. Successivamente, scriveremo una funzione mergesort () che chiami merge (). Contrariamente al bubblesort, il mergesort è molto efficiente. Nel seguito viene presentata una funzione di nome mergesort (),che opera su di un array key [] la cui lunghezza è una potenza di 2; tale requisito serve a semplificare la presentazione, e nell'Esercizio 16 viene indicato come essa possa essere rimossa. Per comprendere il funzionamento del mergesort, si supponga che key [] contenga i 16 interi seguenti: 4

    3

    1 67 55 8 0

    4

    -5 37 7 4

    2 9 1

    -1

    Array, puntatori e stringhe 237

    L'algoritmo elabora i dati in un certo numero di passaggi. Nella seguente tabella viene mostrato come si desidera che essi siano disposti dopo ciascun passaggio. Dati non ordinati: 4 3 Primo passaggio: 3 4 Secondo passaggio: 1 3 Terzo passaggio: o l Quarto passaggio: -5 -1

    l 67 55 8 o 4 -5 37 l 67 8 55 o 4 -5 37 4 67 o 4 8 55 -5 4 3 4 4 8 55 67 -5 -l o l l 2 3 4 4 4

    7

    4 2 9 l -l 7 2 9 -l l 7 37 -l l 2 9 l 2 4 7 9 37 7 8 9 37 55 67 4

    Si desidera che dopo il primo passaggio sia ordinata ogni coppia di interi successivi, che dopo il secondo passaggio sia ordinato ogni gruppo di quattro interi successivi, dopo il terzo passaggio sia ordinato ogni gruppo di otto interi successivi, e che, infine, dopo il quarto passaggio tutti e 16 gli interi siano ordinati. In ogni fase viene utilizzato merge () per ottenere l'ordine desiderato. Per esempio, dopo il terzo passaggio si ottengono i due seguenti sottovettori, entrambi ordinati.

    ol

    3 4 4 8 55 67

    e

    -5 -1 l 2 4 7 9 37

    Fondendo questi due array viene ottenuto l'array completamente ordinato presentato nell'ultima riga della tabella precedente. Sorprendentemente, il codice che esegue questo compito è abbastanza breve. Inoltre, esso utilizza la potenza dell'aritmetica dei puntatori.

    Nel file mergesorl. c: /* Mergesort: utilizza merge() per ordinare un array di

    lunghezza n. */ #include •mergesort.h• void mergesort(int key[], int n) {

    int

    j, k, m, •w;

    for (m= 1; m< n; m*= 2)

    l* m è una potenza di 2 */

    if '(n < m) { printf(.ERROR: Array size nota power of 2 - byel\n"); exit(1); }

    w= calloc(n, sizeof(int)); /*alloca spazio di lavoro*/ assert(w l= NULL); /* controlla il funzionamento di calloc() */ for (k = 1; k = N)

    error_exit_too_many_words(); if (strlen(word) >= MAXWORD) error_exit_word_too_long(); Se il file contiene troppe parole o ci sono troppi caratteri in una delle parole lette da scanf (),stampiamo un messaggio relativo all'errore riscontrato e usciamo. L'utilizzo di funzioni d'errore in questi controlli contribuisce a rendere più chiaro il codice di mai n (). •

    w[i] = calloc(strlen(word) + 1, sizeof(char)); La funzione calloc () si trova nella libreria standard e il suo prototipo in stdlib.h. Una chiamata a funzione come calloc (n, sizeof ( ... ) ) alloca dinamicamente spazio per un array di n elementi, ognuno dei quali richiede sizeof ( ... ) byte di memoria, e restituisce un puntatore allo spazio allocato. Il minimo numero di byte necessari per memorizzare la stringa word, includendo il marcatore di fine stringa \0, è strlen (word) +1 (si noti che il termine +1 è importante: purtroppo viene spesso dimenticato). La funzione calloc ( ) alloca dinamicamente questo spazio e restituisce un puntatore a esso; tale puntatore viene assegnato a w[ i 1. Avremmo potuto invocare malloc () invece di calloc (). Entrambe le seguenti istruzioni avrebbero funzionato correttamente: w[i] = malloc((strlen(word) + 1) * sizeof(char)); w[i] = malloc((strlen(word) + 1)); Le istruzioni sono equivalenti, perché sizeof ( char) ha valore l. n motivo per cui lo abbiamo evidenziato è quello di ricordare al lettore che nel codice si richiede spazio per memorizzare un array di char. La funzione calloc () inizializza lo spazio allocato a zero, mentre malloc () non esegue nessun tipo di inizializzazione della memoria che alloca. Nel nostro caso dobbiamo sovrascrivere questo spazio e non è quindi richiesta alcuna inizializzazione.

    256 Capitolo 6



    if (w[i] = NULL) error exit calloc failed(); È una buona-nonna di programmazione controllare sempre il valore restituito da calloc () o da malloc ().Se la memoria richiesta non è disponibile viene restituito il valore NULL. In questo caso stampiamo un messaggio d'errore e tenniniamo l'esecuzione.



    strcpy(w[i], word); Dopo che lo spazio è stato allocato, viene utilizzata la funzione st rcpy ( ) per copiare word in memoria a partire dall'indirizzo w[ i]. Alla fine del ciclo for è possibile pensare w come un array di parole.

    w



    sort_words(w, n); /*ordina le parole*/ wrt_words(w, n); /*stampa le parole ordinate */ La funzione sort_words () viene utilizzata per ordinare in modo lessicografico le parole dell'array, che poi vengono stampate tramite la funzione wrt_words ().

    Si consideri ora la funzione sort_words (). Essa utilizza un ordinamento per trasposizione, simile a bubblesort.

    Nel file sort.c: #include "sort.h" void sort_words(char *w(], int n) {

    int

    i, j;

    for (i = 0; i < n; ++i) for ( j = i + 1; j < n; ++j)

    /* ordina n elementi */

    Array, puntatori e stringhe 257

    if (strcmp(w[i1, w[j1) swap(&w[i1, &w[j1);

    >

    0)

    }

    Si osservi che le stringhe puntate da w[ i 1 e w[ j 1 vengono confrontate tramite la funzione st rcmp ( ) e, nel caso non siano in ordine, i due puntatori vengono scambiati utiliz~ zando la funzione swap (). Vengono passati gli indirizzi dei puntatori, in modo che i valori veri e propri dei puntatori nell'ambiente chiamante possano essere modificati dalla funzione chiamata. Le rispettive stringhe in memoria non vengono scambiate: lo scambio avviene solo per i puntatori.

    Prima dello scambio

    w

    Dopo lo scambio w

    w[ i]

    w[ j]

    Nel file swap.c: #include "sort.h" void swap(char **p, char **q) {

    258 Capitolo 6

    char

    •tmp;

    tmp = *p; *p = •q; *q = tmp; }

    Ognuno degli argomenti &w [ i] e &w [ j ] passati alla funzione swap ( ) è un indirizzo di un puntatore a char o, in maniera equivalente, un puntatore a puntatore a char. Per questo motivo, i parametri formali nella definizione di swap () sono di tipo char **.La funzione swap ( ) è molto simile a quella scritta in precedenza; solo i tipi sono differenti. Si consideri l'istruzione

    tmp

    =

    *p;

    Essendo p un puntatore a puntatore a char, l'espressione *p che dereferenzia p è di tipo puntatore a char owero char *. Pertanto la variabile tmp è dichiarata di tipo char *

    Nel file e"or. c: #include "sort.h" void error exit calloc failed(void) { printf ( "%s • , "ERROR: The call to calloc() failed to\n" allocate the requested memory- byel\n " ); exit(1); }

    void error_exit_too_many_words(void) {

    printf("ERROR: At most %d words can be sorted - byel\n", N); exit(1); }

    void error_exit_word_too_long(void) {

    printf ( "%s%d%s", "ERROR: A word with more than ", MAXWORD, "\n" characters was found- byel\n"); exit(1); }

    Nel file wrl.c: #include "sort.h" void wrt words(char *w[], int n) { int i;

    Array, punta tori e stringhe 259

    for (i = 0; i < n; ++i) printf("%s\n", w[i]); }

    Naturalmente, volendo utilizzare il programma sorl_words() per un lavoro impegnativo su una grande quantità di dati, sarebbe opportuno utilizzare un algoritmo di ordinamento più efficiente. Sarebbe possibile, per esempio, modificare le funzioni merge O e merge_sort () per operare su array di puntatori a char anziché su array di int (vedi Esercizio 37) . Inoltre, potremmo contare le parole e poi allocare lo spazio per w[ ] dinamicamente, piuttosto che utilizzare un valore N arbitrariamente grande come lunghezza dell'array (vedi Capitolo 11).

    6.14

    Parametri di main()

    In mai n ()è possibile utilizzare due parametri, chiamati convenzionalmente argc e argv, per comunicare con il sistema operativo. Di seguito è riportato un programma che stampa i parametri della propria riga di comando; si tratta di una variante del comando echo di MS-DOS e UNIX. l* Ristampa i parametri della riga di comando. */

    #include int main(int argc, char *argv[]) {

    in t

    i•t

    printf("argc = %d\n", argc); for (i = 0; i < argc; ++i) printf("argv[%d] %s\n", i, argv[i]); return 0; }

    La variabile argc contiene il numero dei parametri sulla riga di comando. L'array argv è un array di puntatori a char che può essere visto come un array di stringhe, ognuna delle quali è una parola sulla riga di comando. Poiché a rgv [ 0 1 contiene il nome stesso del comando, argc vale sempre almeno l. Compilando il programma precedente e ponendone il codice eseguibile nel file my_echo, mediante il comando

    my_echo a is for apple si ottengono sullo schermo le seguenti righe: argc = 5 argv[0] = my_echo

    260 Capitolo 6

    argv[11 argv[21 argv[31 argv [ 41

    =a

    is for apple

    In un sistema MS-DOS, la stringa in argv [ 01 consiste dell'intero nome di percorso del comando, ed è formata da lettere maiuscole. Come mostrato nel Paragrafo 11.12, spes-so i nomi dei file vengono passati a mai n () come parametri.

    6.15

    Array frastagliati

    Si desidera ora paragonare gli array bidimensionali di tipo char con gli array monodimensionali di puntatori a char; questi due costrutti presentano sia analogie che differenze. #include in t mai n (voi d) {

    char char

    a[2][151 = {"abc:", "a is for apple"}; *p(21 = {''abc: ", "a is for apple"};

    printf("%c%c%c %s %s\n", a[0][01, a[0](11, a[0][21, a[0], a[1]); printf("%c%c%c %s %s\n", p[0][01, p[0][11, p[0][21, p[0], p[1]); return 0; }

    n programma produce il seguente output abc abc: a is for apple abc abc: a is for apple

    n programma e il suo output mostrano delle analogie nel modo in cui i due costrutti vengono utilizzati. n programma viene ora considerato più in dettaglio. L'identificatore a è un array bidimensionale; la sua dichiarazione provoca l'allocazione di spazio per 30 char. L'inizializzatore bidimensionale è equivalente a quanto segue: {{'a',

    'b',

    'c',

    ':',

    '\0'}, {'a',

    '

    ',

    'i',

    's',

    ... }}

    L'identificatore a è un array i cui elementi sono array di 15 char, quindi a [ 01 e a [ 1 1 sono a loro volta array di 15 c ha r. Poiché gli array di caratteri sono stringhe, a [ 01 e a [ 1 1 sono stringhe. L'array a [ 0 1 è inizializzato a {'a',

    'b',

    'c',

    ':',

    '\0'}

    Array, puntatori e stringhe 261

    ed essendo specificati solo cinque elementi, il resto viene inizializzato a zero (carattere nullo). Sebbene in questo programma non vengano utilizzati tutti gli lementi, per esso è stato comunque allocato dello spazio. Il compilatore accede all'elemento a [i 1 [ j 1 utilizzando la mappa di memorizzazione. Ogni accesso richiede una moltiplicazione e un'addizione. L'identificatore p è un array monodimensionale di puntatori a char. La sua dichiarazione fa sì che venga allocato spazio per due puntatori (sulla nostra macchina 4 byte per ogni puntatore). L'elemento p [ 01 viene inizializzato per puntare ad "ab c: ", stringa che richiede spazio per 5 char. L'elemento p [ 1 1 viene inizializzato per puntare ad "a is ... ", tringa che richiede spazio per 15 char, compreso il carattere nullo '\0' alla fine della stringa; dunque p utilizza meno spazio di a. Inoltre il compilatore non genera codice per la mappa di memorizzazione per accedere a p [ i 1[ j 1; dunque gli accessi a p sono più veloci rispetto a quelli ad a. Si noti che a [ 0] [ 14] è un'espressione valida, mentre p [ 0] [ 14] non lo è in quanto supera i limiti della stringa puntata da p [ 01. Naturalmente a [ 0 1[ 141 sta al di fuori della stringa a [ 01 , ma non supera i limiti dell'array a [ 0]. Dunque l'espressione a [ 0] [ 14] risulta accettabile. Un'altra differenza è che le stringhe a cui si punta tramite p [ 0] e p [ 1 1 sono costanti e, di conseguenza, non possono essere modificate. Al contrario, le stringhe a cui si accede con a [ 0 ] e a [ 1 1 sono modificabili. Un array di puntatori i cui elementi vengano utilizzati per puntare ad array di varie lunghezze è detto array frastagliato. Avendo righe di lunghezza differente, l'array p del programma precedente è un esempio di array frastagliato. Pensando gli elementi p [ i] [ j ] come organizzati in un insieme "rettangolare" in righe e colonne, le differenti lunghezze delle righe danno al "rettangolo" un aspetto frastagliato, da cui il nome "array fra tagliato".

    Array frastagliato

    6.16

    Funzioni come parametri

    In C, i puntatori a funzioni possono essere passati come parametri, utili.zzati in array, restituiti dalle funzioni, e così via. In questo paragrafo viene descritto il funzionamento di questa possibilità.

    262 Capitolo 6

    Si supponga di dover eseguire un calcolo con una varietà di funzioni. Per esempio, si consideri il calcolo

    ..

    ,.

    dove dapprima/(k)- sin(k), e in seguito/è data daf(k) to dalla seguente routine.

    =

    l/k. Tale compito viene esegui-

    Nel file sum_sqr.c: #include "sum_sqr.h• double sum_square(double f(double x), int m, int n) {

    in t double

    k•

    ' sum

    = 0.0;

    for (k = m; k 0 && a[i] == 0; -- i) putchar ( l* I l Trovata una cifra iniziale maggiore di zero, I l stampa tutte le cifre rimanenti, compresi gli zero. *l for ( ; i >= 0; -- i) p r i nt f ( " %d " , a [ i ) ) ; putchar ( \n

    in t

    l

    l

    l

    )

    l

    )

    ;

    ;

    }

    Ecco cosa si ottien sullo schermo eseguendo tal programma:

    Integer a: Integer b: Sum:

    88099005798957 776988213577 88875994012534

    Array, puntatorì e stringhe 285

    Si o eiVi che l cifr ono memorizza t in l menti di array che vanno da Oa N- l, ma che s v ngono tampat in ordin inv r o. P r compr nd re il programma, cercat di ricordar l l zioni di aritm tica alle cuol l m ntari. Scrivete un programma imil eh calcoli il prodotto di due int ri. 24.

    L'operatore sizeof può venir u ato per det rminar il num rodi byte n c ari p r memorizzare un tipo o un' pre ione. applicato a un array, o non produce la dim n ione d ll'array; dit eh co a vi ne tampato fornite una pi gazione. #include void

    f(int a[]);

    int main(void) {

    char char in t double

    s[1 *p

    "deep in the heart of texas"; in the heart of texas";

    = "deep

    a[3]; d[S];

    printf("%s%d\n%s%d\n%s%d\n%s%d\n " , "sizeof(s) = " sizeof(s), "sizeof(p) sizeof(p), "sizeof(a) sizeof(a), "sizeof(d) = sizeof(d)); f (a);

    return 0; }

    void f(int a[]) {

    printf("In f(): sizeof(a)

    =

    %d\n", sizeof(a));

    }

    25.

    di ponet d l i tema UNIX cono c t l'utility diff, effettuat il rimento: utilizzat l du i truzioni printf("abc\n");

    gu nt

    pe-

    printf("a%cb%cc\n", '\0', '\0');

    p r criver due versioni di un programma elementar che tampi ullo chermo. Effettuate la redir zion dell'output dei ri pettivi programmi in du file, tmpl tmp2. Utilizzando l'utility UNIX cat per tampar i due file ullo chermo, non apparirà alcuna differenza; provate ora a utilizzare il guente comando: di ff tmp 1 tmp2

    Dite che cosa succede e si fornisca una piegazione. Suggerimento: il comando od con l'opzione -c permette di vi ualizzare in maniera più completa il contenuto d i

    286 Capitolo 6

    file. A propo ito, dite perché è stato utilizzato il formato %c. P rché non tampar dir ttam nte la tringa "a\ 0b \ 0c \n"?

    26.

    Nel C tradizional era po ibile modificar il contenuto di una co tant tringa, bb n far ciò fo e con id rata una cattiva pratica di programmazion . In AN I C i suppone che il programmatore non pos a modificar una co tante tringa, tuttavia ciò viene forzato dai compilatori in modi differenti. Con id rate il guente codic : char

    *p = "abc";

    *p = l xl; p r intf ( "%s \n " , p) ;

    l* illegale? *l l* viene stampato Xbc? *l

    ul no tro istema, uno d i compilatori non ha gnalato alcun probl ma il programma è tato e eguito; l'altro compilatore, invec , ha gnalato un error d' cuzione. Dite che co a succede ul vo tro ist ma.

    27.

    Con si d rate il char

    guente codi c :

    *p = "abc", *q = "abc";

    if (p == q) printf("The two strings have the same addressl\n " ); else printf("As I expected, the addresses are different.\n"); I puntatori p e q ono stati inizializzati all'indirizzo ba in memoria di una tringa costante, cio' "ab c". Si o ervi che p == q v rifica s i du puntatori coincidono, tuttavia non ' un te t sull'uguaglianza dei cont nuti d Il tringh puntate da p da q. In memoria sono pr senti due co tanti stringa o olo una? La ri po ta dipende dal compilatore; molti compilatori forni cono inoltr un'opzion eh p nn tt di det rminare se tutte l co tanti stringa con lo tes o cont nuto d bbano memorizzate eparatamente o come un'unica tringa. N l C tradizional , e ndovi la po ibilità di sovra criv r l costanti tringa (v di E rcizio 26), l co tanti tringa con lo st o cont nuto venivano di olito memorizzat paratam nt . Al contrario, molti compilatori ANSI C le m morizzano n Ilo te o luogo; dit eh co a succ d sul vo tro i tema. 28.

    n comitato ANSI C ha introdotto il qualificatore di tipo const com nuova parola chiave d l linguaggio. Ecco un esempio d l uo utilizzo: const char

    *p;

    In que to ca o il qualificator di tipo const segnala al compilator che il caratt r in m moria puntato da p non deve v nire modificato Oeggete: "p ' un puntator a una co tante char"). I compilatori forzano ciò in modi differ nti; provate il s guente codice.

    Array, puntatori e stringhe 287

    char const char

    s[] = "abc"; *p = s;

    *p = 'A'; printf( "%s\n " , s);

    /* illegale? */

    TI vo tro compilator dovr bb 29.

    gnalar un probl ma.

    ono tati fatti molti forzi n llo tudio d l probl ma d Ila traduzion automatica. Quanto ucc o può av r un approccio naif? A titolo di perim nto c rcat l 100 parol ingl i più fr qu nti con ultando, per mpio, The American Heritage Word Frequency Book di John Carrol t al. (Bo ton, Ma .: Houghton Mifflin, 1971). riv t l 100 parol , con l'aiuto di un dizionario, la loro traduzion in un'altra lingua. Scriv t poi un programma eh utilizzi du array com char

    *foreign[100], *english[100];

    , provat il programma (i ri ultati pore orprendenti). E eguite un altro p rim nto utilizzando 200 pauna traduzion d ci am nt mi30.

    31.

    Dit eh co a vi n

    tampato

    fomit una pi gazion .

    #include void

    try_me(int [][3]);

    in t mai n (voi d) {

    in t

    a[3] [3]

    {{2, 5, 7}, {0, - 1, - 2}, {7, 9, 3}};

    try me (a); return 0; }

    void try_me(int (*a)[3]) {

    printf("%d %d %d %d infinity\n", a[1][0], - a[1][1], a[0][0], a[2][2]); }

    288 Capitolo 6

    Modificat ora la dichiarazione d l param tro nell'inte tazion d Ila definizion della funzion try_me () con int *a[3] la ciat intatto il re to d l codic . Il vo tro compilator dovr bb probl mi. Fornite una pi gazion .

    gnalar d i

    32.

    copo n c ario av r comau tiva d ll'ultima i truzior intrapr a olo da tud nti di

    33.

    #include int main(void} {

    in t

    a[ 3] [ 5], i,

    *p

    = *a;

    j'

    /* un ' inizializzazione simpatica! */

    for (i = 0; i < 3; ++i) for (j = 0; j

    complemento unario bit a bit and bit a bit or esclusivo bit a bit or inclusivo bit a bit scorrimento (shift) a sinistra scorrimento (shift) a destra

    296 Capitolo 7

    Com per gli altri op ratori, anch p r gli op ratori orientati ai bit vi ono r gole di priorità a o iatività eh d t rminano e attamente com avv nga la valutazione d 11 e pr ioni h li utilizzano.

    [ 1 + + (postfisso)

    ()

    -- (postfisso)

    ++ (prefisso) -- (prefisso) sizeof (tipo) l + (unario) - (unario) & (indirizzo) * (dereferenziazione)

    *

    da da da da da da da da da da

    %

    l

    +

    >>

    =

    --

    *= / = &= "'-

    l=

    &

    &&

    Il ?: +=

    >>=

    >. Nel caso in cui p sia uguale a -3579753 (scelta per la sua particolare rappresentazione macchina) e n abbia valore 16, avviene quanto illustrato nella seguente tabella.

    p

    mask p & mask (p & mask) >> n •

    11111111 00000000 00000000 00000000

    11001001 11111111 11001001 00000000

    01100000 00000000 00000000 00000000

    10010111 00000000 00000000 11001001

    -3579753 16711680 13172736 201

    return ((p & mask) >>n); Essendo char il tipo della funzione unpack (), il valore dell'espressione di tipo int (p & mask) >> n viene convertito a char prima di essere restituito all'ambiente chiamante. La conversione di un in t a char avviene estraendo il byte meno significativo, senza considerare gli altri.

    306 Capitolo 7

    i immagini di voi r m morizzar in un intero un r cord abbr viato relativo a un impi gato. A tal copo i upponga che un "num ro id ntifi ator d ll'impi gato" po a r m morizzato in 9 bit, il "tipo di lavoro" in 6 bit, p r un totale di 64 po sibili lavori difi r nti il "s s o" in l bit. Qu sti tr campi richi dono in total 16 bit eh , u una macchina con parol di 4 byte, corrispondono a un int ro short. Qu ti tr campi di bit pos ono e ere visti come s gu :

    bbbbbbbbb

    bbbbbb

    b

    La eguent funzion può e r utilizzata in un programma prog ttato p r inserir i dati relativi a un impiegato in uno short. Al contrario, l' trazion d i dati da uno short dovrebbe e sere effettuata utilizzando delle ma chere. /* Crea i dati di un impiegato in uno short int. */

    short create_ employee_data(int id_ no, int job_type, char gender) {

    short

    employee = 0;

    l* inizia con tutti i bit a zero */

    employee l = (gender == ' m' l l gender == 'M') ? 0 : 1; employee l = job_type lose cnt) printf("CONGRATULATIONS - You wonl\n\n"); else if (win_cnt == lose_cnt) printf(•A DRAW - You tiedl\n\n•); else printf(.SORRY - You lostl\n\n•); }

    void prn_game_status(int win_cnt, int lose_cnt, int tie_cnt) {

    printf{"\n%s\n%s%4d\n%s%4d\n%s%4d\n%s%4d\n\n", ''GAME STATUS: ", • , win_cnt, " Win: Lose: lose_cnt, Tie: tie_cnt, Total: " win_cnt + lose_cnt + tie_cnt); }

    void prn_help(void) {

    printf{"\n%s\n", The following characters can be used for input:\n" p for paper\n" r for rock\n" for scissors\n• s g print the game status\n" h help, print this list\n" i reprint the instructions\n" q quit this game\n"); }

    void prn_instructions(void) {

    printf("\n%s\n", "PAPER, ROCK, SCISSORS:\n" In this game p is for \"paper,\" r is for \"rock,\" and" s is for \•scissors.\•\n• Both the player and the machine\n" will choose one of p, r, or s." If the two choices are the same,\n" then the game is a tie. Otherwise:\n" \"paper covers the rock\" (a win for paper),\n"

    Operatori orientati ai bit e tipi enumerativi

    313

    \"rock breaks the scissors\" (a win for rock),\n" \"scissors cut the paper\" (a win for scissors).\n" "\n"

    "

    There are other allowable inputs:\n" g for game status (the number of wins so far),\n" h for help, \n" i for instructions (reprint these instructions),\n" q for quit (to quit the game).\n"

    • \n •



    This game is played repeatedly until q is entered.\n•

    • \n •



    Good luck!\n•);

    }

    Per giocare, sia la macchina che il giocatore (cioè l'utente) devono effettuare una scelta tra carta (p), sasso (r), forbici (s). Queste routine vengono scritte in selection.c.

    Nel file selection.c: #include "p_r_s.h" p_r_s selection_by_machine(void) {

    return ((p_r_s) (rand()% 3)); }

    p_r_s selection_by_player(void) {

    char p_r_s

    c; player_choice;

    printf("Input p, r, or s: "); while (isspace(c = getchar()))

    ' (c) { switch case l p l: player_choice break; case l rl: player_choice break; case l s l: player_choice break; case l g l: player_choice break; case l i l: player_choice break;

    = paper; rock;

    = scissors; = game; instructions;

    !* salta le spaziature */

    314 Capitolo 7

    case l q l: player_choice break; default: player_choice break;

    quit;

    = help;

    }

    return player_choice; }

    La elezione della macchina viene effettuata utilizzando l'e pr sion rand () % 3 per produrre valori interi uniform ment di tribuiti tra O 2. Poiché il tipo d Ila funzion ' p_ s_ r , e neces ario il valor restituito vi ne conv rtito a que to tipo. È tato critto plicitamente un ca t per render il codice più autodocumentant possibil . Si o ervi che in selection_by_player() vi ne utilizzata la macro isspace() contenuta in ctype.h per saltar i caratt ri di spaziatura (le macro di ctype.h ono di cusnel Paragrafo 8.7). Dopo aver saltato la spaziatura, v ngono laborati tutti i caratt ri dati in input da terminale, la maggior part d i quali corri pond al ca o default d ll'i truzione swi tch. n valore restituito da selection_by _player () d t rmina quale ca o v nga eguito nell'i truzione swi tch () di mai n (). Il valore re tituito dip nde da ciò eh vi n digitato dal giocatore. viene inserito come input il caratter g, allora vi ne chiamata prn_game_status (); se vi ' un caratt r diver o dalle spaziatur da p, r , s, g, i oppure q viene invocata prn_help (). Una volta che il giocatore e la macchina hanno efi ttuato una el zion , ' n c rio confrontare le due selezioni al fine di det nninar l' ito d l gioco. Ciò vi n tuato dal codice eguente:

    Nel file compare.c: #include "p_ r_s.h" outcome compare(p r s player_choice, p_ r_ s machine_ cho i ce)

    --

    {

    outcome

    result;

    if (player_choice == machine_choice) return tie; switch (player_choice) { case paper: result = (machine_choice rock) ? win : lose; break; case rock: result = (machine_choice scissors) ? win : lose; break; case scissors: result = (machine_choice paper) ? win lose; break; default:

    Operatori orientati ai bit e tipi enumerativi 315

    printf("\nPROGRAMMER ERROR: Unexpected choicel\n\n " ); exit(1); }

    return result; }

    Il valor r tituito da compare () in mai n () vi n pa ato alla funzion report_and_tabulate( ), eh r titui c all'ut nt il ri ultato di una mano di gio o increm nta in mani ra appropriata il num ro di vittori , confitt par ggi.

    Nel file reporl.c: #include "p_ r _ s.h" void report tabulate(outcome result, int *win_ cnt_ ptr, int *lose_ cnt_ ptr, int *tie_cnt_ ptr) {

    switch (result) { case win: ++*win_ cnt_ ptr; printf( "%27sYou win.\n", .. .. ); break; case lose: ++*lose cnt ptr; printf ( "%27sYou lo se.\ n " , " " ) ; break; case tie: ++*tie cnt ptr; printf( "%2lsA tie. \n " , "" ); break; default: printf( "\ nPROGRAMMER ERROR: Unexpected resultl\n\n"); exit(1); } }

    È po ibil ora ompilar il programma; ciò può

    cc

    -o

    p_ r_s

    main.c

    compare.c

    Lo viluppo di un programma può

    prn.c

    report.c

    selection.c

    r facilitato m diant l'utility make, pr n l Paragrafo 11.17, utilizzando un opportuno mak fil .

    7.7 l.

    ntata

    Riepilogo Gli operatori orientati ai bit forni cono al programmatore la pos ibilità di ace d r ai bit in un'espression intera. Gli operandi di questi operatori po ono tipicam nte

    er pen ati com

    tringh di bit.

    316 Capitolo 7

    2.

    L'impi go di e pr ioni che operano sui bit pennett la compr ion di dati oltre i confini d i byt . Que ta po sibilità con nte di ri parmiar pazio, ma · ancora più utile per risparmiare tempo. u una macchina con parole di 4 byt , ogni ciclo macchina elabora in parallelo 32 bit.

    3.

    Su molte macchio gli interi v ngono rappr entati in compi mento a du . In queta rappresentazione il bit di egno corri ponde al bit più ignificativo: sso val l p r gli interi n gativi, O per i non n gativi.

    4.

    plicitament dalla macchina. Uno hift

    ogni caso. 5.

    Le maschere ono valori particolari utilizzati generalm nt con l'operatore & p r e trarre una data sequenza di bit. n compattare consi t nel porr un c rto num ro di valori distinti in vari sottocampi di una data variabil . Lo compattamento estrae questi valori.

    6.

    La parola chiave enum permett al programmatore di d finir tipi enum rativi. Una variabile di tale tipo può assumere valori n ll'insiem di numeratori associati al tipo.

    7.

    Gli enumeratori sono identificatori di tinti c lti per il loro ignificato mnemonico. Essi sono utili ia p r i vincoli al programmatore ul controllo d i tipi eh p r l'autodocumentabilità del programma.

    8.

    Gli enumeratori ono costanti di tipo int. E i pos ono v nir utilizzati nell tieh tt case di un'i truzion swi t eh. È possibile ri olv r i conflitti utilizzando un cast.

    7.8

    Esercizi

    l.

    Supponete che gli interi siano rappresentati in complem nto a du u 16 bit. Scrivet la rappresentazione binaria di -1, -5, - 101, -1023. Ricordate che la rappre entazione in compi mento a due degli interi non n gativi è ottenuta complementando la rappresentazione macchina dell'intero po itivo corrispondente e aggiungendo l.

    2.

    Alice, Betty e Carol votano tutte per 16 referendum differenti. Supponete che il voto di ciascuna ia memorizzato bit a bit in un intero di 16 bit.

    Operatori orientati ai bit e tipi enumerativi 317

    Scrivete una d finizione di funzion il cui inizio ia: short majority(short a, short b, short c) {

    Qu ta funzion dovr bb com input i voti di Alle , B tty Carol m morizzati rispettivam nt in a, b c, dovr bb r tituir la maggioranza, cio il valor di majority, bit a bit, di a, be c.

    3.

    riv t una d finizion di funzion eh inizi con: int circular shift(int a, int n)

    -

    {

    Qu ta funzion d v calcolar uno hift a ini tra di n po izioni, in cui i bit più mignificativi v ngano r introdotti com m no ignificativi. I gu nti ono du pi di un'op razione di shift circolar applicata a un char anzich a un int: con hift circolare di l produce con shift circolare di 3 produce

    10000001 01101011

    4.

    La vo tra macchina introduce bit di vi può aiutar n lla ri po ta: in t unsigned

    i

    u

    = - 1;

    00000011 01011011

    gno in uno shift a de tra? Il

    gu nt codi

    /* pone tutti i bit a 1 */

    = - 1;

    if (i>> 1 == u >> 1) printf("Zeros are shifted in.\n"); else printf("Sign bits are shifted in.\n " ); pi gat perch que to codice p rm tt di ri ponder . 5.

    Scriv t una funzion eh rov ci la rappr ntazione macchina di un int. I guenti ono due esempi di un'op razione di rovesciam nto applicata a un char anziché a un int: 01110101 10101111

    rov ciato produc rove ciato produc

    10101110 11110101

    6.

    Scrivete una funzione che e tragga un bit in posizione qualsia i da un'e pres ion di 32 bit. n risultato deve es re re tituito come espressione di 16 bit. La funzione dovrebbe operare sia su macchine con parole di 2 che con parole di 4 byte.

    7.

    Scrivete una funzione che riceva come input una stringa di cifre decimali. Ogni carattere nella stringa dovr bbe ess re con iderato come una cifra d cimai . Le cifr devono e ere convertite in una stringa binaria di 4 bit compattat in un

    318 Capitolo 7

    int. un int ha 32 bit, in es o possono essere compattat 8 cifr . Provando la vo tra funzion , sullo chermo dovrebb apparire quanto gu : Input a string of decimai digi ts: 12345678 12345678 = 0001 0010 0011 0100 0101 0110 0111 1000 rivet anche una funzion e inver a: essa dovr bb compattare un int re tituir la stringa di partenza. Suggerimento: ' po sibile iniziar la fun zione di conv rione n l modo s gue nt : int convert(char *s) {

    char int

    *p; a = 0;

    / * poni tutti i bit a zero */

    for (p = s; *p 1= a = 0.0) ? ((diff > 0.0) ? -1 : 0) : +1); Se diff è positiva viene restituito -1, se vale zero viene restituito O, se è negativa viene restituito l. In questo modo l'array viene ordinato in maniera decrescente. Sostituendo questa riga con if (diff < 0.0) return -1; if (diff == 0.0) return 0; return 1; l'array viene ordinato in maniera crescente.



    void fill_array(double *a, int n) {

    in t

    i•

    '

    srand(time(NULL));

    /* inizializza il generatore */

    334 Capitolo 8

    G n ralment , la chiamata di funzion t ime ( NULL) r ti tu i c il num ro di econdi tra cor i dal1 o g nnaio 1970. ANSI C non garanti c ciò, ma qu ta conv nzion ' ampiam nt ace ttata. Pa ando time(NULL) a srand(), l'array a vi n ri mpito con valori differenti a ogni ecuzion del programma.



    for (i = 0; i < n; ++i) a[i] = (rand() % 1001) l 10.0; L' spr ion rand () % 1001 ha un valor int compr o n ll'int rvallo da O a 1000; tale valore vi ne divi o p r il valor double 10.0. P rtanto il valore a gnato ad a [i 1 si trova nell'intervallo da Oa 100.

    8.6

    Un esempio: macro con parametri

    In que to paragrafo gli array v ngono riempiti e ordinati con qsort (),utilizzando p rò de ll macro con param tri. programma pre entato vi n chiamato sort. n programma vi n po to in tr file: un fil d'int tazion sort.h du file .c. N l fil d'int tazion v ngono post l dir ttive #include, #define un l n o d i prototipi di funzione.

    n

    Nel file sort. h: #include #include #include #include #define #define #define #define #define



    M

    32

    N 11 fractional _ part(x) random_ char() random_ float()

    l* lunghezza di a[] *l l* lunghezza di b[] *l (x - (in t) x) (rand() % 26 + ' a') (rand() % 100 l 10.0)

    #define FILL(array, sz, type) if (strcmp(type, "char") == 0) for (i = 0; i < sz; ++i) array[i] = random_ char()i else for (i = 0; i < sz; ++i) array[i] = random_float() #define PRINT(array, sz, cntrl_ string) for (i = 0; i < sz; ++i) printf(cntrl_ string, array[i]); putchar ( ' \ n ' ) int int

    \ \ \

    compare_fractional_part(const void *, const void *); lexico(const void *, const void *);

    Il preprocessore 335

    ANALISI DEL FILE D'INTESTAZIONE sort.h •

    #include #include #include #include n fil d'int tazion stdio.h conti n la d finizion di macro p r NULL il prototipo d Ila funzion pr intf ().Il fil d'int tazion stdlib.h onti n i prototipi d n funzioni rand () , srand () qsort ().n fil d'int tazion time.h onti n il prototipo di fun zion di t ime (). La chiamata di funzion t ime ( NULL) vi n utilizzata per inizializzar il g nerator di num ri a uali.



    #define fractional_ part(x) (x - (int) x) x ' un f lo a t po itivo, allora l' pr ion x - (in t) x r titui c la part d cimale di x.



    #define random_ char() (rand() % 26 + ' a ' ) Quando vi ne chiamata, la funzione rand ( ) r stitui c un valor int ro di tribuito unifonne m nt tra O e MAX_RAND, costant imbolica definita in stdlib.h. Poich di olito MAX_ RAND è uperiore a 32000, l' pr ione rand () % 26 r titui c un valor in t ro di tribuito unifonn ment tra Oe 25. Poiché 0 + ' a ' val ' a ' 25 + ' a ' val ' z ' , l' pr ion rand() % 26 + ' a' r stitui c un aratt r uniform m nt di tribuito tra ' a '



    'z'.

    #define

    random_ float() (rand() % 100/10.0) d Il' pr ion rand () % 100 ' un in t rodi tribuito uniform m n t tra O 99. Poich l' pr ion 10.0 ' di tipo double, il valor prodotto da rand () % 100 vi n promo o a double quindi l'int ra pr ion

    n valor

    rand() % 100 l 10.0 ' di tipo double. •

    n

    uo valor \ compr o tra

    o

    9.9.

    #define FILL(array, sz, type) if (strcmp(type, "char " ) == 0) for (i = 0; i < sz; ++i) array [i 1 random_ char () ; else for (i = 0; i < sz; ++i) array [i 1 random_ float () In qu ta d finizion di macro, array, sz type ono param tri. Contrariam nt a quanto avvi ne p r le definizioni di funzioni, in qu to ca o non c'' alcun controllo di tipo. È r pon abilità d l programmator richiamar la macro utilizzando param tri di tipo appropriato. i o rvi eh n l corpo d Ila macro vi n utilizzata

    336 Capitolo 8

    la variabile i; non essendo qui dichiarata, essa deve essere dichiarata in ma in ( ) , dove viene richiamata la macro. Si consideri la chiamata di macro: FILL(a, n, "char"); Quando la macro viene espansa si ottiene: if (strcmp( 'Char", "char") == 0) for (i = 0; i < n; ++i) a[i] = random_char(); else for (i = 0; i < n; ++i) a[i] = random_float(); 1

    Gli identificatori array, sz e type sono stati sostituiti rispettivamente da a, n e "char". Si ponga attenzione al fatto che tutto, tranne l'ultimo punto e virgola, deriva dal meccanismo di espansione del preprocessore. •

    #define PRINT(array, sz, cntrl_string) for (i = 0; i < sz; ++i) printf(cntrl_string, array[i]); putchar( l \n l) Questa macro può essere utilizzata per stampare i valori di elementi di un array. Si osservi che la stringa di controllo di printf () è un parametro nella definizione della macro.



    int compare_fractional_part(const void *, const void *); int lexico(const void *, const void *); Questi sono prototipi di funzioni di confronto che verranno passati a qsort (). Si osservi che, per quanto riguarda i tipi, essi corrispondono al prototipo della funzione di confronto nel prototipo di qsort (). Viene ora presentato il restante codice del programma. In ma in () si riempie un

    array, lo si stampa, lo si ordina e lo si stampa nuovamente. Questo processo viene quin-

    di ripetuto, ma con un array di tipo differente.

    Nel file main. c: #include "sort.h" int main(void) {

    char float in t

    a[M]; b[N];

    i;

    srand(time(NULL)); FILL(a, M, "char"); PRINT(a, M, "%-2c");

    Il preprocessore 337

    qsort(a, M, sizeof(char), lexico); PRINT(a, M, "%-2c"); pr intf (" - - -\n" ) ; FILL(b, N, "float"); PRINT(b, N, "%-6.1f"); qsort(b, N, sizeof(float), compare_fractional_part); PRINT(b, N, "%-6.1f"); return 0; }

    Si osservi che a ogni chiamata di qsort () viene utilizzata una funzione di confronto differente. Di seguito è riportato l'output del programma: q mz r h l a j o e t b k w l t z t v i e mh p f y b p s w a j a a b b e e f h h i j j k l l mmo p p q r s t t t v w w y z z 9.4 6.1

    0.2 5.1

    5.1 0.2

    6.7 5.3

    5.4 5.4

    5.3 9.4

    6.1 8.5

    9.6 9.6

    2.8 6.7

    8.8 8.8

    8.5 2.8

    Si considerano infine le due funzioni di confronto. Vengono utilizzati puntatori a void, come richiesto nel prototipo di funzione di qsort () in stdlib.h. Nel seguito viene spiegato accuratamente come questi puntatori siano utilizzati nelle funzioni di confronto. Nel file compare. c: #include "sort.h" int compare_fractional_part(const void *vp, const void *vq) {

    const float float

    *p = vp, *q = vq;

    x. '

    x= fractional_part(*p) - fractional_part(*q); return ((x< 0.0) ? -1 : (x== 0.0) ? 0 : +1); }

    int lexico(const void *vp, const void *vq) {

    const char *p = vp, *q = vq; return (*p- *q); }

    ANALISI DELLA FUNZIONE compare_fractional_part() •

    int compare_fractional_part(const void *vp, const void *vq) {

    Questa funzione riceve come parametri due puntatori a void qualificati con const e restituisce un int. Per questo motivo la funzione può venire passata come parametro a qsort ().

    338 Capitolo 8



    int compare_fractional_ part(const void *vp, const void *vq) {

    const float float

    *p = vp, *q = vq;

    x;

    La lettera v in vp e vq è mnemonica per "void". Poiché i puntatori a void non

    po ono essere dereferenziati, p e q sono dichiarati come puntatori a f lo a t e inizializzati rispettivamente con vp e vq. p e q vengono dichiarati con il qualificatore const , in quanto altrimenti il compilatore ANSI C "si lamenterebbe" di un as egnamento di un puntatore qualificato con const a uno non qualificato. Si osservi che x non viene qualificata con const: e lo fos e arebbe pos ibile a segnare a essa olo un valore di inizializzazione. •

    x = fractional_part(*p) - fractional_part(*q); return ((x< 0.0) ? - 1 : (x== 0.0) ? 0 : +1); Viene assegnata a x la diii renza tra le parti decimali degli oggetti puntati da p e da q. Dunque il risultato restituito è -l, O oppure +l, a eco nda del fatto che x sia negativa, zero o positiva. Chiamando quindi qsort () con compare_fractional_ part () come parametro, gli elementi dell'array v ngono ordinati in bas alle rispettive parti decimali.

    Si osservi che, nella funzione lexico (), p q sono tati definiti come puntatori a const char e inizializzati rispettivamente a vp e vq. Viene poi restituita la differenza tra gli oggetti puntati da p e q. Dunque, una chiamata di qsort () con lexico come parametro provoca un ordinamento lessicografico d gli el menti nell'array.

    8.7

    Macro in stdio.h e ctype.h

    Il i tema C fornisce le macro getc () e putc () in stdio.h. La prima viene utilizzata p r leggere un caratt re da un file, la seconda per crivere un carattere in un fil (vedi Paragrafo 11.5). Poiché il file d'intestazione stdio.h contiene le righe #define #define

    getchar () putchar(c)

    getc(stdin) putc( (c), stdout)

    anche getchar () e putchar () sono delle macro; la prima legg caratteri dalla tastiera e la seconda scriv caratt ri sullo schermo. n sistema C forni sce anche un file d'intestazione standard ctype.h, che contiene un insieme di macro che testano caratteri e un insieme di prototipi di funzioni che convertono caratteri. La direttiva al preproc sore #include

    Il preprocessore 339

    include queste macro e prototipi. Nella tabella eguente v ngono elencate le macro che t tano caratteri: esse accettano tutte un param tro di tipo int restituiscono un int.

    isalpha(c) isupper(c) islower(c) isdigit(c) isalnum(c) isxdigit(c) isspace(c) ispunct(c) isprint(c) isgraph(c) iscntrl(c) isascii(c)

    c c c c c c c c c c c c

    è un a lettera è una l ttera maiu cola

    è una lettera minu cola ' una cifra

    è una lettera o una cifra è una cifra

    ad cimai

    è un caratt r di paziatura è un caratter di punt ggiatura è un caratt r tampabil è un carattere tampabil , ma non uno pazio è un carattere di controllo un codice ASCII

    Nella tabella successiva vengono elencate le funzioni toupper () e tolower (), che i trovano nella libreria standard, e la macro toascii ( ) . La macro e i prototipi per le due funzioni si trovano in ctype.h. Le funzioni la macro ricevono un int e restitui cono un int. Nella tabella si assume che c sia una variabile di un tipo intero come char o int. Si noti che il valore di c in memoria non vi ne modificato.

    valore maiu colo corrispondente a c valore minu colo corri pondent a c valor ASCII corri pond nt

    Quando c non è una lettera minuscola, il valore re tituito da toupper (c) è c. Analogamente, se c non è una lettera maiu cola il valore restituito da tolower (c) è c.

    8.8

    Compilazione condizionale

    E istono direttive al preprocessore per la compilazion condizionale; esse possono e ~ sere utilizzate per lo sviluppo di programmi e per criver codice più facilmente portabile da una macchina all'altra. Ogni direttiva al preprocessore della forma #if #ifdef #ifndef

    constant_integral_expression identijier identifier

    340 Capitolo 8

    è util per la compilazione condizionale del codic che egue, fino al raggiungimento della direttiva: #endif Affinché tale codice venga compilato, nel caso di #i f l' spressione costante deve e ere div rsa da zero (vera), e dopo #i fdef o #i f def in ed l'identificatore menzionato dev essere già stato definito in una riga #def in e s nza che ne sia stata eliminata la definizione mediante una riga: #undef

    identifier

    Dopo #i fndef l'identificatore menzionato deve ri ultare non d finito. L'espressione costante intera utilizzata in una direttiva al preprocessore non può contenere l'operatore sizeof o un cast; essa può però u are l'operatore del pr proce so re def in ed, disponibile in ANSI C, ma non sempre n l C tradizionale. L'espressione: def in ed identijier

    è equivalente a: def in ed (identifier) Essa vale l se l'identificatore è correntemente definito, altrimenti vale O. Il seguente è un e empio del suo utilizzo: #if defined(HP9000) Il defined(SUN4) && ldefined(VAX) l* codice dipendente dalla macchina */ #endif Talvolta le istruzioni printf () risultano utili per il debug. Si supponga di scrivere all'inizio di un file #define

    DE BUG

    e di inserire poi nel resto del file righe come: #if DEBUG printf("debug: a= %d\n", a); #endif Poiché la costante imbolica DEBUG è diver a da zero, le istruzioni printf () vengono compilate. Successivamente, è possibile omettere queste righe dalla compilazione cambiando a O il valore della costante simbolica DEBUG. Uno schema alternativo consiste nel definire una costante simbolica alla quale non sia associato alcun valore. Si supponga di crivere all'inizio del file: #define

    DE BUG

    Il preprocessore 341

    È po sibile poi utilizzare l forme di compilazion condizionai #ifdef o #if defined. P r esempio, crivendo

    #ifdef DEBUG #endif le righe di codice intermedie vengono compilate. Rimuovendo la riga #def in e che defini ce DE BUG all'inizio del file, le righe di codice intermedie non aranno più compilate. Si upponga di crivere del codic di un progetto software di grande dimensione. Può capitare di includer all'inizio d l proprio codic alcuni file d'intestazion preparati da altri; tale codice può dipendere da prototipi di funzion e da macro contenute in questi file d'intestazione, ma essendo quest'ultimi comuni a tutto il progetto, il codice potrebbe non utilizzarli completamente. Può capitare anche di non essere al corrente di tutto ciò che alla fine si troverà nei file d'inte tazione. Per prevenire la ovrapposizione di nomi di macro è possibile utilizzare la direttiva #undef:

    #include "everything.h" #undef #define

    PIE PIE

    "I like appie."

    Nel ca o PIE sia definita in everything.h, qui e a non risulta definita. Se invece non è tata definita in everything.h, allora la direttiva #undef non ha alcun effetto. Di seguito viene illustrato un utilizzo comune della compilazione condizionale. Si immagini di trovarsi nella fase di test durante lo sviluppo di un programma e che il codice abbia la eguente forma:

    bloCCff di istruzioni l blocco di istruzioni 2 blocco di istruzioni 3 Per scopi di debug o prova può essere desiderabile eliminare temporaneamente alcune parti di codice. Per fare ciò è possibile inserire tale codice in un commento.

    blocco di istruzioni l l*

    blocco di istruzioni 2 *l

    blocco di istruzioni 3 Tuttavia, se il codice eliminato contiene a sua volta dei commenti, questo metodo provoca un errore di sintassi. n problema può essere risolto utilizzando la compilazione condizionale.

    342 Capitolo 8

    blocco di istruzioni l #if 0

    blocco di istruzioni 2 #endif

    blocco di istruzioni 3

    npreproces or ha strutture di controllo simili all'i truzione i f- else del C. Ognuna delle forme #i f può essere seguita da un qualunque numero di righe, che possono cont n r anche direttive al preprocessore della forma #eli f constant_integral_expression s guite ev ntualmente dalla direttiva al preprocessore #else e, infine, dalla dir ttiva al preprocessore: #endif Si o servi che #eli f è una contrazione di "el e-if'. n .tlus o del controllo p r la compilazione condizionale è analogo a quello fornito dali i truzioni i f -else.

    8.9

    Macro predefinite

    In ANSI C i tono cinque macro predefinit , empre disponibili, che non po ono e ere re e indefinit dal programmatore. I loro nomi contengono sia all'inizio eh alla fin due caratteri di ottolin atura.

    llacro predltlnlta DATE ::::::::FILE:::::::: LINE

    ::::::::sroc:::::::: --TIME -N ll'E rcizio 6 vien mo trato come provare que te macro ul proprio ist ma.

    8.1 O Operatori # e ## Gli op ratori del pr proc ore# e ## ono di ponibili in ANSI C ma non n l C tradizional . L'op ratore unario #trasforma un parametro formale di una definizion di macro in una stringa. Ecco un e mpio d l uo utilizzo.

    Il preprocessore 343

    #define

    message_ for (a, b) \ printf(#a " and " #b " · We love youl\n")

    int main(void) {

    message_for(Carole, Debra); return 0; }

    Richiamando la macro, ogni parametro della d finizione vi n o tituito con il param tro corrispondente nella chiamata; i parametri con # vengono posti tra doppi apici; il preproce sore tra forma quindi il codice preced nte nel s gu nt : int main(void) {

    printf( "Carole" " and " "Debra" "· We love youl \n"); return 0; }

    Poiché le costanti stringa separate da spaziatura vengono concatenate, l'istruzion printf () risulta equivalente a: printf("Carole and Debra: We love youl\n"); Nel paragrafo successivo viene mostrato come utilizzare l'operatore# nelle asserzioni. L'operatore binario ## viene utilizzato per concatenare token. n seguente è un e empio del suo impiego: #define X(i) x ## i X(1) = X(2) = X(3); Dopo il passaggio del preprocessor si ottien la riga: x1

    8.11

    = x2

    = x3;

    Macro assert()

    ANSI C fornisce la macro assert () nel file d'intestazione asseri. h. Que ta macro può essere utilizzata per controllare che il valore di un'espressione ia proprio quello che ci si aspetta. Si upponga di scriver una funzione particolarment important e di voler er certi che i parametri soddi fino certe condizioni. Ecco un e empio di come sia pos ibile utilizzare assert () a que to scopo: #include void f(char *p, int n) {

    344 Capitolo 8

    assert(p l= NULL); assert(n > 0 && n< 7); Nel caso un'a serzione risulti falsa, il sistema stampa un messaggio d'errore e interrompe il programma. Sebbene su ogni sistema la macro assert () venga implementata in maniera differente, il suo comportamento generale è sempr lo stesso. Un possibile modo per scrivere la macro è riportato di seguito: #include #include #if defined(NDEBUG) #define assert(ignore) ((void) 0) #else #define assert(expr) if (l(expr)) { printf("\n%s%s\n%s%s\n%s%d\n\n", "Assertion failed: ", #expr, "in file ", _ _ FILE __ , "at line ", _ _ LINE__ ); abort();

    l* per abort() */ /* ignorala */

    \ \ \ \ \ \ \

    }

    #endif Si osservi come tutte le asserzioni vengano ignorate nel caso in cui la macro NDEBUG sia definita. Questo permette al programmatore di utilizzare liberamente le asserzioni durante lo sviluppo del programma ed eliminarle successivamente definendo la macro NDEBUG. La funzione abort () si trova nella libreria standard (vedi Appendice A).

    8.12

    Utilizzo di #error e #pragma

    In ANSI C ono state aggiunte le direttive al preproce sore #errar e #pragma. Nel codice seguente viene mostrato l'utilizzo di #errar: #if A SIZE < 8 SIZE #errar "Incompatible sizes" #endif Se durante la compilazione il preprocessore raggiunge la direttiva #errar, si verifica un errore di compilazione, e la stringa che segue la direttiva viene stampata ullo schermo. Nell'esempio, la macro #errar è utilizzata per forzare la con istenza di due co tanti simboliche. In modo analogo, la direttiva può essere utilizzata per forzare altre condizioni. La direttiva #pragma viene fornita per impieghi specifici dell'implementazione. La sua forma generale è #pragma tokens

    Il preprocessore 345

    il cui comportamento dipende dal particolare compilatore C. Ogni direttiva #pragma non ricono ciuta dal compilatore viene ignorata.

    8.13

    Numeri di riga

    Una direttiva al preprocessore della forma #line integral_constant "filename"

    fa sì che il compilatore rinumeri il testo sorgente in modo che la riga eguente corriponda alla costante specificata; inoltre fa sì che il compilatore utilizzi al posto del nome del file org n te corrente il nome filename. Se il nome del file non è presente vengono olo rinumerate le righe. Normalmente, i numeri di riga vengono nasco ti al programmatore e utilizzati solo come riferimento nei me saggi di avvertimento e q'errore.

    8.14

    Funzioni corrispondenti

    In ANSI C, molte delle macro con parametri fomite dai file d'intestazione standard hanno una funzione corrispondente nella libreria standard. Come esempio, si supponga di volere ace d re alla funzione isalpha () anziché alla macro. Una maniera di fare ciò consi te n Ilo crivere #undef isalpha

    in un punto d l file precedente alla chiamata di isalpha (). L'effetto è qu Ilo di eliminar la definizione di macro, forzando il compilatore a utilizzar la funzion al uo po to. È comunque n ces ario includ re all'inizio del file il file d'intestazione ctype.h in quanto, oltre alle macro, o contiene i prototipi di funzion . Un altro modo per otten r una funzion e al po to di una macro consiste nello c rive re: (isalpha)(c)

    n pr

    proc ore non ricono c questo costrutto come macro, ma il compilatore lo ricono c come chiamata di funzion (v di E ercizio 8).

    8.15

    Un esempio: quicksort

    Quicksort è tato inventato da C. Anthony R. Hoar e descritto nel suo articolo "Quicksort" del1962 (Computer ]ournal, vol. 5, n. 1). Tra l molteplici tecniche di ordinamento,

    346 Capitolo 8

    quick orte for e quella più ampiamente utilizzata n l caso di ordinamento interno, cioè di ordinamento in cui tutti i dati possono essere contenuti in memoria centrale. Viene pr sentato un codice per quick ort eh fa largo uso di macro. Ordinando grandi quantità di dati la velocità è essenziale, e l'utilizzo di macro al posto di funzioni aiuta a rendere più veloce il codice e eguibile. Le macro utilizzate ono abbastanza emplici e potrebbero essere sostituite da codice in lin a; tuttavia il loro impiego rende il codice più leggibile. Vista l'importanza di quick ort, la spiegazione è dettagliata. Si supponga di voler ordinare un array di n interi. Se i valori degli elementi sono distribuiti in maniera casuale, in media il num ro di confronti fatti da quick ort ' proporzionale a n log n; nel caso peggiore invece è proporzionale a n2 , e questo è uno d i suoi svantaggi. Per altri ordinamenti, come per esempio merge ort, il numero è proporzionale, anche nel caso peggiore, a n log n. Tuttavia, tra tutti i metodi noti e funzionanti di ordinamento in n log n, in media quicksort è il più v loc per un fattore costante. Un altro vantaggio di quicksort è quello di operar ul po to, s nza richiedere spazio di lavoro addizionai . ncodice di quicksort vien scritto in un unico fil ; gli elementi del codic vengono descritti man mano nel corso d Ila presentazion . All'inizio del file si ha: l* Quicksortl

    Versione con puntatori e macro. */

    #define #define #define #define

    swap(x, y) order(x, y)

    typedef

    enum {yes, no}

    o2 (x, y) o3 (x, y, z)

    static yes_ no static int

    { int t; t = x; x = y; y = t; } if (x > y) swap(x, y) order(x, y) 02 (x' y) ; 02 (x' z) ; 02 ( y' z)

    yes_no;

    find_pivot(int *left, int *right, int *pivot_ptr); *partition(int *left, int *right, int pivot);

    Le macro scritte non sono necessariamente robuste. L'intento è quello di utilizzarle solo in questo file. È stata utilizzata una typedef p r rendere il tipo yes_no inonimo del tipo enum {yes, no}. Poiché le du funzioni f ind_pivot ()e parti tion () hanno cla se di memorizzazione static, e se ri ultano note olo in que to file. void quicksort(int *left, int *right) {

    int

    *p, pivot;

    if (find_pivot(left, right, &pivot) == yes) { p= partition(left, right, pivot); quicksort(left, p - 1); quicksort(p, right); } }

    Il preprocessore 34 7

    Di solito quicksort viene implem ntato ricor ivamente, seguendo l'approccio "divide et impera". Si supponga di dichiarare in ma in () l'array a di lunghezza N. Dopo che l'array ' stato riempito, esso può s r ordinato utilizzando la chiamata: quicksort(a, a+ N - 1); Il primo parametro è un puntator al primo l m nto d ll'array, il secondo è un puntator all'ultimo elem nto dell'array. Nella d finizion di funzione di quicksort () è conveniente pen are che questi puntatori i trovino ri pettivamente sul lato sini tro e destro d ll'array. La funzione find _pivot () c gli , po sibil , uno degli tementi dell'array come upivot". La funzione parti tion () vi ne utilizzata per riorganizzare l'array in modo che la prima part contenga tutti gli l m nti il cui valore è minore del pivot e la parte rimanente tutti gli elementi il cui valor ' maggior o uguale al pivot. Inoltre, parti t io n () re titui c un puntatore a un elemento dell'array. Gli el menti a inistra di tale puntatore hanno valori minori del pivot, quelli a de tra e l'el mento puntato dal puntatore hanno valori maggiori o uguali al pivot. Una volta che l'array è stato riorganizzato rispetto al pivot, quicksort () viene richiamata u ogni ottoarray. static yes_no find_pivot(int *left, int *right, int *pivot_ptr) {

    int

    a, b, c, *p;

    l* valore sinistro *l l* valore centrale *l l* valore destro *l l* ordina questi 3 valori *l l* il pivot sarà il più alto di 2 valori *l

    a = *left; b = *(left + (right - left) 1 2); c = *right; o3 (a, b, c);

    if (a < b) { *pivot_ptr return yes;

    b;

    }

    if (b < c) { *pivot_ptr = c; return yes; }

    for (p = left + 1; p< = right; ++p) if (*p l = *left) { *pivot_ ptr = (*p < *left) ? *left : *p; return yes; }

    return no;

    l* tutti gli elementi hanno lo stesso valore *l

    }

    Idealmente, il pivot dovrebbe sere celto in maniera che a ogni passo l'array ia partizionato in due array di lunghezze uguali o quasi uguali: in questo modo la quantità di lavoro totale svolta da quicksort () verrebbe minimizzata. Poiché tale valor non ' noto a priori, il pivot viene celto come il valore mediano tra gli elementi iniziai , final c ntrale dell'array. Al fine di aver una partizion , deve es erci almeno un l m nto

    348 Capitolo 8

    minor d l pivot Se i valori di tutti gli elementi coincidono, il pivot non ne r titui ce no (p r un ulteriore approfondimento v di E rcizi 25

    la funzio-

    static int *partition(int *left, int *right, int pivot) {

    while (left = pivot) -- right; if (left < right) { swap(*left, *right); ++left; -- right; } }

    return left; }

    La maggior parte della computazion è svolta da parti t io n ( ) . Vi ne ora di eu

    dettaglio il funzionamento di que ta funzion elementi: 4

    7

    3

    5

    2

    5

    8

    2

    o in i upponga di avere un array a [ 1 di 12

    9

    l

    -6

    -3

    Quando viene chiamata find _pivot (),v ngono confrontati gli el m nti iniziai , ndo maggiore dell'elemento minor fra finale e centrai dell'array. n mediano è 5 ed i tr viene scelto come pivot Nella tabella guente sono mo trati i valori degli elem nti d ll'array dopo ogni iterazione del ciclo wh ile est m o n lla funzione parti t io n ( ) . Gli elem nti che vengono cambiati nell'iterazion ono videnziati da un riquadro. Dati non ordinati: Prima iterazion : Seconda iterazion : Terza iterazione: Quarta iterazione:

    7

    -3 -3 -3 -3

    4 4 4 4 4

    3 5 3 5 3 --6 3 -6 3 -6

    2 2 2 2 2

    5 5 5 l l

    8 8 8 8 2

    2 2 2 2 8

    l l l 5 5

    9 9 9 9 9

    --6 --6 5 5 5

    -3 7 7 7 7

    Si osservi che dopo l'ultima iterazione gli elem nti con indici da O a 6 hanno valore minore del pivot e gli elementi restanti hanno valore maggiore o ugual al pivot. All'uscita dalla funzione viene restituito l'indirizzo di a [ 7 1.

    8.16 l.

    Riepilogo

    n preprocessore fornisce la possibilità di includer

    file e definire macro. I file po sono essere inclusi utilizzando direttive della forma: #include

    #include

    "filename"

    Il preprocessore 349

    2.

    Una dir ttiva #def i ne al preproce ore può re utilizzata p r dare un nome imbolico a una tringa di token. Prima della compilazion , il pr processare so tituisce con la stringa ogni occorrenza del nome imbolico nel t to sorgente.

    3. L'utilizzo di #def i ne p r la definizione di co tanti imbolich aum nta la l ggibilità

    4.

    la portabilità dei programmi.

    n pr

    proc ore offre la possibilità di d finir macro con o tituzion di param tri. Un a m acro con parametri è d finita da una dir ttiva al pr proc or d lla forma: #def i ne identijier( identijier, ... , identijier) token_stringopt Un

    empio ' dato da:

    #define

    SQ(X)

    ((X)

    *

    (X))

    Qu sta macro forni ce codice in linea p r l' l vam nto al quadrato di un valor . E a non ' una chiamata di funzione.

    n pr

    5.

    processar permette la compilazion condizionai p r aiutare il programmatore nella fas di te ting, per facilitar la portabilità co i via. Le righ eh iniziano con #if, #ifdef, #ifndef, #elif, #else e #endif v ngono utilizzat a qu to copo.

    6.

    L'op rator def in ed può e ere utilizzato in ieme a dir ttive al preproces or . Un es mpio ': #if (defined(HP3000) l l defined(SUN3)) && 1defined(SUN4) /* codice dipendente dalla macchina */ #endif

    7.

    Un modo fficac per evitare la compilazione di parti di codic durant la fas di debug con i te nell'utilizzo di: #if 0 #endif

    8.

    ANSI C ha introdotto gli operatori del preprocessore #

    9.

    ANSI C fornisce la macro assert () nel file d'intestazione assert.h. Le as erzioni

    ##. L'operatore #è unario; esso può ess re applicato a un parametro formale nel corpo di una macro, e ha l'effetto di racchiudere il testo so tituito tra doppi apici, rendendolo quindi una stringa. L'operatore## è binario e concatena due token. possono essere utilizzate per assicurare che un'e pressione abbia un valor appropriato.

    350 Capitolo 8

    10.

    La funzione qsort () viene fornita dal sistema C, e il suo prototipo si trova in stdlib.h. Sebbene qsort () sia più veloce di bubblesort o di un semplice ordinamento per trasposizione, essa non è veloce come quicksort Un vantaggio di qsort () è la facilità d'implementazione.

    11. Quicksort è uno dei metodi d'ordinamento più utilizzati. Esso è più veloce di un fattore costante di tutti i metodi d'ordinamento n log n noti.

    Esercizi

    8.17 l.

    n debug di un programma che contiene macro con parametri può risultare difficile. Molti compilatori C dispongono di un'opzione che permette di stampar l'output del preprocessore sullo schenno senza poi pro guire la compilazione. Ponete il codice seguente in un file di nome try_me.c: #include #define

    PRN(x)

    printf("x\n");

    int main(void) {

    PRN(Hello from main()); return 0; }

    In seguito compilate il programma ed e eguitelo. Si noti come esso non stampi ciò che ci si aspetterebbe. Per vedere come questo codice viene trattato dal preprocessore, impartite il comando:

    cc

    -E try_me.c

    Se desiderate esaminare attentament l'output prodotto utilizzate la redir zione. Se l'opzione -E non è adatta al vostro compilator , trovate l'opzione corretta. Oservate come l'identificatore PRN non venga prodotto dal preproces ore. Spiegatene la ragione e correggete il codice. Suggerimento: utilizzate l'operatore #. 2.

    Considerate la eguente definizione di macro: #define forever(x) forever(more)

    forever(forever(x))

    Essa embra produrre una ricorsione infinita, ma si suppone che in ANSI C il preprocessore sia abbastanza intelligente da comprendere che ciò che si intende non è una ricorsione infinita. Come viene espansa questa macro dal vostro preprocessore?

    Il preprocessore 351

    3. Supponete che le variabili x, y e z in un programma siano di tipo f loat. Se esse hanno come valori rispettivamente l. l, 2.2 e 3.3, l'istruzione

    PRN3 (x, y, z) ; dovrebb provocare la tampa della riga:

    x vale 1.1, y vale 2.2 e z vale 3.3 Scrivete la definizione della macro PRN3 ( ) . 4.

    Supponete di avere il seguente codice in un file chiamato a_b_c.h:

    #define #define

    TRUE A_B_C int main(void) {

    printf("A Big Cheery \"hello\"1\n"); return 0;

    \ \ \

    \

    }

    e il seguente codice in un file chiamato a_b_c.c:

    #if TRUE #include #include "a_b_c.h• ABC #endifCompilando il programma, il compilatore segnala un problema. Perché? È possibile scambiare due righe in uno dei due file in modo che il programma possa essere compilato ed eseguito? Giustificate la risposta. 5.

    Non sempre le macro risultano sicure come le funzioni, anche se tutti i parametri nel corpo della definizione della macro sono scritti tra parentesi. Definite una macro

    MAX(x, y,

    Z)

    che produca un valore corrispondente al maggiore dei suoi tre parametri. Costruite alcune espressioni da utilizzare in MAX () che producano un risultato non previsto. 6. Verificate se tutte le macro predefinite risultano disponibili sul vostro sistema. Provate il codice seguente:

    printf("%s%s\n%s%s\n%s%d\n%s%d\n%s%s\n", "--DATE--

    -- FILE-LINE

    --STDC-==TIME==

    352 Capitolo 8

    7. Sui piccoli i temi, compilatori come Borland C Micro oft C forni cono modelli di memoria div r i, ognuno dei quali di solito richiede una pecifica d :finizione di ciò awi ne sul vo tro piccotipo per prtdi ff _t. E aminate stddefh per v d r lo i tema. Potete spi gar com mai i difi r nti mod Ili di memoria richi dano un proprio tipo prtdi ff _t? 8.

    In ANSI C i richied eh molt delle macro con param tri d finite n i fil d'intetazion standard iano di ponibili anch com fun zioni. n vo tro i tema forni c tali funzioni? Controllate, p r mpio, il vo tro i tema accetta il codic gu nte: #include

    /* contiene il prototipo di funzione? */

    i f ( ( isalpha) ( 'a' ) ) printf( "Found the isalpha() functionl \ n " ); Non siat r altà

    9.

    orpr i il vo tro i t ma non forni c l funzioni corri pond nti; in non ono molto utilizzat .

    11 C ha la fama di s r cc Il n t per il trattam nto d i caratt ri. Qu ta notori tà ' legata, in part , alla capacità di utilizzar ampiam nt d Il macro anziché delle funzioni n Il' laborazion d i caratteri. I programmatori rit ngono che ciò riduca in modo significativo i tempi d' uzion . Per v rificar ciò, cominciat crivendo un programma che faccia largo impi go d 11 macro in stdio.h ctype.h. #include #include #include int main(void) {

    int

    c;

    pr i ntf( "Clock ticks: %ld \ n " , clock()); /*avvia l'orologio * / while ( (c = getchar ()) l = EOF) if (islower(c)) putchar(toupper(c)); else if (isupper(c)) putchar(tolower(c)); else if (isdigit(c)) d nt programma (clock () vi n pr ntata n l Paragrafo c · una cifra crivete il caratt r x, c · un imbolo di punteggiatura non crivet nulla; c ' un caratt r di paziatura criv telo du volt . ubito prima d Ila fin di mai n (), criv t la riga: printf( "Clock ticks: %ld \ n " , clock()); / * scatti complessivi delle lancette */

    Il preprocessore 353

    Scrivete ora un'altra v r ion d l programma in cui ogni macro ia ostituita dalla funzion corri pondent . P re empio, al po todi islower (c) criv te: (islower)(c) Utilizzate la r dir zione per laborar fil di caratt ri con eia cuna v r ion d l vo tro programma. u piccoli fil non dovr t ri contrar molta differ nza nei t mpi di cuzion a cau a d l carico di lavoro d l i t ma, ma, man mano eh le dimen ioni dei fil aum ntano, le differ nz dovr bbero e r più n ibili È co i? Suggerimento: impartit il comando

    pgm




    output

    per evitar di sprecar inutilmente tempo tampando il fil 10.

    ullo chermo.

    In ANSI C, i file d'inte tazione standard pos ono re inclu i più volte e in un ordine qual ia i. Modificate la prima ver ion del programma critto per l'E ercizio 9 duplicando le righe #include all'inizio del file un c rto num rodi volte: #include #include #include #include #include #include





    l* ripetuto alcune volte */

    int main(void) {

    Il vo tro compilator re un po' più l nta, l'

    gnala d i probl mP bb n la ompilazion po a ri ultacuzion dovr bb avv nir alla t a v lo ità. È co ì?

    11.

    La macro isascii () ' citata n l t to com una macro di ctype.h. In r altà a non vi n p cificata n i docum nti AN I C (for il comitato AN I C non ha voluto favorir il codic A CII ri p tto ad altri). Controllat isascii () vi n fornita dal vostro si t ma.

    12.

    Scopo di qu to rcizio ' qu llo di ottolin ar una ottil diff r nza tra il C tradizional AN I C. N l C tradizional , tolower {) toupper () ono fomite com macro in ctype. h. In AN I C, l macro corri pondenti ono di ponibili in ctype.h, ma con i nomi _tolower() _toupper( ). Controllat eh iano di ponibili ul vo tro i t ma. Nel C tradizional ha n o utilizzare un'e pr ion com toupper (c) olo aP ndo già eh c ' una l ttera minu cola. In ANSI C, toupper () ' implem ntata com funzion l' pr sion toupper (c) ha n o indipendent ment dal valore int rodi c. c non ' una l tt ra minu cola toupper (c) non ha alcun fi tto,

    354 Capitolo 8

    r titui c quindi c. Un di cor o analogo vale per tolower (). Effettuate un rim nto ul vo tro i t ma per veder co a producono le espre sioni: tolower( ' a') 13.

    _tolower( ' a')

    toupper ( 'A' )

    p -

    _toupper( 'A')

    L'operator # tra forma in stringa il param tro pa ato a una macro, in erendo i doppi apici prima e dopo di es o. Cosa ucc d il parametro è già tra doppi apici? Scrivet un programma di prova contenent il gu nte codice: #define YANK(x) s = #x char *s; YANK( "Go home, Yankeel " ); printf( "%s\n M, s); Scriv t un'altra v r ione del vostro programma non contenente l'op ratore #,ed e guite entrambe le versioni. Quali c:lifi r nz i ri contrano tra gli output d Il due versioni? Utilizzate l'opzione -E (o l'opzion quivalente richie ta dal vostro si tema) per ottener il codice span o dal pr proc or . Ch co a fa di div r o il pr proce ore nel ca o ia pre ente l'operator #?

    14.

    Ch co a vien #define

    tampato? Spiegat perch . GREETINGS(a, b, c) \ printf(#a ", " #b " , and " #c "· Hellol\n ")

    int main(void) {

    GREETINGS(Alice, Bob, Carole); return 0; }

    O ervat ciò che viene prodotto dal pr proce or prima d Ila compilazion . Riucite a trovar GREETINGS? 15.

    Con iderat la dir ttiva: #undef

    TRY_ME

    TRY _ME ' tata definita prec d n t m n t con una macro #def i ne, que ta riga provo a l' liminazione della macro. TRY _ME non è stata definita in prec d nza la riga non dovr bb avere alcun ffetto. Scrivete del codice per v rificar che co a ucc de sul vo tro sistema. N l ca o in cui TRY_ME non sia tata definita prima, dit e il vostro si tema gnala d i problemi. 16.

    La macro assert () viene eliminata nel caso sia definita la macro NDEBUG. Succ de così anch sul vo tro istema? Provate il programma mostrato di seguito.

    Il preprocessore 355

    #define

    NO EBUG

    #include int mai n (v o id) {

    in t

    a = 1, b

    2",

    assert(a >b); return 0; }

    Ch 17.

    ambiando l prim du righ d l programma?

    N l programma pr tr righ gu nti:

    ntato n ll'E

    r izio 16, o tituit l du righ iniziali on l

    #include #define

    NO EBUG

    #include

    Il vo tro compilator C gnaJa d i probl mi? ondo voi dovr bb gnalam ? n 1fil d'int tazion asserl.h ' pr nt la riga: Suggerimento: controllat #undef 18.

    assert

    uppon t di tra t rir un programma C di grandi dim n ioni da una ma china con parol di 4 byt a una ma china con parol di 2 byt . u una macchina con paro] di 2 byt , i valori h un int può a um r i trovano appro imativam nt tra -32.000 +32.000. uppon t h qu to int rvallo di valori ri ulti troppo limitato p r alcun parti d l programma eh dov te tra ferire; potet provar a in rir la riga #define

    in t

    long

    in un fil d'int tazion eh venga inclu o in ognuno dei fil eh co titui vo tro programma. Dit qu to m todo funziona e pi gat p r h . 19.

    ono il

    C rcat , n l fil d'int tazion stddefh ul vo tro i t ma, la d finizion d l tipo size_ t. Efi ttuat la ri rea aneh in stdlib.h. uppon t di riv r d l codic eh inizi con: #include #include Poich le definizioni di tipo duplicat non funzionano, il vo tro i t ma d v r in grado di vitar eh la typedef di size_ t v nga inclu a du volt . pi gat com funziona qu to m ccani mo.

    356 Capitolo 8

    20.

    vol t utilizzar qsort () ' n c ario cono c r il uo prototipo di funzion Su alcuni i t mi AN I C o vi n fornito in stdlib.h com : void qsort(void *base, size_t nelem, size_ t width, int (*compare)(const void *, const void *)); Dite ul vo tro i tema il prototipo ' quivalente a qu to. Ricordat eh in un prototipo di funzione gli identificatori d i parametri v ngono ignorati dal compilator , dunque un prototipo di funzion equivalente è: void qsort(void *, size_t, size_t, int (*)());

    21.

    Nel programma qsorl v ngono utilizzate due funzioni di confronto. Ch co a uccede riscrivendo le funzioni di confronto in modo eh non corri pondano a quanto pecificato nell'ultimo parametro nel prototipo di funzion di qsort ( ) ? Ricrivetele come egu : int compare_decimal_ part(float *p, float *q) {

    float

    x· '

    x = decimal_ part(*p) - decimal_ part(*q); return ((x== 0.0)? 0: (x< 0.0)? - 1: +1); }

    int lexico(char *p, char *q) {

    return (*p- *q); }

    Cambiate anche i prototipi corri pond nti in mai n ( ) dit il programma vi n compilato. In ca o affermativo, aminat accuratam nt il prototipo di funzion di qsort () fornito ul vostro i t ma in stdlib.h: l'ultimo param tro ' d l tipo (compare *) ()? il programma non vi ne compilato, ' po ibil utilizzar un cast sull'ultimo parametro nelle due chiamate di qsort () in modo eh lo ia? 22.

    Scrivete un programma che provi il codic di quick ort, strato di eguito. #include #include #include #define void

    N

    10000

    quicksort(int *, int *);

    int main(void) {

    int

    a[N], i;

    cominciat com mo-

    Il preprocessore 357

    srand(time(NULL));

    /* inizializza il generatore di numeri casuali */ for (i = 0; i < N; ++i) a[i] = rand() % 1000; quicksort(a, a+ N - 1); Compi tate il programma criv ndo d l codic eh stampi gli l m nti d ll'array ordinato. guardar tutti gli l m nti vi tanca, pot t tampam olam n alcuni all'inizio, in m zzo alla fin d ll'array per capir o ia tato ordinato. 23.

    Nell'E rcizio 22 è tato ordinato un array di 10.000 l m nti int ri di tribuiti uniform m nt n ll'int rvallo [O, 999]. E guit di nuovo il programma, mi urandon il t mpo d' cuzion . Modi:ficat poi il programma in modo eh tutti gli 1 menti d ll'array iano di tribuiti uniform m nte n ll'int rvallo [O, 9]. Fomit un argom nto uri tico che pieghi com mai i t mpi ri ultino molto div r i.

    24.

    L'algoritmo quicksort può esser utilizzato per ordinare array di tutti i tipi, non olo array di interi. Ri crivetene il codice in modo che o po a v nir utilizzato per ordinar un array di stringhe. Scrivete un programma p r provar il vo tro codice.

    25.

    un array conti ne pochi elementi, per e empio 7 o meno, un hubble orto un ordinam nto p r tra po izion dovrebbe ri ultare più veloce d 1 quick ort. La gu nt v r ion di quicksort () ti n conto di ciò: int quicksort(int *left, int *right) {

    int static int

    *p, *q, pivot; cnt = 0;

    if (right - left < 7) { for (p = left; p < right; ++p) for (q = p+ 1; q *q) swap(*p, *q); }

    else if (find_ pivot(left, right, &pivot) -- yes) { p = partition(left, right, pivot); quicksort(left, p - 1); quicksort(p, right); }

    return ++cnt; }

    O rvat l'utilizzo d lla variabile cnt. nvalore re tituito all'ambi nt il num ro di volte eh la funzion vi ne chiamata. Controllat qu di quicksort () ' più v loce. Esi te una corr ]azione tra il tempo d'e numero di chiamate d Ha funzion ?

    358 Capitolo 8

    26.

    La di ponibilità di un buon algoritmo p r la deterrninazion d l pivot ri ulta crucia1 n l quick ort. Un mplic algoritmo consi te n l trovar du l m nti di tinti d ll'array c gli r com pivot il maggiore. Con que to algoritmo, quick ort impi ga un t mpo proporzionai a n 2 anziché a n log n u array già ordinati o qua i ordinati. Qu to a p tto ' importante perché molte volt gli array ono parzialm n t ordinati già prima di cominciare. Riscrivete il codic eli quick ort per impl mentare qu to algoritmo criv te un programma di prova eh mo tri il comportamento cadent di quick ort con array già ordinati. Dite eh cosa ucc d l'array iniziai in ordin inver o.

    27.

    N ll' s mpio fornito n l t to, il pivot viene celto tra tr l m nti d ll'array. Una scelta tra cinqu l menti riduce un po' il tempo d'e cuzion ; impl m ntat qu ta trategia ri crivendo il codic di quick ort, utilizzando d Il macro ov opportuno. Scrivet un programma per v rificare il t mpo d' cuzion ' minor . La trategia ottimale per la d t rrninazion d l pivot dip nd dai dati n ll'array. La c lta tra cinqu lem nti ' una trategia comun ; eh o a ucc d c gli ndo tra tte l m nti? i te ambiziosi provat anch qu ta trat gia.

    28.

    Suppon te eh a [ l ia un array di interi di lungh zza 100 tal eh p r ogni i l' lem nto a [i l valga i. Se vi ne chiamato quicksort (a, a + 99), quant chiamat alla funzione quicksort () vengono fi ttuate? Calcolat qu to num ro p r ogni ver ione di f ind_ pi vot ().

    29.

    n puntator

    r tituito da parti tion () vi ne utilizzato per divid r l'array di part nza in due ottoarray. La lunghezza d l primo ottoarray vien d tta lunghezza della partizione. M diante un generator eli num ri ca uali ri mpit un array di lunghezza 100. Chiamat find_pivot() partition() per d terminar la lunghezza della partizion per l'array. Fat qu to rip tutament , p r e mpio 100 volte, ten ndo traccia d lla media d Il lunghezz d Il partizioni. Ci i a p tta eh la lungh zza m dia d lle partizioni corrisponda a m tà dell'array; dit , in ba al vo tro p rimento, ciò vi mbra v ro.

    30.

    La lunghezza ottima! di una partizion di un array di lungh zza n è n/2. Qu to identifica du ottoarray di lungh zz uguali, o quasi uguali, p r un'ulterior laborazione. Per e mpio, dato un array di lunghezza 100, una lunghezza di partizione 50 ' ottimal . Si o ervi che una lungh zza di partizion di 49 id ntifica du ottoarray di lungh zz 49 e 51, m ntre una lungh zza di partizion di 51 id ntifica du ottoarray di lungh zz 51 49. Dunqu le lunghezze di partizione 49 51 i quivalgono. Modificate il programma critto nell'E ercizio 27 in modo che t nga traccia d lla media dei valori as oluti d lla differenza k - 50, dov k ' la dim n ion d lla partizione ott nuta da parti t io n (). Qu sto num ro corrispond inv rament alla bontà d Ila lunghezza d Ila partizion . Più in generale, definit lk- (n/2)1

    m =- - - n

    Il preprocessore 359

    dov k ' la lungh zza d Ila partizion ott nuta da parti t io n () u un array di lungh zza n. Ri mpit gli array in mani ra ca ual d guit d gli perim nti u una macchina p r di cut r u m. 31.

    32.

    33.

    4.

    Dov ndo ordinar una piccola quantità di dati ri ultano conv nienti il hubble orto l'ordinam nto p r tra posizione; in pre nza di una grande quantità di dati ' po ibil utilizzar qsort (). bben qsort () ia c rtam nt molto più v loc di un bubbl ort, o non raggiunge la v locità di quicksort ().n vantaggio di qsort () ri p tto a quicksort () ta nel fatto eh può v nir impl m ntato con poch righ di codic ; quicksort () tuttavia, ' molto più v loc di qsort (). riv t un programma eh dimo tri ciò. N l vo tro programma dichiarat du array grandi, ri mpit il primo array con int ri di tribuiti uniform m nt copiat lo n l ondo. Mi urat il t mpo con cui qsort () ordina il primo array il t mpo con cui quicksort () ordina il condo. tampat qu ti valori il loro quozi nt . A au a d l carico d' cuzion , la dim n ion d l quozi nt dipend dali lungh zz d gli array da ordinar . E a dip nd anch da quanto forzo i ' po to n l m tt r b n a punto l'algoritmo di quick ort. In ogni ca o, ri ordat o annotat i quozi nti ott nuti. Chiunqu lavori riam n t con l ma hin dovr bb av r id a di quanto quicksort () ri ulti più v loc di qsort (). rcizio pr c d nt av t calcolato il quozi nt tra i t mpi di uzion ti per ordinar un grand array on qsort () con quicksort (). In qu rcizio i richi d di calcolar di nuovo qu ti quozi nti, prima con array di m nti di tribuiti uniform m nt n ll'int rvallo [O, 100000] poi con array di m n ti di tribuiti uniformemente n li' in t rvallo [O, l]. I ri ultati di qu to rcizio ono abbastanza orpr nd nti.

    chi

    Capitolo 9

    Strutture e unioni

    Il C è un linguaggio facilmente estendibile mediante macro memorizzate nei file d'intestazione e funzioni memorizzate in librerie, e anche definendo tipi di dati costruiti a partire dai tipi fondamentali. Un esempio di ciò è dato dal tipo array: esso è un tipo derivato utilizzato per rappresentare dati omogenei. Al contrario, il tipo struttura viene utilizzato per rappresentare dati eterogenei. Una struttura ha dei componenti, detti membri, ognuno dei quali ha un nome. Poiché i membri di una struttura possono essere di vari tipi, il programmatore può creare aggregati di dati adatti alla particolare applicazione.

    9.1

    Strutture

    Il meccanismo di struttura fornisce un modo per aggregare variabili di tipi differenti. Come semplice esempio viene definita una struttura che descrive una carta da gioco. I punti chiamati "pip" sulla carta rappresentano il suo valore numerico. Una carta da gioco come il tre di picche ha come punteggio (pip) 3 e come seme (suit) picche. Per memorizzare l'informazione necessaria a rappresentare una carta da gioco, è possibile dichiarare il seguente tipo struttura:

    struct card { int pips; char suit; };

    In questa dichiarazione struct è una parola chiave, card è il nome etichetta della struttura e le variabili pips e sui t sono i membri della struttura. La variabile pips assumerà d h e valori da l a 13 (da asso a re); la variabile sui t assumerà un valore tra c s che rappresentano rispettivamente fiori (clubs), quadri (diamonds), cuori (hearts) e picche (spades). 1

    l,

    l

    l,

    l

    l,

    l

    l

    362 Capitolo 9

    Questa dichiarazione crea il tipo eli dati derivato struct card. Essa è un esempio di tipo definito dall'utente. La dichiarazione può essere pensata come uno schema: essa crea il tipo struct card, ma non alloca memoria. Il nome etichetta, unito con la parola chiave struct, può ora essere utilizzato per dichiarare variabili di questo tipo. struct card

    c1, c2;

    Questa dichiarazione alloca memoria per gli identificatori c1 e c2 che sono di tipo st ruct card. Uno schema alternativo consiste nello scrivere struct card { in t pips; char suit; } c1 , c2; che definisce il tipo struct card e, allo stesso tempo, dichiara c1 e c2 di questo tipo. Per accedere agli elementi di una struttura viene utilizzato l'operatore di accesso ai membri " . " . Per esempio, i valori che rappresentano il tre di picche possono essere assegnati a c 1 nel modo seguente: c1.pips c1.suit

    =

    =

    3; 's';

    Un costrutto della forma

    structure_variable . member_name viene utilizzato come variabile nella stessa maniera in cui viene utilizzata una variabile semplice o un elemento di un array. Volendo che c2 rappresenti la stessa carta di c1 è possibile scrivere: c2

    = c1;

    Questo fa sì che a ogni membro di c2 venga assegnato il valore del membro corrispondente di c1. Di solito i programmatori utilizzano il meccanismo typedef quando trattano tipi strutturati. Un esempio è il seguente: typedef

    struct card

    card;

    Se si desiderano ora altre variabili che rappresentino carte da gioco è possibile scrivere: card

    c3, c4,

    es;

    Si osservi che l'identificatore card è utilizzato due volte nella definizione di tipo. In C lo spazio dei nomi per le etichette è separato da quello degli altri identificatori, dunque la definizione di tipo per card è corretta.

    Strutture e unioni 363

    I nomi dei membri all'interno di una struttura devono essere unici. Tuttavia, i membri di strutture differenti possono avere lo stesso nome; ciò non crea confusione in quanto per accedere a un membro è sempre necessario un identificatore della struttura o un'espressione. Si consideri il codice seguente: struct fruit { char *name; int calories; }; struct vegetable { char *name; int calories; }; struct fruit struct vegetable

    a·, b;

    Date queste dichiarazioni, risulta chiaro che è possibile accedere senza ambiguità ad a. calories e a b. calorie s. Le strutture possono essere complicate. Esse possono contenere membri che sono a loro volta array o strutture. Inoltre, sono possibili array di strutture. Prima di fornire alcuni esempi, viene data la sintassi di una dichiarazione di struttura:

    structure_declaration ::= struct_specijier declarator_list ; struct_specijier ::= st ruct tag_name l struct tag_nameopt { { member_declaration h. } tag_name ::= identijier member_declaration ::= type_specijier declarator_list declarator_list ::= declarator { , declarator }+ Un esempio è: struct card { int pips; char suit; } deck[52]; In questa dichiarazione, deck è un array di struct card. Se non viene fornito un nome etichetta, allora il tipo struttura non può venire utilizzato in dichiarazioni successive. Un esempio è struct { int day, month, year; char day_name[4]; char month_name[4]; } yesterday, today, tomorrow;

    l* Lun, Mar, Mer, ecc. *l l* Gen, Feb, Mar, ecc. *l

    364 Capitolo 9

    che dichiara yesterday, today e tomorrow per rappre entar tre date. Non es ndo presente il nome etichetta, non è possibile dichiarare succe sivamente altre variabili di que to tipo. Al contrario, la dichiarazione struct date { int day, month, year; char day_ name[4]; char month_name[4]; };

    l* Lun, Mar, Mer, ecc. *l l* Gen, Feb, Mar, ecc. *l

    ha date come nome etichetta della struttura, ma non dichiara variabili di qu to tipo. E a può es ere vista come uno chema. Variabili di qu sto tipo pos ono sere dichiarate scrivendo: struct date

    yesterday, today, tomorrow;

    Associare un nome etichetta a un tipo struttura viene di olito considerata una buona pratica di programmazione e risulta conveniente sia per ulteriori dichiarazioni che come documentazione. Tuttavia, quando typedef viene utilizzata per dare un nome a un tipo struttura, il nome etichetta non è importante. Un e empio è: typedef struct { float re; float im; } complex; complex

    a , b , c [ 100 ] ;

    Il tipo complex sostituisce in questo caso il tipo struttura. Utilizzando typedef per dare un nome ai tipi derivati e scrivendo queste definizioni nei file d'intestazione, il programmatore ottiene un alto grado di portabilità e modularità.

    9.2

    Accesso ai membri di una struttura

    In questo paragrafo vengono cliscussi i metodi per accedere ai membri di una struttura.

    È stato mostrato in precedenza l'utilizzo dell'operatore "." p r accedere ai m mbri. Vengono ora forniti ulteriori esempi del suo impiego e viene introdotto l'operatore di accesso ai membri - >. Si upponga di scrivere un programma di nome class_info che gen ri informazioni relative a una da se di 100 studenti. Si inizia creando un file d'inte tazione.

    Nel file class_info.h: #define

    CLASS_SIZE

    struct student {

    100

    Strutture e unioni 365

    char int char

    *last name; student id; grade; -

    };

    Qu sto file d'intestazion può es ere ora utilizzato per condivid re informazioni con i moduli che co titui cono il programma. Si supponga di scriv re in un altro file: #include Hclass_info.h H int main(void) {

    struct student

    tmp, class[CLASS_SIZE];

    È po i bile assegnar d i valori ai membri della variabile truttura tmp utilizzando i truzioni come:

    tmp.grade = 'A'; tmp.last name = "CasanovaM; tmp.student_id = 910017; Si supponga ora di voler contare il numero di studenti di scarso rendimento in una data clas . A questo copo viene scritta una funzione fail () che conta il numero di voti F (cioè minimi) nell'array class []. È necessario accedere al membro gr ade di eia cun elemento dell'array di strutture. l* Conta i voti scarsi. */

    #include Hclass_info.h " int fail(struct student class[]) {

    int

    i, cnt = 0;

    for (i = 0; i < CLASS SIZE; ++i) cnt += class[i].grade 'F'; return cnt; }

    ANALISI DELLA FUNZIONE fail() •

    int fail(struct student class[]) {

    int

    i, cnt = 0

    n parametro class è un puntatore a struct lente per questo parametro potrebbe essere: struct student *class

    student. Una dichiarazione equiva-

    366 Capitolo 9

    class può e r vi ta com un array monodim n ional di truttur . I param tri di qualunqu tipo, compr i i tipi truttura, po ono s r utilizzati n Il int tazioni di funzion . •

    for (i = 0; i < CLASS_SIZE; ++i) i pr um eh , quando qu ta funzion vi n chiamata, v nga pa ato om param tro un array di tipo struct student di lungh zza CLASS_SIZE.



    cnt += class[i].grade == F Un' pr ion com que ta dimo tra quanto il C po a C ' ricco di op ratori, il programmator d v far att nzion tività per pot rio impiegare flu ntem nt . Qu ta i truzion 1

    1

    ;

    cnt += (((class[i]).grade) ==

    1

    il

    F 1 );

    lezionato il m mbro grade d 11' l m nto di po to i ( ontando da z ro) d ll'array di truttur c las s. Viene effettuato un t st p r v d r o ia ugual a F In ca o afi rmativo, il valor d Il' pr io n

    Vì n l

    l •

    class[i].grade ==

    1

    F

    1

    ' l e il valor di cnt vie n incr mentato. non val l'uguaglianza, allora il valor d ll'e pre ion ' O il valore di cnt non vi n modificato. •

    return cnt; ro di voti car i vie n r

    n num

    tituito all'ambiente chiamant .

    Il C fornisce l'op ratore - >di ace o ai m mbri di una truttura tramit un puntator (questo operator vi ne prodotto ulla ta ti ra digitando un gno m no guito da un gno di maggior ) . a una variabil puntator vi n as gnato l'indirizzo di una truttura, allora ' po ibil ace d re a un m mbro d Ila struttura p r m zzo di un cotrotto d lla forma:

    pointer_to_structure

    ->

    member_name

    Un co trutto quival n t a qu sto ' : (* pointer_to_structure)

    . member_name

    * (pointer_to_structure . member_name) Qu to ' un rror , in quanto l'op rator "." può non con puntatori a truttur .

    r utilizzato olo con truttur ,

    Strutture e unioni 367

    Vi n illu trato ora l'utilizzo di - > criv ndo una fun zion compi si. Si upponga innanzitutto h in un file d'int tazion gu nt typedef:

    addiziona num ri ia pr nt la

    Nel file complex.h:

    struct complex { re; double double i m;

    /* parte reale */

    /* parte immaginaria */

    };

    typedef

    struct complex

    criva poi in un altro fil quanto

    complex; gu .

    Nel file 2_add.c:

    #i nclude "complex.h " voi d add(complex *a, complex *b, complex *c)

    l* a

    b + c */

    {

    a - > re = b a -> i m b

    -> ->

    re + c - > re; i m + c - > i m;

    }

    io

    struct student , pr

    Dichiarazioni e assegnamenti

    struct student tmp, *p = &tmp; tmp.grade = ' A' ; tmp.last name = "Casanova " ; tmp.student i d = 910017; Espressione

    Espressione equivalente

    Valore concettuale

    tmp.grade tmp.last name (*p).student id * p - > last_name + *(p - > last_name + 2)

    p - > gr ade p - > last name p - > student i d (*(p - > last_name)) + 1 (p - > last_name)[2]

    A

    Ca anova 910017

    D

    nta-

    368 Capitolo 9

    9.3

    Priorità e associatività degli operatori: una panoramica finale

    Nella tabella succe siva sono mostrate tutte le regole di priorità e a sociatività di tutti li operatori del C. Gli operatori "." e -> sono stati introdotti in questo capitolo in ieme a ( ) e [ ] hanno priorità massima.

    Operatori -- (postjisso) ++ (prefisso) -- (prefisso) l - sizeof (type) + (unario) - (unario) & (indirizzo) * (dereferenziazione) ()

    []

    ++ (postfisso)

    ->

    da da da da da da da da da

    %

    * + >

    >=

    l=

    &

    && Il

    tra tra tra tra tra tra tra tra tra

    ?:

    • >>=

    +=

    *=

    ->

    pips; suit;

    if (suit == clubs) suit_name = "clubs"; else if (suit == diamonds) suit_name = "diamonds"; else if (suit == hearts) suit_name = "hearts•; else if (suit == spades) suit_name = "spades"; printf("card: %2d of %s\n", pips, suit_name); }

    Nella funzione prn_card_values (), pips e sui t vengono utilizzate come variabili locali. n compilatore non può confondere questi identificatori con i membii di card in quanto i membri di una struttura sono accessibili solo tramite gli operatori "." e ->. La funzione successiva è play _poker ( ) . Essendo la funzione principale del programma e contenendo alcuni concetti fondamentali, essa viene esaminata in dettaglio.

    374 Capitolo 9

    void play_poker(card deck[521) {

    flush_cnt = 0, hand_cnt = 0; i, j; han d [ NPLAYERS 1 [51; l* ogni giocatore riceve 5 carte *l

    int in t card

    l* inizializza il generatore di numeri casuali *l for (i = 0; i < NDEALS; ++i) { shuffle(deck); deal_the_cards(deck, hand); for (j = 0; j < NPLAYERS; ++j) { ++hand cnt; if (is=flush(hand[j1)) { ++flush_cnt; printf("%s%d\n%s%d\n%s%f\n\n", " Han d number: " , han d cnt, Flush number: ", flusfi cnt, • Flush probabili ty: ", (double) flush_cnt l hand_cnt);

    srand(time(NULL));

    } } } }

    ANALISI DELLA FUNZIONE play_poker() •

    card hand [ NPLAYERS] [ 5]; l* ogni giocatore riceve 5 carte *l L'identificatore hand è un array bidimensionale. Esso può anche essere immaginato come un array di array. Poiché la costante simbolica NPLAYERS vale 6, hand corrisponde a 6 mani, ognuna di 5 carte.



    l* inizializza il generatore di numeri casuali *l La funzione srand () viene utilizzata per inizializzare il generatore di numeri casuali chiamato da rand (). 11 valore fornito come parametro a s rand ( ) è detto seme. In questo caso è stato utilizzato come seme il valore restituito dalla chiamata di funzione t ime ( NULL). Questa funzione si trova nella libreria standard e il suo prototipo nel file d'intestazione time.h. Poiché t ime ( ) restituisce un valore intero ottenuto dal dock interno, a ogni esecuzione del programma il seme risulta differente, provocando la generazione da parte di rand () di numeri differenti. Dunque, a ogni esecuzione del programma si ottengono mani differenti.



    for (i = 0; i < NDEALS; ++i) { shuffle(deck); deal_the_cards(deck, hand);

    srand(time(NULL));

    Strutture e unioni 375

    Prima della distribuzione delle carte, il mazzo viene mescolato; come mostrato successivamente, questa operazione viene simulata utilizzando rand ( ) . •

    for (j = 0; j < NPLAYERS; ++j) { ++hand_cnt; if (is_flush(hand[j])) { ++flush_cnt; Dopo la distribuzione, vengono controllate le carte di ogni giocatore per vedere se ha un colore. In caso affermativo flush_cnt viene incrementato e si stampa.

    A questo punto è necessaria la funzione che mescola le carte. Essa è molto semplice: si muove lungo tutto il mazzo scambiando ciascuna carta con un'altra scelta a caso. Si osservi che il numero 52 è riportato nella definizione di funzione solo per facilitare il lettore; il compilatore lo ignora.

    void shuffle(card deck[52]) {

    int

    i, j;

    for (i = 0; i < 52; ++i) { j = rand ( ) % 52; swap(&deck[i], &deck[j]); } }

    void swap(card *p, card *q) {

    card

    tmp;

    tmp = *p; *p *q; *q = tmp; }

    Sebbene la funzione swap () venga utilizzata per scambiare strutture, la forma generale resta la stessa. void deal the cards(card deck[52], card hand[NPLAYERS][SJ)

    -

    {

    int

    -

    card_cnt

    = 0,

    i, j;

    for (j = 0; j < 5; ++j) for (i = 0; i < NPLAYERS; ++i) hand[i][j] = deck[card_cnt++J; }

    376 Capitolo 9

    Poiché il compilator ignora la prima lunghezza in un array eh compar in una dichiarazione di parametri, un'inte tazione equivalente p r que ta funzion è: void deal_ the_ cards(card deck[], card hand[][S]) Inoltr , poiché gli array nell dichiarazioni di parametri ono in realtà puntatori, un'altra inte tazione quivalente è data da: void deal_ the_ cards(card *deck, card (*hand)[5]) In ognuna di queste inte tazioni il numero 5 risulta neces ario al compilator per g n rare la mappa di memorizzazion corretta. Si noti che n lla funzion l cart ono di tribuit una alla volta a eia cun giocatore. Al contrario, il codice for (i = 0; i < NPLAYERS; ++i) for (j = 0; j < 5; ++j) hand[i][j] = deck[cnt++); ha come effetto quello di distribuire insieme 5 cart a ciascuno d i giocatori. D sid rando simulare il gioco del poker come realmente vi ne giocato, il codic utilizzato in de al_ the_ cards () ri ulta preferibile. int is flush(card h[5])

    -

    {

    in t

    i•

    '

    for (i = 1; i < 5; ++i) if (h[ i] .suit l= h[0] .suit) return 0; return 1; }

    La funzione is_ flush () controlla

    le carte in una mano iano tutt dello te o m . E sa viene richjamata dalla funzione play_ poker () con l' pr ion is_flush (han d [ j]). Si ricordi che hand è un array di array dunque hand [ j] t o ' un array di tipo card; quando viene chiamata is_flush () i ace d agli lem nti di que to array.

    9.7

    Unioni

    Un'unione, come una struttura, è un tipo derivato. La inta si delle unioni ' com quella delle struttur , ma i membri delle unioni condividono la memoria. Un tipo unione definisce un insieme di valori alternativi che po sono e r cont nuti in una porzione di memoria condivisa. n programmatore ' respon abile della corretta interpretazione d i valori memorizzati.

    Strutture e unioni 377

    i con id ri la di hiarazion union int or float { int - iT float f; }; In qu ta dichiarazion union ' una parola chiav , int_or _float ' il nom d ll'union l variabili i d f ono i m mbri d ll'union . Qu ta dichiarazion cr a il tipo di dati d rivato union int_or _float. La dichiarazion può r con id rata om uno eh ma: a cr a il tipo nza allocar m moria. Il nom tich tta, accompagnato dalla parola chiav union, ' ora utilizzabil p r dichiarar variabili di qu to tipo. union int_or_float

    a, b, c;

    Qu ta dichiarazion ri rva m moria per gli identificatori a, b c. Il compilator allo a p r ogni variabil una porzion di memoria sufficiente a cont n r il più grand tra i m mbri p cificati. La notazione impiegata per accedere a un membro di un'union ' id ntica a qu Ila utilizzata per acced re ai membri delle struttur . N 11' mpio eguente viene mostrato come ciò che ' memorizzato in un'union po a r int rpr tato in più modi.

    N el file numbers. c: typedef union int_or_float { int i; float f; } number; int main(void) {

    number

    n·,

    n.i = 4444; printf( "i : %10d n.f = 4444.0; printf( "i: %10d return 0;

    f: %16. 10e \ n " , n. i, n. f) ; f: %16. 10e \n " , n. i, n. f);

    }

    In qu to emplic p rim nto vi n mo trato com il no tro i t ma ovrapponga un i nt un float. L'output d l programma ' dipend nt dal i t ma; eco che co a ha prodotto il no tro: i: 4444 i: 1166729216

    f: 6.227370375e - 41 f: 4.4440000000e+03

    378 Capitolo 9

    ANALISI DEL PROGRAMMA int_ or_ float •



    typedef union int_ or_float { int i; float f; } number; Come nel caso delle strutture, è possibile utilizzar il m ccani mo typedef per abbreviare nomi lunghi di tipo. number

    n;

    n tipo number è equivalente a union

    int_or _float. Con qu ta dichiarazion il sistema alloca spazio per la variabile union n. Lo pazio d v e r in grado di contenere il più grande dei due membri di n. Suppon ndo eh un in t ia m morizzato in 2 o in 4 byte e un float ia memorizzato in 4 byt , il i t mari rv rà 4 byte per n.



    noi = 4444; printf("i: %10d f: %16o10e\n", noi, nof); Al membro n. i di tipo int viene ass gnato il valor int 4444. Quando il valor di n oi viene stampato in formato decimale, i ottiene naturalment 4444. Tuttavia, s la ste sa porzione di memoria vien int rpr tata come un num ro r aie, tampando n of nel formato %e, i otti ne un ri ultato compi tam n t div r o da 4444.



    n.f = 4444o0; printf("i: %10d f: %16.10e\n", noi, nof); Si proc de ora nell'altro modo. Al m mbro n of di tipo f lo a t vi n as gnato il valore f lo a t 4444.0. Quando il valore di n of vi ne tampato n l formato %e i otti ne il valor corretto, ma stampando la t a porzion di m moria n l formato %d, il risultato ottenuto è completam nt difi r nt .

    n si

    tema interpreta lo st sso valore memorizzato in ba

    al m mbro

    lezionato;

    è responsabilità del programmatore ffettuar la c lta corretta.

    Le unioni vengono utilizzate in applicazioni che richiedono int rpr tazioni multiple per una data parte di memoria. Più comun mente, e e v ngono impi gat p r riparmiare m moria, permettendo l'utilizzo dello stes o spazio p r una varietà di tipi. I membri di un'union po ono er strutture o altre unioni; mpio: struct flower { char enum {red, white, blue} }; struct frui t { char *name; int calories; };

    •name; color;

    Strutture e unioni 379

    struct vegetable { char *name; int calories; int cooking_time; };

    l* in minuti *l

    union flower_fruit _or_ vegetable { struct flower flw; struct fruit frt; struct vegetable veg; }; union flower_fruit _or_ vegetable Dat qu

    t dichiarazioni, ' po ibile utilizzar un'i truzion com

    ffv.veg.cooking _time p ra

    9.8

    ffv;

    = 7;

    gnar un valor al m mbro cooking_time d l m mbro veg d ll'union ffv.

    Campi di bit

    n m mbro int o unsigned di una truttura può er dichiarato com compo to da uno p ificato num ro di bit. Tal m mbro ' chiamato campo di bit, il num ro di bit a o iato ' la ua ampiezza. L'ampi zza vien pecificata da una co tant non n gativa in t ra pr c d uta da du punti (:). L'ampi zza ' pari, al ma imo, al num ro di bit in una parola d Ila macchina. Tipicam nt , i campi di bit v ngono di hiarati om m mbri on utivi di una truttura v ngono ompattati dal ompilator in un num ro minimo di par l macchina. Il gu nt ' un mpio: struct pcard { unsigned pips unsigned suit };

    l* rappresentazione compattata *l 4•J 2•J

    na variabil di tipo struct pcard ha un campo di 4 bit di nom pips, in grado di m morizzar uno tra 16 valori compr i tra i num ri da O a 15, un ampo di 2 bit di nom sui t, in grado di m morizzar uno d i valori O, l , 2 o 3, utilizzabili p r rappr ntar ri p ttivam nt fiori, quadri, cuori picch . P rtanto i 13 valori pips i 4 valori n tar l cart da gio o po ono r rappr ntati in mas ui t n c ari p r rappr ni ra compatta in 6 bit Si upponga di criv r la dichiarazion : struct pcard

    c·J

    P r a s gnar a c il nov di quadri ' po ibil scriv r : c.pips = 9;

    380 Capitolo 9

    c.suit

    = 1;

    La inta i p r un campo di bit in una struttura o unione ' data da: bit_field_member ::= { int l unsigned }1 { identifier expr ::= constant_integral_expression

    }opt :

    expr

    A conda d Ila macchina, il compilator as gna i bit da ini tra a d tra o da d stra a ini tra. Alcune macchine a egnano campi di bit eh i t ndono attrav r o i confini tra l parol , ma nella maggior part d i ca i ciò non a cad . Dunqu , u una macchina con parol di 4 byte, dichiarando struct abc { in t a : 1 , b : 16, c : 16;

    } x;

    x v rrà g n ralment m morizzata su du parol , con a conda. N i campi di bit unsigned po ibil m morizzar

    olo valori non negativi; ciò eh accad in campi di bit in t dipend dal i tema. u alcuni i t mi il bit più ignificativo del campo vi n trattato com bit di gno (v di E rcizio 25). N lla maggior parte d Ile applicazioni vengono utilizzati campi di bit unsigned. n motivo principale per utilizzare campi di bit è ri panniar m moria. Su macchin con paro l di 4 byte ' po ibil m morizzar in un'unica parola 32 variabili di l bit. In alt mativa ' po ibil utilizzar 32 variabili char; chiaram nt la prima oluzion p rm tte un not voi ri pannio di memoria. E i tono tuttavia alcune r trizioni: non ono perm i array di campi di bit, inoltr l'operator d'indirizzo & non può re applicato ai campi di bit. Ciò ignifica eh un puntator non può r utilizzato per indirizzar dir ttam nt un campo di bit, bb ne ia po ibil l'utilizzo d ll'op rator di ace o ai m mbri - >. Ultimo punto: ' po ibil utilizzar a copo di ri mpim nto di allin am nto campi di bit privi di nom . i upponga di av r una macchina con parol di 4 byt di voler una truttura contenente i campi di 7 bit, tre d i quali n Ila prima parola tr n Ila conda. Ciò può e re realizzato mediant la dichiarazion gu nt : struct small_ integers { unsigned i1 7, i2 11 , i4

    7, i3

    7,

    /* allinea alla parola successiva */

    7, i5 : 7, i6 : 7;

    };

    Un altro metodo per provocar l'allineamento alla parola ucc siva con i t nell'utilizzare campi di bit privi di nome di ampiezza z ro. Si consideri il codice: struct abc {

    Strutture e unioni

    unsigned

    381

    1 ,.

    a : 1 , : 0, b : 1 , : 0, c

    };

    iò cr a tr campi da l bit in tr paro)

    9.9

    parate.

    Un esempio: accesso ai bit e ai byte

    ln qu to paragrafo vi n fornito un mpio di com ia po ibil ace d r ai bit ai byt di una parola in m moria. Una po ibilità ' qu ila di utilizzar gli operatori ori ntati ai bit l ma h r , com pi gato n i Paragrafi 7.1 e 7.2. Vi n qui mo trato come ciò ia po ibi1 utilizzando campi di bit n programma pr ntato ' per macchin con parol di 4 byte.

    Nel file check_bits.c: #include typedef struct { unsigned b0 } word_ bytes; typedef struct unsigned 1, b0 1, b6 1, b12 1, b18 b24 1, 1, b30 } word_bits;

    a,

    8, b1

    b2

    a,

    b3

    a;

    {

    b1 b7 b13 b19 b25 b31;

    1, 1, 1, 1, 1,

    b2 b8 b14 b20 b26

    1, 1, 1, 1, 1,

    b3 b9 b15 b21 b27

    1, 1, 1, 1, 1,

    b4 b10 b16 b22 b28

    1, 1, 1, 1, 1,

    b5 b11 b17 b23 b29

    1, 1, 1, 1, 1,

    typedef union { i•, in t word bits bit; word- bytes byte; } word; int main(void) {

    word void

    w = {0}; bit_ print(int);

    w.bit.b8 = 1; w. by t e . b0 = a printf( "w.i = %d\n " , w.i); bi t _ p r i nt (w . i) ; return 0; l

    l

    ;

    }

    di in mai n () viene mo trato com ia pos ibile accedere a un parti olar bit o di una parola. La funzion bi t _ print (), pre entata nel Paragrafo 7.3, vi n utiliz-

    382 Capitolo 9

    zata per vedere che cosa accade esattamente alla parola in memoria. Poiché il modo con cui i bit e i byte vengono contati (dal più significativo o dal meno significativo) varia da macchina a macchina, l'utilizzo di un'utility come bi t _ pr in t () è es enziale. Di seguito è riportato l'output ottenuto dal programma su una particolar macchina, w.i = 353 00000000 00000000 00000001 01100001

    mentre su un'altra macchina si è ottenuto: w.i = 1635778560 01100001 10000000 00000000 00000000

    Poiché le macchine variano relativamente alla dimensione delle parole e al modo con cui vengono contati bit e byte, il codice che impiega campi di bit può non risultare portabile.

    9.1 O Tipo di dati astratto stack (ADT) Il termine tipo di dati astratto (ADT, da abstract data type) viene utilizzato in informatica per indicare una struttura dati con le relative operazioni, senza specificare un'impl~ mentazione. Si supponga di volere un nuovo tipo intero eh possa memorizzare valori arbitrariamente grandi. Il nuovo tipo intero, insieme con l ue op razioni aritmetich ' un ADT. È compito di ogni singolo istema determinare come i valori di un dato int ro iano rappresentati e manipolati. Tipi nativi come char, int e double vengono impl~ mentati dal compilatore C. Spesso i tipi definiti dal programmatore sono implementati mediante trutture. In questo paragrafo viene sviluppato e implementato l'ADT stack, una della più utili trutture dati standard. Uno stack è una struttura dati che permett inserimenti e cancellazioni di dati solo in una specifica posizione, la cima (top) dello stack. Que ta è la disciplina LIFO (last-in-first-out, l'ultimo a entrare è il primo a uscire). Concettualment uno stack si comporta come una pila di vassoi in un self-service; essa ere ce o si abbassa quando i va oi v ngono aggiunti o rimossi. Le operazioni tipiche u uno tack sono push, pop, top, empty, full e reset. L'operatore push pone un valore ullo tack. L'operator pop recupera d elimina il valore in cima allo stack. L'operator top r stituisce il valore in cima allo stack. L'op ratore empty verifica se lo stack è vuoto. L'operatore full verifica se lo stack ' pieno. L'operatore reset cancella o inizializza lo tack. Lo stack con queste operazioni è un tipo di dati astratto. Il contenuto dello stack viene memorizzato in un array di char di lungh zza fis ata (sono possibili altre scelte implementative: nel Paragrafo 10.5 lo tack viene implementato mediante una lista concatenata). La cima dello stack è un membro di valore intero di nome top. Le varie operazioni sullo stack vengono implementate come funzioni, ognuna delle quali contiene n l proprio elenco dei parametri un parametro di tipo puntatore

    Strutture e unioni

    383

    a stack. L'utilizzo di un puntatore evita di dover ricopiare uno stack potenzialmente grande per svolgere un'operazione elementare. Nel file stack. c: /*Un'implementazione del tipo stack . */ #define #define #define

    MAX_LEN EMPTY FULL

    typedef

    enum boolean {false, true}

    1000 -1

    (MAX_LEN - 1) boolean;

    typedef struct stack { char s[MAX_LEN]; int top; } stack; void reset(stack *stk) {

    stk - > top

    =

    EMPTY;

    }

    void push(char c, stack *stk) {

    stk -> top+ +; stk -> s[stk - > top]

    = c;

    }

    char pop(stack *stk) {

    return (stk - > s[stk - >top-- ]); }

    char top(const stack *stk) {

    return (stk -> s[stk - >top]); }

    boolean empty(const stack *stk) {

    return ((boolean) (stk - >top

    EMPTY));

    }

    boolean full(const stack *stk) {

    return ((boolean) (stk - >top }

    FULL));

    384 Capitolo 9

    ANALISI DELlJMPLEMENTAZIONE DEL TIPO stack •

    typedef enum boolean {false, true} boolean; Mediante questa typedef viene dato un nuovo nome, boolean, al tipo enumerativo enum boolean. Si osservi che questo nuovo nome coincide con il nome etichetta; si tratta di una pratica frequente.



    typedef struct stack { char s[MAX_LEN]; int top; } stack; Questo codice dichiara il tipo struttura struct stack e utilizza contemporaneamente una typedef per dare a struct stack il nuovo nome stack. Un modo equivalente per fare ciò è il seguente: struct stack { char s[MAX_LEN]; int top; };

    typedef

    struct stack

    stack;

    La struttura ha due membri, il membro array se il membro top di tipo int.



    void reset(stack •stk) {

    stk

    ->

    top

    = EMPTY;

    }

    Al membro top nello stack puntato da stk viene assegnato il valore EMPTY. Concettualmente questa operazione cancella il contenuto dello stack, rendendolo vuoto. Nell'ambiente chiamante, se st è uno stack, per effettuare questa operazione è possibile scrivere: reset(&st); All'inizio del programma, di solito si inizia con uno stack vuoto. •

    void push(char c, stack •stk) {

    stk stk

    ->

    ->

    top++; s[stk -> top]

    }

    char pop(stack* stk) {

    c;

    Strutture e unioni 385

    return (stk -> s[stk ->top--)); }

    L'operazione push viene implementata come una funzione con due parametri. Viene prima incrementato il membro top. Si osservi che: stk -> top++

    è equivalente a:

    (stk - > top)++

    In seguito viene introdotto in cima allo stack il valore c. Questa funzione si basa sull'ipotesi che lo stack non sia pieno. L'operazione pop viene implementata in maniera simile. Per essa viene ipotizzato che lo stack non sia vuoto. Il valore dell'espressione stk -> top--

    è il valore memorizzato correntemente nel membro top. Si supponga che esso sia 7; viene quindi restituito stk -> s[7] e viene poi decrementato il valore di top memorizzato, che diviene così 6. •

    boolean empty(const stack •stk) {

    return ( ( boolean) .( stk -> top

    EMPTY));

    }

    boolean full(const stack •stk) {

    return ((boolean) (stk ->top== FULL)); }

    Ognuna di queste funzioni effettua un controllo sul membro top di stack, restituendo un valore true o false di tipo boolean. Si supponga che l'espressione stk -> top == EMPTY nel corpo di empty () risulti vera; l'espressione ha valore int l. Questo valore viene convertito al tipo boolean, ottenendo true, che è ciò che viene restituito. Per provare questa implementazione degli stack, si ponga il codice precedente in un file .c a cui venga aggiunto alla fine il seguente codice. La funzione mai n () inserisce i caratteri di una stringa in uno stack, li preleva e li stampa uno alla volta. n risultato è la stampa dei caratteri in ordine inverso rispetto a quello in cui sono tati introdotti nello stack.

    386 Capitolo 9

    N el file stack. c: /* Prova dell ' implementazione dello stack rovesciando una stringa. */ #include int ma in (v o id) {

    char str[] = "My name is Laura Pohll"; int i; stack s; rese t ( &s) ; l* in izializza lo stack */ printf(" In the string: %s\n", str); for (i = 0; str[i] l = '\0'; ++i) if ( lfull(&s)) push(str[i], &s); /*inserisce un carattere sullo stack */ printf ( "From the stack: "); while (lempty(&s)) putchar(pop(&s)); /* preleva un carattere dallo stack */ putchar(' \n'); return 0; }

    L'output di questo programma di prova è: In the string: My name is Laura Pohll From the stack: llhoP aruaL si eman yM Si osservi che nelle chiamate di funzioni che utilizzano lo stack viene impiegata come parametro l'espressione &s, che è l'indirizzo della variabile stack s. Poiché ognuna di queste funzioni richiede un puntatore di tipo stack *,l'espressione &s risulta appropriata.

    9.11

    Riepilogo

    l.

    Strutture e unioni sono i metodi principali con i quali il programmatore può definire nuovi tipi.

    2.

    Mediante typedef è possibile associare nuovi nomi ai tipi. I programmatori utilizzano normalmente typedef per dare un nuovo nome a un tipo struttura o unione.

    3.

    Una struttura è un aggregato di componenti che vengono trattate come un'unica variabile. Le componenti della struttura sono chiamate membri.

    Strutture e unioni 387

    4.

    I membri delle strutture sono accessibili per mezzo dell'operatore di accesso ai membri".". Se s è una variabile struttura con un membro di nome m, respressione s. msi ru risce al valore del membro mdella truttura s.

    5. I membri di strutture sono accessibili anche per m zzo dell'operatore di acce so ai membri - >. Se p è un puntatore a cui è tato a gnato il valore &s, anche l'espressione p - > mfa riferimento a s. m. Sia"." eh - >hanno la massima priorità tra gli operatori del C. 6. In ANSI C, s a b sono due variabili d llo t o tipo truttura l'e pr ion di a egnam nto a = b è corretta. Essa fa ì che a ogni m mbro di a venga as egnato il valore del corrispondente membro di b. Inoltr , un' pr ione struttura può r pa ata com param tro a una funzion re tituita da una funzione. Molti compilatori del C tradizionale hanno qu sta possibilità, ma non tutti. 7. Quando una variabile struttura viene passata a una funzione come parametro, essa vi ne pa ata "per valore". Se però una struttura ha molti membri, o membri eh ono grandi array, ciò può risultare inefficiente. Riscrivendo la definizione di funzion in modo che essa utilizzi un puntator alla struttura invece che la struttura t sa, si vita la creazione della copia locale. Un'unione è simile a una struttura, tranne per il fatto che tutti i membri dell'unione condividono lo stesso spazio in memoria. Le unioni vengono utilizzate principalmente per risparmiare memoria. Lo spazio allocato per una variabile unione può sser utilizzato per contenere svariati tipi, specificati dai membri dell'unione. È r pon abilità del programmator apere quale sia la rappres ntazion corr ntem nte memorizzata in una variabile union . I membri di truttur e unioni po sono ss r array o altre trutture e unioni. Innestando array, strutture e unioni è possibile ottenere una consid revol compi ità. È necessario verificare con cura l'ace o all variabili corrett . l .

    Un campo di bit ' un membro int o unsigned di una truttura o unione, formato da un numero di bit specificato. Il numero di bit è dato da un'espressione costante int ra pr ceduta da due punti: que to num ro viene chiamato ampiezza d l campo di bit. L'ampiezza non può superare il numero di bit di una parola della macchina. Campi di bit con ecutivi in una struttura vengono memorizzati di solito in bit contigui di una parola della macchina, s mpre eh sa sia in grado di contenerli.

    11. È po sibile utilizzare campi di bit privi di nom ; ssi sono utili per riempire una parola o a scopo di allineamento. n campo di bit enza nome e di ampiezza O ' p dale: sso provoca un allineamento immediato alla parola uccessiva. 12. L'implementazione dei campi di bit dip nde dal istema, quindi il loro utilizzo non risulta portabile. Comunque es i trovano impieghi di una certa importanza.

    388 Capitolo 9

    13.

    Lo stack è un tipo di dati astratto (AD1) con molteplici impieghi, specialmente in informatica. Un ADT può essere implementato in molte maniere differenti.

    Esercizi

    9.12 l.

    In alcune situazioni è possibile sostituire una typedef con una #def in e. Di seguito è riportato un semplice esempio: typedef

    float

    DOLLARS;

    int main(void) {

    DO LLAAS

    amount

    = 100.0,

    = %.2f\n",

    printf("DOLLARS return 0;

    interest

    = 0.07

    * amount;

    amount + interest);

    }

    Eseguite il programma per comprenderne il comportamento, poi sostituite l'istruzione typedef con: #define

    DOLLARS

    float

    Dite se, ricompilando il programma ed eseguendolo, si ottiene lo stesso comportamento di prima.

    2. In alcune situazioni una typedef non può essere sostituita da una #define. Considerate il seguente programma: typedef

    float

    DOLLARS;

    int main(void) {

    DO LLAAS {

    amount

    = 100.00,

    interest

    = 0.07

    l* nuovo blocco float DOLLARS; DOLLARS = amount + interest; printf("DOLLARS = %.2f\n", DOLLARS);

    * amount; */

    }

    return 0; }

    Eseguite il programma per comprenderne il comportamento. Nel caso l'istruzione typedef venga sostituita con la riga #define

    DO LLAAS

    float

    Strutture e unioni 389

    il programma non viene compilato. Spiegate per quale motivo typedef funziona e #define no. Nel programma mostrato nell'Esercizio 2 viene utilizzato un nuovo blocco per illustrare una certa cosa. A causa di ciò il codice non risulta molto naturale. In questo esercizio viene ancora utilizzata typedef, e questa volta il codice risulta del tutto semplice. typedef

    char *

    5tring;

    int main(void) {

    5tring

    a[]= {''I .. , .. like . , "to .. , .. fight,"}, b[] = { pinch,", "and", bight ... }; 11

    11

    printf("%s %5 %5 %5 %5 %s %s\n", a[0], a[1], a[2], a[3], b[0], b[1], b[2]); return 0; }

    Eseguite il programma per comprenderne il comportamento. Nel caso l'istruzione typedef venga sostituita con la riga #define

    string

    char *

    il programma non viene compilato. Spiegate per quale motivo typedef funziona e #def in e no. Per fare in modo che il programma funzioni anche con #def in e è sufficiente aggiungere un carattere: dite quale. 4. Scrivete una funzione add (a, b, c) dove le variabili a, be c sono di tipo matrix. Scrivete un programma per provare la vostra funzione.

    5. La velocità non è tutto, ma è di grande importanza nell'ambito informatico. Se una struttura è grande, risulta più efficiente passare un indirizzo della struttura piuttosto che la struttura stessa. Questo è vero anche se la struttura è piccola? Occorre ricordare che c'è un costo anche nell'utilizzo dei puntatori. Supponete di avere parecchi numeri complessi da moltiplicare: come dovreste progettare la vostra funzione di moltiplicazione? Potreste passare strutture come parametri, complex mult{complex b, complex c) {

    complex a; a.re = b.re * c.re - b.im * c.im; a.im = b.im * c.re + b.re * c.im; return a; }

    390 Capitolo 9

    ma può risultare più veloce passare indirizzi: void mult(complex *a, complex *b, complex *c) {

    Completate questa versione della funzione mu l t ( ) e scrivete un programma di prova per vedere quale versione sia più veloce.

    6.

    n programma di questo esercizio utilizza una typedef per dar un nome al tipo ''puntatore a una funzione che riceve un double restituisce un double". P r prima cosa eseguite il programma per comprenderne il comportamento. #include #include #define

    PI

    typedef typedef

    double dbl

    3 14159 o

    dbl; (*PFOO) (dbl);

    l* un'abbreviazione *l l* puntatore a funzione che riceve double e restituisce double *l

    int main(void) {

    PFDD

    f

    = sin,

    printf("f(%f) printf("g(%f) return 0;

    g

    = cos;

    %f\n", PI, f(PI)); %f \ n " , PI , g ( PI ) ) ;

    }

    Modificate ora il codic , so tituendo la seconda typedef con: typedef

    dbl

    FOO(dbl);

    l* funzione di double

    typedef

    FOD

    *PFDD;

    l* puntatore a FDD */

    che restituisce un double *l

    Dite se il programma risulta ancora eseguibile (dovrebbe). Spiegate con preci ione come funziona typedef per PFDD. 7.

    Scrivete una funzione che calcoli la differenza tra numeri complessi. Scrivetene due ver ioni: una che restituì ca un puntatore a complex e l'altra che restituisca un valore di tipo complex.

    8.

    Scrivete un piccolo gruppo di funzioni che eseguano operazioni su vettori e matrici complesse. Includete in tale gruppo delle funzioni che stampino vettori e matrici complesse sullo schermo. Scrivete un programma per provare le vostre funzioni.

    Strutture e unioni 391

    Scrivete un programma in grado di produrre un pasto bilanciato. Iniziate considerando una struttura contenente il nome del cibo, le sue calorie per porzione, il suo tipo (come carne o frutta) e il suo prezzo. I cibi dovrebbero essere memorizzati in un array di strutture. n programma deve costruire un menu che contenga quattro differenti tipi di cibo e soddisfi vincoli di calorie e prezzo. Il programma dovrebb e sere in grado di produrre un gran numero di menu differenti. l . Create una struttura che descriva un ristorante. Essa dovrebbe avere membri che contengano il nome, l'indirizzo, il costo medio e il tipo di cibo propo to. Scrivete una routine che stampi tutti i ristoranti che propongono un certo tipo di cibo in ordine di prezzo, a partire dal meno costoso. l l. Ponete in un file di nome data un elenco di nomi di tudenti, numeri identificativi e voti. Per esempio, l'inizio del file potrebbe essere imile a: Casanova Smith Jones

    910017 934422 878766

    A

    c

    B

    Scrivete un programma di nome reorder utilizzabile per leggere i dati nel file e porli in un array class di tipo struct student. Ciò può essere effettuato con la redirezione:

    reorder




    data

    ->

    next

    ha valore 2 e a.next ha valore 3.

    ->

    data

    Strutture e trattamento di liste 399

    Liste lineari concatenate

    10.2

    na lista lineare concatenata è simile a ltlla corda per il bucato su cui le strutture dati no appese in sequenza. Un puntatore di testa fornisce l'indirizzo del primo elemento d Ila lista; ogni elemento punta all'elemento successivo, eccetto l'ultimo il cui link vale NULL. Per la discussione successiva si fa riferimento al seguente file d'intestazione:

    Nel file list.h: #include #include typedef

    char

    DATA;

    /* negli esempi si utilizzano char */

    struct linked_ list {

    DATA

    d;

    struct linked_list

    *next;

    };

    typedef typedef

    struct linked list ELEMENT -

    ELEMENT; *LINK;

    Allocazione di memoria Le specifiche contenute in list.h non allocano memoria. Il sistema può allocare memoria n l caso vengano dichiarate variabili e array di tipo ELEMENT; tuttavia ciò che rende queste strutture particolarmente utili è la possibilità di utilizzarle con funzioni che alloano dinamicamente la memoria. Il sistema fornisce malloc () nella libreria standard, il ui prototipo si trova in stdlib.h. Una chiamata di funzione della forma malloc (size) r stituisce un puntatore a un'area di memoria di dimensione sufficiente per un oggetto di size byte. Tale area non viene inizializzata. Il parametro di malloc () è di tipo size_ t, il valore restituito è di tipo puntatore a void. Si ricordi che size_t è il tipo intero privo di segno definito sia in stdde/.h che in stdlib.h, ed è il tipo del risultato dell'operatore sizeof. Se head è una variabile di tipo LINK significa che head

    = malloc(sizeof(ELEMENT));

    ottiene dal sistema una porzione di memoria adeguata a contenere un ELEMENT e ne a segna quindi l'indirizzo al puntatore head. Come mostrato nell'esempio, malloc () viene utilizzata con l'operatore sizeof. Poiché malloc () restituisce un puntatore a void, assegnabile a una variabile di qualunque tipo puntatore, non risulta necessario l'utilizzo di un cast. L'operatore sizeof calcola il numero di byte richiesto per la particolare struttura dati.

    400 Capitolo 1O

    N l odic h gu vi n cr ata dinamicam nt una li ta lin ar v ngono m morizzati i tr caratt ri n, e w. Il codic

    head head head

    oncat nata in cui

    = malloc(sizeof(ELEMENT)); d = ' n' ;

    -> ->

    next

    ->

    NULL;

    cr a una li ta di un ingoio l m nto.

    Creazione di una lista concatenata h e ad

    on il

    gu nt a

    head head head

    -> -> ->

    gnam nto i aggiung un

    next next next

    condo l m nto:

    = malloc(sizeof(ELEMENT)); -> ->

    d = 'e'; next = NULL;

    A qu to punto i ha una li ta di du

    l m nti.

    Lista concatenata di due elementi h e ad

    Vi n aggiunto infin l'ultimo l m nto:

    head head head

    -> -> ->

    next next next

    -> -> ->

    next next next

    = malloc(sizeof(ELEMENT)); d = ' w' ; next = NULL;

    -> ->

    i ' co ì ott nuta una li ta di tr l m nti puntata da head, h t rmina quando next conti n il valor " ntin Ila" NULL.

    Lista concatenata di tre elementi h e ad

    ~~ e

    l 3+1

    w

    !NULLI

    Strutture e trattamento di liste

    401

    Operazioni sulle liste

    10.3

    prin ipali op razioni di ba

    ull li t lin ari ono:

    l.

    l m nti.

    2.

    3. 4. 5. 6.

    om primo lir da una tringa uor d lla funzion · ai m mbri. / * Creazione di liste utilizzando la ricorsione. */

    #include #include "list.h" LINK string_to_list(char s[]) {

    LINK

    head;

    if (s[0] == '\ 0 ' ) /*caso base */ return NULL; else { head = malloc(sizeof(ELEMENT)); head - > d = s[0]; head - > next = string_to_list(s + 1); return head; } }

    i noti ancora una volta com la ricor ion abbia un ca o ba , cio ' la cr azione d Ila li ta vuota, un ca o g n ral , cio ' la cr azion d l r to d Ila li ta. La chiamata ricoriva d 1 ca o g n ral r titui c com valor un puntator di tipo LINK alla sottoli ta riman nt .

    402 Capitolo 1O

    ANALISI DELLA FUNZIONE string_to_list() •

    LINK string_to_list(char s[1) {

    LINK head; Passando una stringa come parametro viene creata una lista concatenata dei caratteri di tale stringa. Poiché viene restituito un puntatore alla testa della lista, lo specificatore di tipo nell'intestazione di questa definizione di funzione è LINK.



    if (s[01 == '\0') l* caso base */ re tu rn ( NULL) ; Quando viene raggiunto il marcatore di fine stringa viene restituito NULL e, come mostrato più avanti, la ricorsione ha termine. Il valore NULL viene utilizzato per marcare la fine della lista concatenata.



    else { head = malloc(sizeof(ELEMENT)); Se la stringa s [ 1 è diversa dalla stringa vuota, è necessario chiamare malloc ( ) allo scopo di recuperare un numero di byte sufficiente alla memorizzazione di un oggetto di tipo ELEMENT. Poiché malloc () restituisce un puntatore a void, quest'ultimo può essere assegnato alla variabile head, che è un puntatore di tipo differente, senza necessità di un cast. La variabile puntatore he ad contiene ora l'indirizzo del blocco di memoria fornito da malloc ().



    head ->d= s[01; Il primo carattere della stringa s [ 1 viene assegnato al membro d dell'oggetto allocato di tipo ELEMENT.



    head -> next = string_to_list(s + 1); L'espressione puntatore s + 1 punta al resto della stringa. La funzione viene chiamata ricor ivamente con s + 1 come parametro. Al membro puntatore next viene assegnato il valore puntatore restituito da st ring_ to_list ( s + 1). Questa chiamata ricorsiva restituisce come suo valore un LI NK o, in maniera equivalente, un puntatore a ELEMENT che punta alla sottolista rimanente.



    return head; All'uscita la funzione restituisce l'indirizzo della testa della lista.

    Questa funzione può anche essere scritta in maniera iterativa con l'ausilio di un puntatore addizionale di nome t a i l. Per distinguere la versione ricorsi va string_to_list () da quella iterativa, quest'ultima viene chiamata s_to_l().

    Nel file iter_list. c: /* Creazione di liste impiegando l'iterazione. */

    #include

    Strutture e trattamento di liste 403

    #include "list.h" LINK sto l{char s[])

    -

    {

    .LINK in t

    -

    head = NULL, tail; i•,

    if (s[0] l = '\0') { /* primo elemento *l head = malloc(sizeof(ELEMENT)); head - >d= s[0]; tail = head; for (i= 1; s[i] l= '\0'; ++i) { /*aggiunge in coda *l tail - > next = malloc{sizeof(ELEMENT)); tail = tail -> next; tail ->d= s[i]; }

    tail - > next = NULL;

    /* fine della lista */

    }

    return head; }

    pesso le funzioni che operano sulle liste richiedono variabili locali di tipo puntatome he ad e t a il. Tali puntatori possono essere utilizzati liberamente per rendere il li più semplice. Risulta inoltre importante simulare le routine a mano, ed è utile v r il proprio programma su una lista vuota, su una lista unitaria, cioè una lista di un l lemento, e su una lista di due elementi. Spesso la lista vuota e la lista unitaria ullano casi speciali. Il passaggio di una stringa vuota a s_ to_l () crea la lista vuota, in quanto la funzior tituisce il valore NULL. La creazione del primo elemento viene effettuata dalla ma parte del codice. Nella figura seguente viene mostrata la lista di un elemento ta dalla stringa •A"; essa rappresenta lo stato della computazione prima che al memne xt venga assegnato il valore NULL.

    lista di un elemento

    G tailG head

    ~---+: ~~--A----~----7--~

    404 Capitolo 1O

    Nel caso di due elementi, per esempio • AB", la creazione della lista viene mostrata nelle figure successive. Prima di tutto viene creata la lista unitaria contenente A Quindi viene eseguita ristruzione f or, con i uguale a l e 5 [ i] uguale a B Un nuovo elemento viene allocato e attaccato alla lista. l

    l

    l •

    l •

    Viene attaccato un secondo elemento

    headG tailG Vistruzione tail = tail -> next; sposta tail sul nuovo elemento, al cui membro d viene poi assegnato B l

    l •

    Aggiornamento di tail

    head Gf----.~ IL--A~-·~

    B

    ?

    t ai l

    Ora 5 [ 2] vale \0 e all,uscita dalristruzione f or si ha una lista di due elementi. La fine della lista viene quindi marcata con un NULL. Poiché malloc () non deve inizializzare la memoria a zero, durante alcuni passi della computazione si hanno dei membri con valori indefiniti.

    Strutture e trattamento di liste

    405

    Dopo l'assegnamento eli NULL

    B

    NULL

    Alcune funzioni per l'elaborazione di liste fl no ora presentate altre due funzioni ricorsive: la prima conta gli elementi di una

    , mentre la seconda li stampa. Entrambe attraversano la lista in maniera ricorsiva

    ) a trovare il puntatore NULL. Tutte e due le funzioni utilizzano il file d'intestazione lt.

    La funzione count ( ) restituisce O se la lista è vuota, altrimenti restituisce il numeli l menti della lista. l * Conta ricorsivamente gli elementi di una lista */

    i nt count(LINK head) {

    if (head == NULL) return 0; else return (1 + count(head

    ->

    next));

    }

    v rsione iterativa di questa funzione sostituisce la ricorsione con un ciclo for. l * Conta iterativamente gli elementi di una lista *l

    i nt count it(LINK head) { int cnt = 0; for

    ; head != NULL; head ++cnt; return cnt;

    }

    head

    ->

    next)

    406 Capitolo 1o

    Si ricordi che he ad viene passato "per valore"; pertanto la chiamata count_i t () non modifica il puntatore di accesso alla lista nell'ambiente chiamante. La routine print_list () attraversa una lista ricorsivamente stampando il valore della variabile membro d. /* Stampa una lista ricorsivamente */

    void print_list(LINK head) {

    if (head == NULL) printf ( "NULL"); else { printf("%c --> ", head ->d); print_list(head -> next); } }

    Per presentare l'utilizzo di queste funzioni, viene scritto un programma che converte la stringa "ABC • in una lista e la stampa.

    Nel file prn_list.c: #include #include #include "list.h" LINK void int

    string_to_list(char []); print_list(LINK); count(LINK);

    in t mai n (voi d) {

    LINK

    h.

    '

    h= string_to_list("ABC"); printf("The resulting list is\n"); print_list(h); printf("\nThis list has %d elements.\n", count(h)); return 0; }

    n programma produce l'output seguente: The resulting list is A --> B --> C --> NULL This list has 3 elements. Spesso si desidera ottenere un'unica lista a partire da due liste distinte. La concatenazione delle liste a e b, nell'ipotesi che a non sia vuota, è la lista ottenuta attaccando la

    Strutture e trattamento di liste 407

    ta balla fine della lista a. Una funzione per la concatenazione può attraversare la lista · r andone la fine, marcata dal puntatore nullo, e, tenendo traccia dell'ultimo puntaton n nullo, attaccare la lista b allink next dell'ultimo elemento di a. /* Concatena le liste a e b con a in testa. */

    void concatenate(LINK a, LINK b) {

    assert(a l= NULL); if (a - > next == NULL) a - > next = b; else concatenate(a -> next, b); }

    Utilizzando la ricorsione è possibile evitare l'utilizzo di puntatori ausiliari per muor i nella lista a. In generale, l'impiego della ricorsione risulta del tutto naturale grazie al carattere ut referenziante dell'elaborazione di liste. La forma generale di queste funzioni ricorv · la seguente: void generic_recursion(LINK head) {

    if (head == NULL)

    esegui il caso base else

    esegui il caso generale e opera una ricorsione con generic_resursion(head -> next)

    Inserimento na delle proprietà più utili delle liste è rappresentata dal fatto che l'inserimento di un l mento richiede una quantità di tempo costante, una volta che sia stata determinata la izione nella lista. Al contrario, nel caso si volesse porre un valore in un array con molti elementi, mantenendo tutti gli altri valori nel medesimo ordine sequenziale, l'inserimento richied in media un tempo proporzionale alla lunghezza dell'array: infatti i valori di tutti gli l menti dell'array che seguono l'elemento appena inserito devono essere spostati di una posizione. Di seguito è presentato l'inserimento di un nuovo elemento puntato da q tra due l menti adiacenti di una lista puntati da p1 e p2.

    408 Capitolo 1O

    ···-1

    A

    p1c5 qG---1



    l ·l p2(5 B

    l



    c

    .....

    NULL

    ···~

    c

    ••-+-+.....

    p1d) La seguente funzione insert () pone l'elemento puntato da q tra gli elementi puntati da p1 e da p2:

    l* Inserimento di un elemento in una lista concatenata. */

    void insert(LINK p1, LINK p2, LINK q) {

    assert(p1 -> next -- p2); p1 -> next = q; /* inserimento */ q -> next = p2; }

    Cancellazione La cancellazione di un elemento da una lista lineare concatenata risulta estremamente semplice. Al membro link del predecessore dell'elemento da cancellare viene assegnato l'indirizzo del successore dell'elemento cancellato. Ricorrendo di nuovo alla rappresentazione grafica, lo schema iniziale è il seguente.

    Strutture e trattamento di liste 409

    Prima deHa cancellazione · · · ---+ p

    l

    A



    l •1~-..._8_....&....-_•--JI • ~.-l_c_...&......__••=~+--•• · · ·

    cf)~

    iene ora eseguito il seguente codice:

    p

    ->

    next

    =

    p

    ->

    next

    ->

    next;

    Dopo la cancellazione

    .____e_...&......_•--JI ~ ~.-l_c_....~.-__••==:,+--•., . . .

    ome mostrato nel diagramma, l'elemento contenente B non risulta più accessibile d è quindi inutile; tale tipo di elemento è detto garbage (sporcizia). Poiché la memoria spesso una risorsa critica, è opportuno che quest'area venga restituita al sistema per sere riuti]izzata, e a tale scopo è possibile utilizzare la funzione free ( ) in stdlib.h. M diante la chiamata l

    l

    free(p); lo spazio di memoria precedentemente allocato per l'oggetto puntato da p viene reso di poni bile al sistema. Il parametro formale di free () è un puntatore a void. Utilizzando free ( ) è possibile scrivere una routine di cancellazione che restituìa al sistema la memoria allocata per la lista. l* Cancellazione ricorsiva di una lista. *l

    void delete list(LINK head) { if (head l= NULL) { delete list(head -> next); free(head); } }

    l* rilascia la memoria *l

    41 O Capitolo 1O

    Poiché free () riceve un unico parametro di tipo void *, a tale funzione può essere passato un puntatore di qualunque tipo. Non è necessario utilizzare un cast in quanto void * è il tipo puntatore generico.

    10.5

    Stack

    Nel Paragrafo 9.10 viene discusso il tipo stack come array. In questo paragrafo viene presa in considerazione un'implementazione dello stesso ADT mediante liste lineari concatenate. Nel caso di uno stack, l'accesso è ristretto alla testa della lista, chiamata in questo caso top (cima). Inoltre, gli inserimenti e le cancellazioni awengono solo al top, mediante le operazioni dette rispettivamente push e pop. Uno stack può essere visualizzato come una pila di vassoi; un vassoio viene sempre posto in cima alla pila o prelevato dalla cima. Graficamente, gli stack vengono rappresentati verticalmente.

    Implementazione di uno stack

    Viene ora presentato un programma che implementa uno stack e che consiste in un file .h e due file .c. Di seguito è riportato il file d'intestazione.

    Strutture e trattamento di liste

    411

    Nel file stack.h: l* Implementazione di uno stack mediante una lista concatenata. *l

    #include #include #define #define

    EMPTY FULL

    typedef typedef

    char enum {false, true}

    struct elem { data struct elem

    0 10000

    data; boolean;

    l* un elemento dello stack *l

    d·,

    *next;

    };

    typedef

    struct elem

    elem;

    struct stack { int cnt; elem *top;

    l* contatore degli elementi *l l* puntatore al top *l

    };

    typedef

    struct stack

    voi d voi d data data boolean boolean

    initialize(stack *stk); push(data d, stack *stk); pop(stack *stk); top(stack *stk); empty(const stack *stk); full(const stack *stk);

    stack;

    pr totipi di funzione delle sei operazioni standard del tipo stack sono elencati in fondo fil d'intestazione. Concettualmente queste funzioni si comportano come quelle prentate nel Paragrafo 9.10; l'impiego del tipo data rende il codice riutilizzabile (esso n utili.zzato anche nel Paragrafo 10.6). Nel file stack.c:

    l* Le routine base per uno stack. *l #include "stack.h" void initialize(stack *stk) {

    stk -> cnt stk -> top

    =

    0; NULL;

    }

    void push(data d, stack *stk)

    412 Capitolo 1O

    {

    elem

    *p;

    p= malloc(sizeof(elem)); = d; p -> next = stk -> top; stk -> top = p; stk -> cnt++;

    p -> d

    }

    data pop(stack *stk) {

    data elem

    d; *p;

    d = stk -> top -> d; p = stk - > top; stk -> top = stk -> top -> next; stk -> cnt--; free (p); return d; }

    data top(stack *stk) {

    return (stk ->top-> d); }

    boolean empty(const stack *stk) {

    return ( ( boolean) ( stk -> cnt

    EMPTY));

    }

    boolean full(const stack *stk) {

    return ( ( boolean) ( stk -> cnt == FULL)) ; }

    La routine pus h () richiama malloc () per creare un nuovo elemento dello stack; pop () restituisce al sistema lo spazio di memoria liberato. Uno stack è una struttura dati LIFO (last-in-jirst-out, l'ultimo a entrare è il primo a uscire). Pertanto, ponendo sullo stack prima una a e poi una b pop ( ) preleverà prima b Questa proprietà viene utilizzata in mai n ( ) per stampare una stringa in ordine inverso; ciò è utile come prova per l'implementazione dell'ADT stack. l

    l

    l

    l

    l ,

    l •

    Nel file main.c: l* Prova l'implementazione di uno stack rovesciando una stringa. *l

    #include "stack.h" int main(void)

    Strutture e trattamento di liste 413

    {

    char in t stack

    str[ 1

    "My name is Joanna Kelleyt";

    i;

    s;

    initialize(&s); /* inizializza lo stack */ printf(" In the string: %s\n", str); f or ( i = 0; s t r [ i 1 l = 0 ++i) i f ( 1full ( &s) ) push(str[i1, &s); /*pone un char sullo stack */ printf("From the stack: "); while (lempty(&s)) putchar(pop(&s)); /*preleva un char dallo stack */ putchar( l \n l); return 0; l

    \

    l

    ;

    }

    rvi che la funzione ma in() è del tutto simile a quella scritta nel Paragrafo 9.10. bbene qui lo stack sia implementato come una lista concatenata e nel Paragrafo 9.10 , m una stringa, l'utilizzo delle operazioni è simile. Di seguito è riportato l'out:put del proIn the string: My name is Joanna Kelleyl From the stack: tyelleK annaoJ si eman yM

    10.6

    Un esempio: notazione polacca e valutazione di uno stack

    notazione ordinaria utilizzata per scrivere le espressioni, nella quale gli operatori parano gli argomenti, è chiamata infissa. Un'altra notazione per le espressioni, utile r la valutazione basata su stack, è detta notazione polacca o priva di parentesi. Nella 1totazione polacca gli operatori si trovano dopo gli argomenti. Per esempio: 3, 7, + quivalente a: 3 + 7

    Ila notazione polacca, spostandosi da sinistra a destra, gli operatori possono essere pplicati appena vengono incontrati, dunque 17, 5, 2,

    *, +

    quivalente a: 17 + (5 * 2)

    414 Capitolo 1O

    Un'espressione polacca può essere valutata con un algoritmo che utilizza due stack: lo stack polacco, che contiene l'espressione polacca, e lo stack di valutazione, che memorizza i valori intermedi durante l'esecuzione. Nella tabella seguente viene presentato un algoritmo a due stack che valuta le espressioni polacche con operatori tutti binari.

    Se lo stack polacco è vuoto, terminare dando come risultato il top dello stack di valutazione. Se lo stack polacco non è vuoto, eliminare il valore al top dello stack polacco e porlo in d. (Vengono utilizzati d, d1 e d2 per memorizzare dati.) Se d è un valore, va posto nello stack di valutazione. Se d è un operatore, è necessario prelevare i due elementi in cima allo stack di valutazione ponendo il primo in d2 e il secondo in d1. L'operazione in d va applicata a d1 e a d2; il risultato va posto sullo stack di valutazione. Ritornare al passo l.

    l.

    2. 3. 4.

    L'algoritmo viene illustrato nel seguente diagramma, nel caso della valutazione dell'espressione: 13, 4, -, 2, 3, *' +

    Algoritmo a due stack per la valutazione di espressioni polacche

    s t a c k

    s a c k p o

    1 a c c o

    d

    v a

    l u t a z i o n e

    13 4

    4

    2

    2

    2

    2

    3

    3

    3

    3

    3

    *

    *

    *

    4

    *

    *

    2

    *

    2

    +

    +

    +

    13

    +

    +

    9

    +

    9

    13

    9

    3 +

    6 9

    15

    Strutture e trattamento di liste

    415

    Viene ora presentato un programma che implementa questo algoritmo a due stack. Jn'idea chiave è quella di ridefinire data in modo che possa memorizzare un valore in rma di intero o un operatore in forma di carattere. nprogramma è formato da un file .h da cinque file .c. Di seguito è riportato il file d'intestazione: Nel file polish.h: l* Implementazione di uno stack polacco mediante una lista

    concatenata. */

    #include #include #include #include #define #define



    EMPTY FULL

    0 10000

    struct data { enum {operator, value} union { char op; int val; }

    kind;

    u;

    };

    typedef typedef

    struct data enum {false, true}

    struct elem { data struct elem

    data; boolean;

    l* un elemento dello stack *l

    d;

    *next;

    };

    typedef

    struct elem

    struct stack { int cnt; elem *top;

    elem;

    l* contatore degli elementi *l l* puntatore al top *l

    };

    typedef boolean in t voi d boolean voi d data voi d voi d voi d data

    struct stack stack; empty(const stack *stk); evaluate(stack *polish); fill(stack •stk, const char *str); full(const stack •stk); initialize(stack *stk); pop(stack •stk); prn_data(data *dp); prn_stack(stack *stk); push(data d, stack *stk); top(stack *stk);

    416

    Capitolo 1O

    Si noti che questo file d'intestazione è simile a quello utilizzato nel programma dello stack del Paragrafo 10.5. La differenza fondamentale è che data è definito come tipo struttura. Tale struttura contiene una union in grado di memorizzare un valore int o un operatore in forma di char e un "flag" in forma di tipo enumerativo. 11 flag indica quale tipo di dato sia memorizzato.

    Nel file main.c: /* Verifica l'algoritmo a due stack per la valutazione di espressioni polacche. */ #include "polish.h" int main(void) {

    char stack

    str[ 1 = "13, 4, polish;

    2, 3, • ' +"·

    '

    printf("\n%s%s\n\n", "Polish expression: ", str); fill(&polish, str); /* riempie lo stack da una stringa */ prn_stack(&polish); /*stampa lo stack */ printf("\n%s%d\n\n", "Polish evaluation: " evaluate(&polish)); return 0; }

    In mai n ( ) , lo stack polacco viene riempito in base a una stringa e stampato al fine di verificare che le operazioni siano corrette. La funzione più interessante è quella che valuta lo stack polacco, presentata qui di seguito.

    Nel file eval.c: /* Valutazione di uno stack polacco */

    #include "polish.h" int evaluate(stack •polish) {

    data stack

    d' d1' d2; eval;

    initialize(&eval); while (lempty(polish)) { d= pop(polish); switch (d.kind) { case value: push(d, &eval); break;

    Strutture e trattamento di liste 417

    case operator: d2 = pop(&eval); d1 = pop(&eval); d.kind = value;

    l* 1n1z1o della riscrittura di d *l

    switch (d.u.op) { case l+ l: d.u.val d1.u.val + d2.u.val; break; case l- l: d.u.val = d1.u.val d2.u.val; break; case * d.u.val = d1.u.val * d2.u.val; 1

    1

    :

    }

    push(d, &eval); } }

    d= pop(&eval); return d.u.val; }

    funzione evaluate() incorpora l'algoritmo a due stack presentato sopra. Prima di utto d viene prelevata dallo stack polish. Se essa è un valore viene posta nello stack val, se è un operatore è necessario prelevare d2 e d1 dallo stack eval ed eseguire ' perazione indicata, ponendone il risultato sullo stack eval. Quando lo stack polish ulta vuoto, d viene prelevato dallo stack eval e viene restituito come risultato il valoi nt d. u. val. Le operazioni sullo stack vengono scritte nel file stack.c. A parte l'inlu ione di un file d'intestazione diverso, non vi sono differenze rispetto al file stack.c li ' usso nel Paragrafo 10.5.

    Nel file stack.c: l* Le routine base per uno stack. *l #include "polish.h" void initialize(stack *stk) {

    stk -> cnt 0; stk -> top = NULL; }

    void push(data d, stack *stk) {

    elem

    *p;

    p= malloc(sizeof(elem)); p -> d

    = d;

    p -> next = stk -> top; stk -> top = p;

    418 Capitolo 1O

    stk -> cnt++; }

    Nell'implementazione dello stack presentata nel Paragrafo 10.5, il tipo data risulta equivalente a char; in questo caso data è un tipo struttura. Utilizzando una typedef per incorporare l'idea di "dato", si è ottenuta un'implementazione riutilizzabile dell'ADT stack. Questo punto è fondamentale. Utilizzando codice già scritto e provato si realizza un rispannio di lavoro sul progetto corrente. E necessario poter riempire uno stack da una stringa contenente un'espressione polacca. Di seguito è riportata la funzione che compie questa operazione.

    Nel file fili. c: #include "polish.h" void fill(stack *stk, const char *str) {

    const char char boolean data stack

    *p = str; c1, c2; b1, b2; d;

    tmp;

    initialize(stk); initialize(&tmp); l* Il Elabora prima di tutto la stringa e pone i dati su tmp. *l while (*p l= \0 while (isspace(*p) l l *p== ++p; b1 = (boolean) ( (c1 = *p) == + Il c1 == ~ - ~ Il c1 == l* l); b2 = ( boolean) ( ( c2 = * (p + 1 ) ) == Il c2 == \0 if (b1 && b2) { d.kind operator; d.u.op c1; 1

    1

    )

    {

    1

    l,

    )

    1

    1

    l

    ,

    l

    l

    l

    )

    ;

    }

    else { d.kind = value; assert(sscanf(p, "%d", &d.u.val) == 1); }

    if (lfull(&tmp)) push(d, &tmp); while (*p l= && *p l= ++p; l,

    l

    l* pone i dati su tmp *l 1

    \0

    1 )

    }

    l* Il Ora preleva dati da tmp e li pone su stk. *l

    Strutture e trattamento di liste 419

    while (lempty(&tmp)) { d= pop(&tmp); if ( lfull(stk)) push(d, stk);

    l* preleva dati da tmp */ l* pone i dati su stk *l

    } }

    n primo luogo viene elaborata una stringa per estrarne i dati, che, una volta trovati, n ono posti sullo stack tmp. Terminata l'elaborazione della stringa, i dati vengono l vati da tmp e posti sullo stack puntato da stk. In questo modo gli elementi nello a k puntato da stk sono in ordine corretto. Vengono ora presentate due funzioni di stampa, utili per controllare che il codice nzioni correttamente.

    Nel file print.c: #include "polish.h" void prn data(data *dp)

    -

    {

    switch (dp -> kind) { case operator: printf("%s%3c\n", "kind: operator break; case value: printf(M%s%3d\n", "kind: value

    op:", dp

    ->

    u.op);

    val:", dp

    ->

    u.val);

    } }

    void prn_stack(stack *stk) {

    data

    d;

    printf("stack count:%3d%s", stk -> cnt, (stk -> cnt == 0) ? "\n" if ( tempty(stk)) { d= pop(stk); l* preleva il dato *l l* stampa il dato *l prn_data (&d); prn_stack(stk); l* chiamata ricorsiva *l push(d, stk); l* inserisce il dato *l •

    Il

    Il )

    ;

    }

    L'algoritmo per stampare lo stack è molto semplice; prima preleva d dallo stack u sivamente lo stampa, quindi chiama ricorsivamente prn_stack () e, infin , pon nuovamente d sullo stack. L'effetto è quello di stampare tutti i dati nello stack, la d lo nello stato originale.

    420 Capitolo 1O

    Di seguito è riportato l'output del programma: Polish expression: 13, 4, stack stack stack stack stack stack stack stack

    count: count: count: count: count: count: count: count:

    7 6 5 4 3

    2 1 0

    kind: kind: kind: kind: kind: kind: kind:

    '

    2, 3, * + '

    value value operator value value operator operator

    val: 13 val: 4 op: val: 2 val: 3 op: * op: +

    Polish evaluation: 15

    10.7

    Code

    Una coda è un altro esempio di tipo di dato astratto (AD1) implementabile come lista lineare concatenata. Una coda ha due estremità: una testa e una coda. Gli elementi vengono inseriti in coda e prelevati in testa.

    Implementazione di una coda

    queue cnt front rear -

    ~

    elem

    elem

    -+

    elem

    ~oo_t_a~--·--~1 ~~~_oo_M--~-·--~1 ~~~_oo__~~--NU_L_L~ L'implementazione dell'ADT qui presentata contiene alcune delle funzioni standard sulle code. Di seguito è presentato il file d'intestazione.

    Strutture e trattamento di liste

    421

    Nel file queue.h: /* Implementazione di una coda mediante lista concatenata. */

    #include #include #include #define #define

    EMPTY FULL

    typedef typedef

    unsigned int enum {false, true}

    struct elem { data struct elem

    0 10000

    data; boolean; l* un elemento della coda */

    d;

    *next;

    };

    typedef struct elem struct queue { in t cnt; elem *front; elem •rear;

    elem; l* contatore degli elementi */ l* puntatore alla testa */ l* puntatore alla coda *l

    };

    typedef voi d voi d data data boolean boolean

    struct queue queue; initialize(queue *q); enqueue(data d, queue *q); dequeue(queue *q); front(const queue *q); empty(const queue *q); full(const queue *q);

    Alla fine del file d'intestazione viene posto l'elenco dei prototipi di funzione. Le definizioni di funzione vengono scritte nel file queue.c. Queste funzioni, insieme a questo file d'intestazione, implementano l'ADT coda.

    Nel file queue.c: /* Le routine di base relative alle code. */ #include "queue.h" void initialize(queue *q) {

    q -> cnt = 0; q -> front = NULL; q -> rear = NULL; }

    data dequeue(queue *q)

    422 Capitolo 1O

    {

    data elem

    d; *p;

    d = q -> front -> d; p = q -> front; q -> front = q -> front -> next; q -> cnt--; free(p); return d; }

    void enqueue(data d, queue *q) {

    elem

    *p;

    p= malloc(sizeof(elem)); p -> d = dj p -> next = NULL; i f ( l empt y (q ) ) { p; q -> rear -> next q -> rear = p; }

    else q -> front q -> cnt++;

    q -> rear = p;

    }

    data front(const queue *q) {

    return (q-> front-> d); }

    boolean empty(const queue *q) {

    return ((boolean) (q-> cnt -- EMPTY)); }

    boolean full(const queue *q) {

    return ( ( boolean) (q -> cnt

    ==

    FULL)) ;

    }

    La routine enqueue () utilizza l'allocatore di memoria malloc () per creare un nuovo elemento della coda; la routine dequeue ( ) restituisce al sistema lo spazio di memoria liberato. Una coda è una struttura dati FIFO (jirst-in-jirst-out, il primo a entrare è il primo a uscire). Il primo elemento posto nella coda sarà il primo a essere rimosso. Le code risultano estremamente utili in svariate applicazioni relative alla programmazione di sistema. Per esempio, esse vengono spesso utilizzate per la schedulazione di risorse nei sistemi operativi e per la scrittura di simulatori di eventi.

    Strutture e trattamento di liste 423

    Come esempio viene presentato uno schedulatore elementare per un sistema a u processori. Si suppone che un processo possa richiedere un servizio dal processore ) dal processore B. Un processo viene individuato da un proprio numero identificato(PID). Dopo aver letto tutte le richieste, lo schedulatore stampa l'ordine con cui i si vengono serviti da ogni processore.

    Nel file main.c: /*

    Utilizzo delle code per schedulare due risorse. */

    #include "queue.h• int main(void) {

    in t in t in t data queue

    c; cnt a cnt-b p idi a, b;

    0; 0;

    l* numero PIO *l

    initialize(&a); initialize(&b); l* Accoda le richieste. *l while ((c= getchar()) l= EOF) { switch (c) { case 'A': assert(scanf("%u", &pid) if (lfull(&a)) enqueue(pid, &a); break; case 'B': assert(scanf("%u", &pid) if ( lfull(&b)) enqueue(pid, &b);

    1);

    1);

    } }

    l* Preleva le richieste dalla coda e le stampa. *l printf("\nA's schedule:\n"); while (lempty(&a)) { pid = dequeue(&a); printf(" JOB %u is %d\n", ++cnt_a, pid); }

    printf("\nB's schedule:\n"); while (lempty(&b)) { pid = dequeue(&b); printf(• JOB %u is %d\n", ++cnt_b, pid); }

    return 0; }

    424 Capitolo 1O

    Per provare il programma viene creato il seguente file di input:

    Nel file input: B A B A A

    7702 1023 3373 5757 1007

    Impartendo il comando

    scheduler


    d = d1; tmp - > child no = num; tmp - > sib =- sibs; return tmp; }

    Qu t routin po ono e r utillzzat p r cr ar l'alb ro del pr c d nt o conti n otto nodi, ' n c ari o un array t [ 1 di dim n i n , mma. Poi h NODE, in cui t [ 01 ' il puntator alla radic .

    430 Capitolo 1O

    t[0] = init_gnode('a', 1, NULL); t [ 1 ] = init_gnode('b', 2, NULL); t [ 1] -> sib = init_gnode('f', 6, NULL); t [ 1 ] -> sib -> sib = init_gnode('h', a, NULL); t[2] = init_gnode('c', 3, NULL); t[2] -> sib = init_gnode( ' d', 4, NULL); t[2] -> sib -> sib = init_gnode('e', 5, NULL); t[3) NULL; NULL; t[4] t[5] = NULL; init_gnode('g', 7, NULL); t[6] t[7] NULL; NULL; t[8] Utilizzando questa rappresentazione, è facile contare il numero di figli di un nodo, o v rificare se un nodo sia una foglia. Infatti, se t [n] è NULL allora il nodo n è una foglia.

    Visita La visita agli alberi generali è una combinazione di un movimento lungo le liste di un accesso agli elementi dell'array che puntano alle liste. È abbastanza semplice generalizzare le visite a queste strutture in ordine anticipato, posticipato e simmetrico. Ancora una volta, questi algoritmi sono prototipi per funzioni più complicate applicabili a un albero, in quanto garantiscono che ogni elemento sia raggiunto in tempo lineare.

    l* Visita in ordine anticipato di alberi generali. *l void preorder_g(GTREE t, int ind) {

    GTREE

    tmp;

    l* tmp attraversa la lista dei fratelli *l

    tmp = t[ind]; l* t[ind] è il nodo radice *l while (tmp ! = NULL) { printf("%c %d\n", tmp -> d, tmp -> child_ no); preorder_g(t, tmp -> child_no); tmp = tmp -> sib; } }

    la funzione preorder_g () differisce dalla funzione corrispondente per il caso di alberi binari poiché, per muoversi lungo la lista lineare dei fratelli, è nece sario un ciclo while. Si noti che la ricorsione permette di gestire in maniera pulita ogni ottoalbero.

    Utilizzo di calloc() e costruzione di alberi la funzione di libreria calloc ( ) permette l'allocazione di memoria contigua utilizzabile per gli array. Il suo prototipo di funzione si trova in stdlib.h come: void *calloc(size_t n, size_t size);

    Strutture e trattamento di liste 431

    1 rametri di calloc () vengono dunque convertiti al tipo size_t, e il valore restituito

    i tipo puntator a void. Normalmente size_t è equivalente a unsigned. Una chiala di funzione della forma

    calloc (n, size) tituisc un puntatore a uno spazio di memoria contiguo ufficiente per n oggetti, nuno di size byte. Tale spazio viene inizializzato a zero dal si tema. Dunque calloc {) 1 s r utilizzata per allocare dinamicamente spazio per array definiti durante l'esezi n . Ciò è utile per allocare solamente lo spazio necessario senza dover specificare rante la compilazione la lunghezza dell'array, che potrebbe essere notevole, per postire tutti i casi possibili. Com esempio vi ne considerata una routine per la costruzione di un albero geneda una lista di archi e da un array di tipo DATA. Volendo un array di lunghezza lO per morizzar i puntatori ai ottoalberi, è pos ibile crivere: t = calloc(10, sizeof(GTREE)); ' rray t di tipo puntatore a GTAEE, allocato dinamicamente, può essere passato a una nzione buildt ree {) per la costruzione di un albero geo rale. Que ta funzione riceve 1 rappre entazione di un albero come lista di archi e ricava la sua rappresentazione truttura a lista generai . / * Function buildtree: crea un albero da un array di archi. */

    typedef struct { int out; in t in; } PAIA;

    /* PAIA rappresenta un arco nell'albero */

    void buildtree(PAIA edges[], DATA d[], int n, GTAEE t[]) {

    in t in t

    i•

    x,' y;

    /* estremi di un arco */

    t[0] = init_gnode(d[1], 1, NULL); /* t[0] considera il nodo 1 come radice */ for {i = 1; i 0) { c = getc(ifp);

    l* si sposta avanti di un carattere *l

    putchar(c); fseek(ifp, - 2, SEEK_CUR);

    /* torna indietro di due

    caratteri *l

    }

    return 0; }

    11.8

    lnput/output con descrittori di file

    Un de crittor di fil ' un intero non negativo associato a un file. In qu to paragrafo vi ne pres ntato un in ieme di funzioni di libreria utilizzate con i descrittori di file; s hbene quest funzioni non facciano parte di ANSI C, e e ri ultano disponibili su molti ist mi C ia sotto MS.DOS che sotto UNIX. A cau a di alcune differ nze occorre pretare attenzione quando si trasferisce il codice da UNIX a MS.DOS o viceversa.

    standard input standard output tandard error

    O l

    2

    Le funzioni della Ubr ria standard eh operano con puntatori a FILE fanno solitam nt u o di un buffer. Al contrario, l funzioni che usano d scrittori di fil po ono richieder dei buffer pecificati dal programmatore. L'uso dei de crittori di file vi ne illustrato con un programma che legge da un file e scrive in un altro tra formando le lettere maiu cole in minuscole e viceversa.

    Nel file change_case.c:

    l* Trasforma le lettere maiuscole in minuscole e viceversa. *l #include #include #include #define

    BUFSIZE

    !* in MS -DOS si utilizzi io.h *l

    1024

    lnput/output e sistema operativo 461

    int main(int argc, char **argv) {

    char int

    mybuf[BUFSIZE], *p; in_fd, out_fd, n;

    in_fd = open(argv[1], O_RDONLY); out_fd = open(argv[2], O_WRONLY l O_EXCL l O_CREAT, 0600); while ((n = read(in_fd, mybuf, BUFSIZE)) > 0) { for (p = mybuf; p - mybuf < n; ++p) if (islower(*p)) *p = toupper(*p); else if (isupper(*p)) *p = tolower(*p); write(out_fd, mybuf, n); }

    close(in_fd); close(out_fd); return 0; }

    NALISI DEL PROGRAMMA change_case #include #include #include /* in MS-DOS si utilizzi io.h */ n fil d'intestazione lenti. h conti ne costanti simbolich che vengono utilizzate nel programma, il file d'intestazione unistd.h contiene i prototipi di funzione per open ( ) re ad (). In MS-DOS è invece n ces ario includere io. h. in_fd = open(argv[1], O_RDONLY); open () è un nome di file; il condo parametro specifica come il file debba ssere aperto. Se non ci sono errori la funzione restitui ce un descrittor di file, altrimenti viene restituito -l. L'identificatore in_fd è mnemonico e sta per "in file descriptor". Sia in MS-DOS che in UNIX, la costante simbolica O_RDONL Y è fornita infcntl.h, e il suo nome sta per "open for reading only", apri in ola lettura.

    n primo parametro di

    out_fd = open(argv[2], O_WRONLY l O_EXCL l O_CREAT, 0600); Le co tanti imboliche infcntl.h utilizzate per aprire un file possono essere combinate con un operatore di OR bit a bit. In questo caso viene specificato che il fil viene aperto solo per la scrittura, che il file deve essere aperto in esclusiva (ci che si ha un errore se il file è già stato aperto) e che il file deve essere cr ato non e iste. O_EXCL viene usata olo con O_CREAT. Se il file viene cr ato, il rz parametro definisce i permessi relativi al file; altrimenti esso non ha al un fi tt . I vari tipi di permessi vengono presentati nel seguito.

    462

    Capitolo 11



    while ((n = read(in_fd, mybuf, BUFSIZE))

    >

    0) {

    Dal file a ociato a in_fd v ngono l tti e po ti in mybuf un ma imo di BUFSIZE caratt ri; vi n r tituito il num rodi caratteri l tti. Il corpo d l iclo while vi n e guito fino a quando rea d ( ) ' in grado di ric v r caratteri dal fil . N l orpo d l ciclo l l tter cont nut in mybuf vengono conv rtit da maiu col in minuol •

    write(out_fd, mybuf, n); N l fil a ociato a out_fd v ngono scritti n aratt ridi mybuf.



    close(in_fd); close(out_fd); Qu t i truzioni chiudono i du fil . nza un' pli ita chiu ura da part d l programmator , i fil v ngono comunqu chiu i all'u cita dal programma.

    Attenzione: qu to programma non è u er-fri ndly ("amkh voi con l'ut nt ''). Forn ndo il comando change _case

    file1

    file2

    file2 i t , o non vi n ri critto com ci i a pett r bb : un programma m glio prog ttato dovr bb informar l'ut nt di ciò.

    11.9

    Permessi di accesso ai file

    In UNIX, un fil vi n r ato con a ociati alcuni perm n d t rminano l posibilità di ace o da part d l propri tario, d l gruppo d gli altri. L'ace o può er in l ttura, crittura, cuzion o in ogni combinazion di qu t modalità, compr sa la combinazion vuota. Quando un fil vi ne cr ato chiamando open (), ' po ibil utilizzar un int ro ottale di 3 cifr com t rzo param tro per d finir i penn i. Ogni cifra ottal controlla i p rm i in 1 ttura, crittura d cuzion ; la prima cifra ottale controlla i p rm i p r l'ut nt , la conda per il gruppo e la t rza p r gli altri ( io' chiunqu ).

    cifri oltl nel r. -w- -x rw r -x -wx rwx

    100 010 001 110 101 011 111

    04 02 01 06 05 03 07

    lnput/output e sistema operativo 463

    Mnemonlco rw - - - - - - rw - - - - r - -

    rwxr -xr -x rwxrwxrwx il propri tarlo può l ggere, scrivere ed e guir il il fil gli altri po ono l ggere ed es guire il file. ono vi ualizzati in forma mn monica m diante il i di a c o ai fil , ma olo per tutti.

    1.10

    Esecuzione di comandi dall'interno di un programma C

    funzion di li br ria system () perm tt di ace d r ai omandi d l i t ma operati. ia in M DO eh in UNIX, il comando date provoca la vi ualizzazion ullo eh rd Ila data corr nt . Voi ndo tampar qu ta informazion dal programma ' po isystem( "date " ); tringa pa ata a system () vi n trattata come un comando d l i t ma op rativo. uand l'i truzion vi n guita, il controllo pa a al si t ma op rativo, vi n gui> il comando quindi il controllo ritorna al programma. vi ' un ditor di t to utilizzato comun m nt in UNIX; voi ndo utilizzar vi dal'lnt mo di un programma, p r criv r un fil il cui nom ' tato dato com parametro quanto gu : ulla riga di comando, ' po ibil char

    command[MAXSTRING];

    sprintf(command, "vi %s " , argv[1]); printf( "vi on the file %s is coming up ... \n " , argv[1]); system(command);

    464 Capitolo 11

    Nel file lower_case.c: l* Scrive sullo schermo solo lettere minuscole. * / #include #include #include #define

    MAXSTRING

    100

    int main(void) {

    char int FILE

    command[MAXSTRING], *tmp_ filename; c; *ifp;

    tmp_filename = tmpnam(NULL); sprintf(command, "dir> %s " , tmp_filename); system(command); ifp = fopen(tmp_filename, " r " ); wh i le ((c = getc(ifp)) l = EOF) putchar(tolower(c)); remove(tmp_filename); return 0; }

    Viene usata innanzi tutto la funzione di libreria tmpnam ( ) per creare un nome di file temporaneo. Viene poi invocato system () per redirigere l'output del comando dir nel file temporaneo, quindi viene stampato sullo chermo il contenuto del fil , conv rt ndo le maiuscole in minuscole, e infine, terminato l'uso del file temporaneo, esso vie ne rimosso chiamando la funzione di li br ria remove ( ) . Per ulteriori dettag li su queste funzioni si consulti l'Appe ndice A, Paragrafo Al2.

    11.11

    Utilizzo di "pipe" da un programma C

    Per comunicare con il sistema operativo il sistema UNIX forni sce popen () e pelose (); queste funzioni non sono di ponibili in MS-DOS. Si supponga di e sere tanchi d lle l ttere minuscole prodotte dal comando ls di UNIX.

    Nel file upper_case.c: #include #include int main(void) {

    int FILE

    c; *ifp;

    lnput/output e sistema operativo 465

    ifp = popen( "ls", "r"); while ((c = getc(ifp)) l = EOF) putchar(toupper(c)); pclose(ifp); return 0; }

    primo parametro di popen () è una stringa che viene interpretata come un comando l i tema operativo; il secondo parametro è una modalità di apertura di un fil , r " o •. Quando la funzione viene chiamata essa crea un canale ("pipe", da cui il nom pe n) tra l'ambiente chiamante e il comando che deve essere eseguito; in questo esemi ha acces o a ciò che viene prodotto dal comando ls. Poiché l'accesso al file puntato fp avviene via pipe, non è possibile utilizzare le funzioni di posizionamento sui file; mpio, rewind (i fp) non è in grado di operare. L'unico accesso possibile ai carat. quello s quenziale. nfile aperto da popen () deve essere chiuso con pelose ().Se hiu ura non è esplicita, essa avviene automaticamente all'uscita del programma. Il

    Variabili di ambiente in UNIX che in MS-DOS ono disponibili variabili di ambiente; esse possono venire mpat sullo schermo utilizzando il seguente programma: #include int main(int argc, char *argv[], char *env[]) {

    in t

    i•

    '

    for (i = 0; env[i] l = NULL; ++i) printf( "%s\n", env[i]); return 0; }

    rzo argomento di mai n () è un puntatore a char, che può essere immaginato come n rray di puntatori a char o come un array di stringhe; il sistema fornisce le stringhe, i m allo pazio per memorizzarle. L'ultimo elemento dell'array env è il puntatore L. Quanto segu ' un e empio di output del programma sul nostro sistema UNIX. HOME =/ c/c/blufox/center manifold SHELL=/bin/csh TERM=vt102 USER=blufox

    l n m a sinistra del segno di uguaglianza rappresenta una variabile d ll'ambi nt , ntr a destra è scritto il relativo valore, che va visto come stringa. Quanto mpio di output sul nostro sistema MS-DOS.

    gu

    un

    466

    Capitolo

    11

    COMSPEC=C:\COMMANO.COM BASE=d:\base INCLUOE =d:\msc\include

    Il si tema UNIX fornisce un comando p r la stampa d Il variabili di ambi nt , · cui nome dipende dalla shell utilizzata: nel ca o d Ila C h Il il nom ' printenv, m ntr n l ca o della Bourne hell e in MS-DOS il nom ' set. L'output prodotto dal comand coincide con quanto prodotto dal programma pr c d ntem nt pr ntato. Per conv nzione, i nomi d 11 variabili di ambi nt ono di olito critti in maiu c lo. In un programma C ' po ibil acceder al valor di una variabil di ambi nt pa mpio d ll'u • andola alla funzion getenv () come parametro. Quanto gu ' un lizzo di getenv (). printf( "%s%s\n%s%s\n%s%s\n%s%s\n", " Name: Il getenv C' NAME Il), User: ", getenv( "USER"), Shell: ", getenv ( SHELL"), "Home directory: " , getenv ( "HOME")); 11

    Il prototipo di funzion si trova in stdlib.h; n l ca o v nga pa ata com param tro un tringa eh non ia una variabil di ambi nt vi n r tituito il puntator NULL.

    11.13

    Il compilatore C

    E i tono di.U renti compilatori C, un si t ma op rativo può fomim egu nt tab lla ono elencat olo alcun d Il po ibilità.

    Comando

    Quale compilatore c viene rlchllmltD

    cc

    Il ompilator C nativo fornito dal i t ma.

    ace

    Una d Il prim v r ioni d l compilator AN I C di un Mi roY t m. Il ompilator C/C++ di Borland in ambi nt int grato. Il ompilator C/C++ di Borland in ver ion a righ di ornando. Il ompilator GNU C di Fr Softwar Foundation. Il ompilator High C di M tawar . Il compilator Or gon C di Or gon Softwar . Il ompilator Quick C di Micro oft. Il compilator Turbo C di Borland in ambi nt int grato. Il compilator Turbo C di Borland in v r ion a righ di ornando.

    be bee

    gcc hc

    occ qc te

    tec

    In questo paragrafo vengono pre ntate alcun d 11 opzioni utilizzabili con il comand

    cc sui i temi UNIX. Simili opzioni vengono fornite anche da altri compilatori.

    lnput/output e sistema operativo 467

    un programma C è cont nuto in un ingoio fil , i supponga pgm.c, il comando

    cc pgm. c il codic C pr nte in pgm.c in codice ogg tto es guibil , che vien posto nel a.out (o in pgm.exe nel ca o di MS-DOS). n programma può s r e guito mediani) ornando a.out. Un ucc ivo comando cc riscriv il cont nuto del fil a.out. Forlu

    cc - o pgm pgm.c s guibile vien critto direttam nte nel fil pgm, enza intaccare il nt nuto di un eventual fil a.out esistente. n comando cc opera in tr fa i: vi ne richiamato prima il pr proce or , poi il coml t r e infin ilload r, o linker, eh riunisce le varie porzioni di codic p r co truire 11 guibile. L'opzione --c può e ere utilizzata per la sola compilazion , cioè per hiamar il pr proc s or il compilatore, ma non il load r. Qu ta opzion ' utile l a o in cui un programma ia critto u più file. i con ideri il gu nt comando:

    cc -c main.c tile1.c tile2.c n n vi ono rrori, v ngono cr ati fil oggetto corrispondenti che t rminano con il 1 o .o. P r cr ar un fil guibil è possibile utilizzare sia file .c che file .o. Per un error nel file main.c, dopo averlo corretto sar bb possibile il comando:

    -o pgm main.c file1.o file2.o

    .o invec dei fil .c riduce il tempo di compilazion Alcune opzloo.l utili del compilatori Compila olam nte, generando i fil .o corri pond nti. -c G n ra codic util al d bugg r. -g - o name Pone il codic guibil ottenuto in name. G n ra codic utile al profiler. -p Opzion "v rbo ", gen ra molt informazioni. -v - D name=def

    -E - I dir

    -M -0

    -S

    Pon all'inizio di ogni file .c la riga #define name de/. Richiama il pr proce or ma non il compilatore. Cerca i file #include n Ua dir ctory dir. Crea un makefil . C rea di ottimizzare il codic . G nera codic a mbler n i fil .s corri pond nti.

    468

    Capitolo 11

    11.14

    Utilizzo del profiler

    quicksorl pr Nel file main.c:

    #include #include #include #define void

    N

    50000

    quicksort(int *, int *);

    in t mai n (v o id) {

    int a[N], i; srand(time(NULL)); for (i = 0; i < N; ++i ) a[i] = rand() % 10000; quicksort(a, a + N - 1); for (i = 0; i< N - 1; ++i) if (a[i] > a[i + 1]) { printf( "SORTING ERROR - byel\n"); exit(1); }

    return 0; }

    P r ott n r un profilo d' on l' opzion -p:

    uzion d l programma, qu t'ultimo vi n

    cc -p -o quicksort mai n.c quicksort.c

    ompilat

    lnput/output e sistema operativo

    ivam nt

    i forni c il ornando:

    quicksort i' prov a la r azion d l fil mon. out; infin , p r ott n r un profilo d Il' il mando

    uzion ,

    i imparti

    prof quicksort diant il qual

    i otti n la

    %t ime

    cumsecs

    #ca l l

    ms/call

    46.9 16. 1 11 . 7 10.8 6.9 6.4 1. 4 0.0 0.0 0.0 0.0

    7.18 9.64 11.43 13.08 14. 13 15. 12 15.33 15.33 15.33 15.33 15.33

    9931 1 19863

    0.72 2460.83 0.09

    50000 19863

    0.02 0.05

    1 1 1 1

    0.00 0.00 0.00 0.00

    na me partition mai n

    ~) ind_pivot

    mcount rand =quicksort _monstartup _gettimeofday _profil - tsrand - ime tra l quali om qu to può tto al t mpo d' uzion .

    n tutt l fun zioni m

    Librerie

    11.15

    tandard i trova olitam nt in /usr/lib/libc.a, ma ro o olo in part , an h in altri fil . i di pon di

    ar t / usr /lib/libc.a hiav t vi n utilizzata p r tampar i titoli, o nomi, d i fil d lla libr ri . nti par hi titoli; p r ontarli ' po ibil fornir il comando:

    ar

    t

    t usr/lib!libc.a l wc

    470 Capitolo 11

    . om

    mpio

    #include #include void *gcalloc(int n, unsigned sizeof_something) {

    void

    *p;

    if ((p = calloc(n, sizeof_something)) == NULL) { fprintf ( stderr, "\ nERROR: calloc () failed - by e. \ n \ n- ); exit(1); }

    return p; }

    P r r ar la libr ria ' n p nd nti. u ivam nt

    ario ompilar prima i fil .c p r ott n r i fil .o omandi gu nti:

    i imparti ono i du

    ar ruv g_ lib. a gfopen. o gfclose. o gcalloc. o ranlib g_ lib. a

    cc - o pgm mai n. c file 1. c file2. c g_ lib. a

    ...

    rri

    lnput/output e sistema operativo 471

    in ui il programma ri hiami una funzion nza fornim r ata prima in g_lib. a poi n Ila li br ria tandard. l fil lo l funzioni n

    1.16

    typedef ty pedef

    Cronometrare il codice C

    long long

    clock t; time_t;

    u ti tipi v ngono utilizzati a loro volta n i prototipi di funzion . Di guito ono l nti i prototipi di tr funzioni utilizzat n Il iv routin di mi urazi n d i t mpi. clock t time t doubie

    clock(void); time(time_ t *p); difftime(time_t time1, time_t time0);

    guito un programma, il i t ma op rativo ti n tra ia d l t mpo di atilizzo d l pro or . La funzion clock () re ti tu i om val r la miglior appro mazion d l i t ma r lativa al t mp utilizzato da] programma ino a qu l punto. anità di lo k po ono variar a onda d Ila ma hina. La ma ro #defi ne

    CLOCKS_PER_SEC

    60

    /* dipendente dalla macchina */

    fin ita in time.h, può v nir utilizzata p r tra formar in ondi il valor c loc k (). Attenzione: n 11 v r ioni pr liminari di AN I C la ma r

    K_TCK.

    4 72

    Capitolo 11

    la funzione t ime ( ) restituisce il numero di secondi trascorsi dal l o gennaio 1970; sono possibili anche altre unità e altre date di partenza. Se il parametro puntatore passato a t ime ( ) non è NULL il valore restituito viene assegnato anche alla variabile puntata. Quanto segue è un tipico utilizzo della funzione

    srand(time(NULL)); che inizializza il generatore di numeri casuali. Se due valori prodotti da t ime ( ) vengono passati a di fftime (),il valore restituito, di tipo double, rappresenta la differenza espres-, sa in secondi. Viene presentato ora un insieme di routine di misurazione dei tempi utilizzabili per svariati scopi, tra cui lo sviluppo di codice efficiente. Queste routine vengono scritte nel file time_keeper.c. #include #include #include #define

    MAXSTRING

    100

    typedef struct { clock t begin_clock, save_clock; time_t begin_time, save_time; } time_keeper; static time_keeper

    tk;

    l* nota solo in questo file *l

    void start_time(void) {

    tk.begin_clock = tk.save_clock = clock(); tk.begin_time = tk.save_time time(NULL); }

    double prn_time(void) {

    char in t double

    s1[MAXSTRING], s2[MAXSTRING]; field_width, n1, n2; clocks_per_second = (double) CLOCKS_PER_SEC, user_time, real_time;

    user_time = (clock() - tk.save_clock) l clocks_per_second; real_time = difftime(time(NULL), tk.save_time); tk.save clock = clock(); tk.save=time = time(NULL); l* stampa in modo chiaro i valori trovati *l

    n1 = sprintf(s1, "%.1f", user_time); n2 = sprintf(s2, "%.1f", real_time); field_width = (n1 > n2) ? n1 : n2;

    lnput/output e sistema operativo 473

    printf("%s%*.1f%s\n%s%*.1f%s\n\nM, "User time: ", field_width, user_time, " seconds", "Aeal time: ", field_width, real_time, " seconds"); return user_time; }

    i noti che la struttura tk è e tema alle funzioni ed è nota solo in questo file. Essa viene utilizzata a copo di comunicazione tra le funzioni. Chiamando start_time( ), i valori r tituiti da clock () e t ime () vengono posti in tk. Chiamando prn_ t ime (),vengono utilizzati nuovi valori di clock () e t ime () per calcolare e stampare il tempo dell'utente il tempo reale trascorsi, e in t k vengono memorizzati nuovi valori. tempo dell'utente il tempo che il sistema riserva all'esecuzione del programma, mentre il tempo reale è qu Ilo misurato effettivamente da un orologio; su un sistema time-sharing essi possono ri ultare differenti. Nel file si trova anche la funzione prn_total_time( ), che non è stata presentata; a è simile a p rn _t ime ( ) , salvo che i tempi trascorsi vengono calcolati rispetto all'ultima chiamata di sta rt _t ime ( ) anziché rispetto all'ultima chiamata di una qualunque l Il tre funzioni. Poiché queste routine di misurazione verranno utilizzate in svariati programmi, vengono poste nella libreria di utility u_lib.a mediante i seguenti comandi:

    n

    cc -c time_ keeper. c; ar ruv u_lib.a time_keeper.o; ranlib u_lib. a l file d'intestazione u_lib.h vengono posti i prototipi delle funzioni in u_lib.a; è poi ibile includere questo file d'intestazione ove necessario. Viene ora mostrato come possano essere utilizzate queste routine. In certe applicazi ni è desiderabile una veloce moltiplicazione in virgola mobile; per decidere se sia m glio utilizzare variabili f lo a t o double è possibile fare uso del seguente programma.

    Nel file mult_time.c: l* Confronta i tempi della moltiplicazione tra float e double. *l #include #include "u_lib.h" #define

    100000000

    N

    l* cento milioni *l

    int main(void) {

    long float double

    i;

    a, b

    x,

    y

    = 3.333,

    c = 5.555; 3.333, z = 5.555;

    l* valori arbitrari *l

    4 74 Capitolo 11

    printf("Number of multiplies: %d\n\n", N); printf("Type float:\n\n"); start time(); for (I = 0; i < N; ++i)

    a

    = b

    * c;

    prn_time(); printf("Type double:\n\n"); for (i = 0; i < N; ++i)

    x

    = y

    * z;

    prn_time(); return 0; }

    u una v cchia macchina con un compilatore C tradizionale, i orpr nd ntem nte cop rto eh la moltiplicazion in ingoia pr ci ion · piu lenta di qu Ila in doppia precision ! N l C tradizional ogni float vi n automaticament promo o a double; foril risultato dipend dal t mpo con umato da tal conv r ion . u un'altra macchina con un compilator AN I C, i ' trovato, com si pr v d va, eh la moltiplicazion in ingoia pr ci ion · più v loc di circa il 30%. E gu ndo il programma u una Sun pare tation 10 si · ottenuto il guente ri ultato: Number of multiplies: 100000000 Type float: User time: 33.6 seconds Real time: 34.0 seconds Type double: User time: 33.5 seconds Real time: 33.0 seconds o · orprend n t ! Ci si a pettava eh la moltiplicazion di f lo a t ri parmia e cir a il30% d l tempo, ma co ì non ' . i o rvi che il t mpo r al indicato per i double · iill rior al t mpo dell'ut nt ; ciò è dovuto ali appro imazioni n l m todo di cronom traggio. Attenzione: per otten re mi ur accurat d 1 t mpo di moltiplicazion d Ila macchina ar bb nec ario stimar il co to g n ral in t rmini di t mpo d l programma (r lativam nte trascurabile) e il tempo con umato dai cicli for.

    11.17

    Utilizzo di make

    Manten re in un unico fil un programma di dim nsioni m dio-grandi eh debba s er ricompilato ripetutament ri ulta inefficiente co to o sia p r il programmatore che p r la macchina. Una trategia migliore consi te n l suddivid r il programma in più file .c, da compilarsi eparatamente quando n ce sario. L'utility make può es r utilizzata p r tenere traccia dei file sorgente e per fornir un opportuno ace sso ali librerie e ai file d'inte tazione a es e a ociate. Que ta potente utility · mpr disponibil ul

    lnput/output e sistema operativo 475

    ' i t ma UNIX, com aggiunta lo · spesso in MS-DOS; il uo utilizzo facilita sia la truzion eh la manut nzione d i programmi. Si upponga di criv r un programma formato da alcuni fil .h .c; in g nere u ti file v ngono po ti in una dir ctory a essi riservata. n comando make l gg un file, il ui nom di d fault ' makefile, cont nente una de crizion d Ile dipend nz tra i vari moduli, o file, eh ostitui cono il programma, insieme alle azioni da volg re. In parti" lar , so conti n le i truzioni per compilare e ricompilar il programma. Un file di u to tipo vi n chiamato makefile. P r mplicità, i con ideri un programma contenuto in du file di nome main.c e um.c, ciascuno dei quali include un fil d'intestazione di nom sum.h; i vuole eh il dic guibil d l programma venga scritto in un fil di nom sum. Di guito è pr ntato un mplic mak fil utilizzabile per lo sviluppo la manut nzion d l programma.

    sum: main.o sum.o cc - o sum main.o sum.o main.o: main.c sum.h cc - c main.c sum.o: sum.c sum.h cc - c sum.c La prima riga indica eh il fil sum dipende dai du fil oggetto main.o sum.o: sa è un s mpio di riga di dipendenza, e deve iniziare dalla prima colonna. La s conda riga indica com il programma d bba essere compilato n l ca o ia tato modificato almen uno d i fil .o; a · una riga d'azione, o di comando. Dopo una riga di dip ndenza po sibile p cificar anch più di un'azione. Una riga di dip ndenza l righ d'azion eh la guono co titui cono una regola. Attenzione: ogni riga d'azion d v iniziare con un caratt r di tabulazione, che ullo eh rmo appar com una quenza di spazi. Il comando make gu p r d fault la prima regola eh trova n l makefile; dei file dip nd nti in tal r gola po ono tuttavia es ere a loro volta dip nd nti da altri file se. ndo quanto pecificato da altr r gol , che vengono quindi guite prima; tali file ono a loro volta cau ar l' ecuzion di altre regol . La econda r gola d l mak file pre ntato sopra stabilisce eh main.o dipende dai fil main.c sum.h. S uno di qu ti du fil vie ne modificato, la riga d'azion motra che co a ' n c s ario far p r aggiornare main.o. Una volta che il makefile tat ·r ato, il programmatore può compilare o ricompilar il programma sum impart ndo il · mando

    make ·h fa sì eh make l gga il file makefile, crei un proprio albero di dipend l azioni nece sarie.

    d """""J" ......

    476 Capitolo 11

    Albero di dipendenze utilizzato internamente da make su m

    l

    (

    sum.o

    main.o

    (

    ( main.c

    sum.h

    main.c

    sum.h

    Alcune regole, tra cui la dipendenza di ogni file .o dal file .c corrispond nte, sono predefinite; il mak file prec dente ri ulta dunque quivalente al guente: sum: main.o sum.o cc - o sum main.o sum.o main.o: sum.h cc - c main.c sum.o: sum.h cc - c sum.c L'utility make riconosce alcune macro predefinit . Una di ess p nn tt di otten r un altro makefile equivalente: sum: main.o sum.o cc -o sum main.o sum.o main.o sum.o: sum.h cc -c $*.c In qu to caso, las conda regola tabili ce che i due file .o dipendono da sum.h. Modificando sum.h, sia main.o che sum.o devono essere ricr ati. La macro $*.c viene pansa in main. c nel caso debba essere creato main.o, m ntre viene espansa in su m. c nel caso debba es ere creato sum.o. Un makefile è costituito da una quenza di regole che specificano dip ndenz e azioni. Una regola inizia dalla prima colonna con una sequenza di nomi di file "target" eparati da spazi, eguita da due punti, seguiti a loro volta da una s ri di file che rappresentano i prerequi iti, chiamati anche file sorgente. Tutte l righe ucce ive che iniziano con un carattere di tabulazione rappresentano azioni, come la compilazione, che devono essere svolte dal sistema per aggiornare i file target I file targ t dipendono in qualche modo dai file prerequi iti, e devono ess re aggiornati quando questi ultimi vengono modificati. Nell'Esercizio 33 del Capitolo 8 è stato proposto un confronto tra qsort () e quicksort (),ed è stato scritto un programma che confronta i tempi d'esecuzione di tre routine d'ordinamento: quicksort (), presentata nel testo, qsort (), fornita dal sistema, e una routine qsort () presente u un altro sistema, della quale era disponibile il

    lnput/output e sistema operativo 477

    dic composto da circa 200 righe contenute in un unico file. Il programma che è stato lt nuto, includendo anche quest'ultimo file, consiste di circa 400 righe suddivise in

    ·inque fil . Di s guito è riportato il makefile utilizzato.

    Nel file makefile: # Makefile per confrontare routine d'ordinamento.

    BASE

    /home/blufox/base gcc CFLAGS - O - Wall EFILE $(BASE)/bin/compare_sorts INCLS - I$(LOC)/include LIBS = $(LOC)/lib/g_lib.a $(LOC)/lib/u lib.a /usr/local LOC OBJS main.o another_qsort.o chk_order.o compare.o quicksort.o $(EFILE): $(0BJS) ~echo "linking ~$(CC) $(CFLAGS) -o $(EFILE) $(0BJS) $(LIBS) $(0BJS): compare_ sorts.h $(CC) $(CFLAGS) $(INCLS) - c $*.c

    cc

    NALISI DEL MAKEFILE PER IL PROGRAMMA compare_sorts # Makefile per confrontare routine d'ordinamento. È po ibile in rire dei commenti in un makefile; e i iniziano con il carattere # e terminano alla fine della riga.

    BASE /home/blufox/base Questo ' un es mpio di definizione di macro; la forma gen rale di una definizione di macro ' la eguente:

    mac1·o_name

    =

    replacement_string

    Per convenzione, di olito i nomi di macro vengono scritti utilizzando lettere maiuscole, ma ciò non è obbligatorio. La stringa di ostituzione può contenere degli pazi, e un eventuale barra inversa \ alla fine della riga indica la prosecuzione della tringa sulla riga successiva. La home directory utilizzata su questa particolar macchina è 1home 1blufox, e la sottodirectory base è stata creata per con n r tutte le altre sottodirectory principali: essa può essere intesa come la "ba di l v ro". La macro BASE fa riferimento a questa sottodirectory.

    = gcc CFLAGS -0 -Wall Le prima macro specifica quale compilatore C venga utilizzato, in qu il compilatore GNU C. La seconda macro specifica le opzioni, o "f1 ", utilizzat cc

    4 78 Capitolo 11

    alla chiamata di gcc. L'opzione -0 richiama l'ottimizzator , m ntr l'opzion -Wali richiede la tampa di tutti i messaggi d'avvertimento. Scrivendo invec

    cc CFLAGS

    =

    gcc

    la stringa di ostituzione per CFLAGS sarebbe tata vuota. •

    EFILE

    $(BASE)/bin/compare_sorts

    La macro EFILE pecifica dove deve essere po to il fil

    guibil . La valutazione o chiamata, di macro avviene mediante un costrutto d Ila forma $(macro_name) eh produc la tringa, eventualmente vuota, as ociata a macro_name. La tring a ociata alla macro vi n anch d tta stringa di sostituzione. Dunque EFILE

    =

    $(BASE)/bin/compare_ sorts

    · equivalent a: EFILE •

    =

    /home/blufox/base/bin/compare_ sorts

    INCLS LIBS LOC



    OBJS

    main.o another_ qsort.o chk_ order.o \ compare.o quicksort.o La macro OBJS viene definita allo scopo di costituir l' lenco dei file ogg tto che s· trovano a destra del segno di uguale. Si noti l'impi go d Ila barra inv r a p r pr guire sulla riga successiva. Sebbene main.o sia stato scritto p r primo, s guit dagli altri in ordine alfabetico, l'ordine è irrilevant .



    $(EFILE): $(0BJS) @echo "linking ... " @$(CC) $(CFLAGS) - o $(EFILE) $(0BJS) $(LIBS) La prima riga è una riga di dipend nza; la seconda la t rza pecificano le azio · da intraprendere. Si presti attenzione al fatto eh le righ d'azione iniziano con u

    lnput/output e sistema operativo 479

    n

    ingoio carattere di tabulazione, che sullo sch rmo equivale a otto spazi bianchi. imbolo @ indica che la linea d'azione tes a non dev essere riscritta sullo schermo (v di E rcizio 28). Poiché una chiamata di macro ha la forma $ ( macro _name)

    il co trutto $ ( EFI LE) viene o tituito da $(BASE)/bin/compare_ sorts h a ua volta viene o tituito da: /home/blufox/base/bin/compare_sorts Analogam n te, $ ( OBJS) vi n o tituito dall'elenco dei file oggetto, co ì via. La riga di dipendenza pecifica quindi che il file es guibil dipend dai fil ogg tto. uno o più file ogg tto ri ultano modificati viene es guita l'azion pecificata. La s conda riga d'azion vi n pan a in: @gcc - O - Wall - o /home/blufox/base/bin/compare_ sorts main.o another qsort.o chk order.o compare.o quicksort.o /home/blufox/base/lib/g_ lib.a /home/blufox/base/lib/u_ lib.a

    \ \ \

    u qu ta pagina tampata il testo ' tato critto u quattro righe a causa dello spazio limitato, ma nella realtà o vi n generato come singola riga. Suggerimento: utilizzando p r la prima volta make è opportuno vitare l'u o del imbolo @; dopo av me compr so il comportamento e o può e ere utilizzato per vitar la tampa ullo h rmo d i comandi durante l'e ecuzione degli stessi. $(0BJS): compare_sorts.h $(CC) $(CFLAGS) $(INCLS) - c $*.c La prima riga è una riga di dip nd nza, che asserisce che i fil oggetto dipendono dal fil d'intestazion compare_sorls.h. il file d'inte tazione è tato aggiornato è nec sario aggiornar anch tutti i file ogg tto, e tale compito vi ne seguito mediante la riga d'azione. In UNIX una riga d'azione deve iniziare con un tab, in MSDOS a può anche iniziare con uno o più pazi. n costrutto$* nella riga d'azion ' una macro predefinita detta macro del nome del file di base. Essa viene espansa con il nome del file in corso di creazion , eludendone le e tensioni. Per e mpio, cr ando main.o, $*.c vi n pan a in main.c, e la riga d'azione divi n gcc

    - 0 - Wall

    - 1/usr/local/include

    -c

    main.c

    Alcun dipendenze sono predefinit nell'utility make; per esempio, ogni fil .o diP n de dal file .c corrispondente: ciò significa che se il file .c viene modifi t vien ricompilato per produrre un nuovo file .o; a causa di ciò occorr ri hiam r il link r per ricollegare tutti i file oggetto.

    480 Capitolo 11



    -1/usr/local/include Un'opzione d Ila forma -I dir specifica una ric rea dei file #include nella dk cto dir; tale opzion completa l'impiego dell libr rie. All'inizio dei fil .c eh co tituiscono il programma si trova la riga #include "compare_sorts.h" all'inizio di compare_sorl.h vi sono le eguenti righe: #include "g_ lib.h" #include "u_ lib.h" Que ti file d'inte tazione contengono i prototipi delle funzioni delle libreri eh ono tate co truite, e l'opzione - I indica al compilator dov si trovano tali file.

    L'utility make può ss r utilizzata per manten r programmi scritti in un qual ia si linguaggio, non solam nt in C C++; più in generale, make può ere utilizzata pe azioni a sociate. ogni tipo di progetto composto di file con dipendenz

    11.18

    Utilizzo di touch

    L'utility touch di ponibil in UNIX e spe o anch in MS-DO , e modifica la data · aggiornamento di un file. L'utility make decid quali azioni intrapr ndere confrontando le date di aggiornamento dei file, quindi touch può esser utilizzata per forzare il co portamento di make. Per presentare l'u o di touch si consideri il mak fil di cus o nel paragrafo pr c dente, con i relativi file .h, .c e .o. È po ibil a ociare al fil compare_sorls.h la da corrent fom ndo il comando:

    touch compare_ sorts.h Tale azione as oda al file una data di aggiornamento più r cente di quella di tutti i fil oggetto che dipendono da s o; e a questo punto vien impartito il comando

    make tutti i file .c vengono ricompilati e collegati per creare un nuovo file

    11.19

    guibile.

    Altri strumenti utili

    n sistema operativo fornisce parecchi strumenti utili al programmatore, alcuni dei quali, presenti su UNIX, sono riportati nel seguente elenco. Utility analoghe sono talvolta di sponibili anche in M~DOS.

    lnput/output e sistema operativo 481

    cb dbx

    diff gdb grep

    indent

    wc

    "Abb llitor "per codic tampa d l codic C. D bugger a liv Ilo di org nt ; il codice con l'opzion - g. tampa l righ in cui difi ri cono du fil . Debugg r GNU; n ce ario compilar il codice con l'opzion - g. C rea un patt rn in uno o più fil ; uno d i più importanti trum nti per i programmatori. Formattator di codic C con molte opzioni. Conta le Un , le parole e i caratteri in uno o più file.

    L'utility cb legge da stdin scrive su stdout. E sa non è molto pot nt ; per vedem il omportamento si impartisca il comando cb < pgm.c

    dov pgm. c ia un file non ben formattato. L'utility indent è più potent , ma a differenza di cb non empre è disponibile; per poterla utilizzare in modo efficiente ' opportuno l ggem il manuale in linea. Un debugger permette al programmatore di eseguire una riga di codice alla volta e verificare i valori dell variabili e delle e pre sioni dopo ogni pas o. Ciò risulta stremamente utile per scoprire perché il comportamento di un programma ia differente da quello che il programmatore si aspetta. E istono molti debugr, dbx non è uno dei migliori, tuttavia e so è disponibile sulla grande maggioranza d i ist mi UNIX. In MS-DOS i debugger ono prodotti eparati da aggiungere al sistema; Borland, Micro oft e altre case forniscono prodotti ccellenti. Strumenti come di//, grep e wc sono di natura g nerale, e v ngono utilizzati da variati utenti e non solo dai programmatori. Sebbene iano trumenti UNIX, e si sono p so di poni bili anche in MS-DOS, soprattutto grep, molto util per i programmatori. Infine, i ricordi che il C può e sere utilizzato insieme con altri trumenti ad alto liv llo, alcuni dei quali sono veri e propri linguaggi.

    awk

    bison csh fiex lex nawk

    peri sed yacc

    Linguaggio per la ricerca l'elaborazione di patt rn. Ver ion GNU di yacc. C h 11, programmabile. V rsione GNU di lex. Genera codice C per l'analisi le icale. Versione nuova e più potente di awk. E trazion di informazioni e rapporti. Editor di linea, riceve i comandi da un file. "Yet another compiler-compiler" (un altro compilator utilizzato per generare codice C.

    ompil t r ) ,

    482 Capitolo 11

    Di particolare importanza per i programmatori ono lex e yacc, o l corri pond nti utility GNU flex bison; vedi il Capitolo 8 di The UNIX Programming Environment di Brian Kemighan e Rob Pike (Engl wood Cliffs, N.].: Prentice-Hall, 1984). La Fr Software Foundation produce strumenti GNU, e eguibili u diver e piattaforme ott nibili via Intero t (Intero t forni ce ace i remoti ad altri strumenti) .

    11.20

    Riepilogo

    l.

    Le funzioni printf () scanf (),e le relative v r ioni eh operano con fil tringh , utilizzano specifiche di conversione scritt in una tringa di controllo che perm tte di utilizzar un l neo di parametri di lungh zza variabil .

    2.

    Il fil d'int tazion tandard stdio.h vien incluso nel ca o v ngano utilizzati dei fil . Es o contien le d finizioni d ll'identificator FILE (una struttura) i puntaton ai file stdin, stdout e stderr, oltr ai prototipi di par c h i funzioni per la g tione d i file ali definizioni d 11 macro getc () e putc (). La chiamata di funzione getc (i fp) l gge il carattere ucc ivo dal file puntato da i fp.

    3.

    Per aprir chiuder i file v ngono utilizzate ri pettivament fopen () fclose (); dopo l'apertura di un file i ru rim nti a so avv ngono tramite il puntator al fil .

    4.

    Un fil può es re immaginato com una quenza di caratt ri, ace s ibile equ nzialm nt o in modo dir tto. Legg ndo un caratter da un file, il i t ma operativo incr m nta di l la po izion dell'indicator sul fil .

    5. All'inizio dell' cuzion di un programma il i tema apr tr fil tandard, stdin, stdout stderr. La funzion printf () criv u stdout, m ntr la funzione scanf () legg da stdin. I file stdout stderr ono di olito a o iati allo hermo, il fil stdin è a sociato alla ta ti ra. Tali a sociazioni po ono r modificat utilizzando le redirezioni. 6.

    I fil sono una risor a limitata; il ma imo num ro di fil che po ono e r aperti contemporaneamente è dato dalla costante simbolica FOPEN_MAX in stdio.h. Questo num ro dipend dal sistema, e generalmente es o ' compr o tra 20 e 100; è compito del programmatore tenere traccia di quali file siano ap rti. All'uscita dal programma tutti i file aperti vengono chiusi automaticamente dal i t ma.

    7. Anch non ono part di ANSI C, alcun funzioni che utilizzano i d crittori dei file risultano disponibili u molti sistemi. Es e richiedono buffer definiti dall'utente. I de rittori dei fil stdin, stdout e stderr ono rispettivamente O, l e 2. 8. È possibile seguire un comando del istema operativo dall'interno di un program"l ma mediante la chiamata system ().

    lnput/output e sistema operativo 483

    In MS-DOS l'istruzion system( "dir");

    provoca la tarnpa ullo chermo di un elenco di directory e file. 9.

    In UNIX è po sibile utilizzar la funzione popen () per comunicare con il sist ma operativo. Si con ideri il guente comando: wc •.c

    E o tampa ullo chermo il numero di parole di ogni fil .c pres nte nella dir ctory corr nte. Per accedere a questa sequenza di caratt ri dall'interno di un programma, il programmatore può scrivere: FILE *ifp; ifp = popen( "wc *.c", "r");

    È n ce ario chiudere con pelose () i file aperti con popen (). 10.

    Molti sistemi operativi forniscono un'utility p r creare e gestire librerie. In UNIX l'utility è chiamata archiver, viene richiamata con il comando ar. ln MS-DOS qu sta utility, chiamata Hbrarian, ' fornita come prodotto da aggiungere al ist~ ma.

    11.

    L'utility make può e r utilizzata per t n r traccia dei file org nt l'ace o corretto ali libr ri e ai fil d'int tazion a ociati.

    11.21 l.

    fornir

    Esercizi

    Ri criv t il programma dbl_space d l Paragrafo 11.5 in modo che so ric va il nom del fil di input com parametro ulla riga di comando criva su stdout. Fatto ciò, risulta po ibile utilizzare il comando dbl_ sp infile > outfile

    p r raddoppiar la paziatura d l cont nuto di qualunqu co a si trovi in infile, tampando l'output su outjile. Poiché il programma ' pensato per utilizzare le r dir zioni, ' sen ato utilizzare fprintf ( stderr, ... ) invece di printf ( ... ) in prn_ info (). Se su stdout viene critto un m ssaggio d'errore, esso ri ult rà r diretto e l'utente non lo vedrà sullo chermo. n imbolo >viene utilizzato per r dirig r tutto ciò che viene scritto su stdout, ma non ha alcun effetto u i h vi ne critto u stderr. Scriv te il programma in due modi, cio ' con i m gi d' rrore critti prima su stderr poi su stdout. Provate le du v r ioni d l pr gramma per comprendern i diversi effetti.

    484 Capitolo 11

    2.

    Ri criv t il programma dbl_space del Paragrafo 11.5 in modo che utilizzi un'opzio-

    ne una riga di comando come -n, dove n può s r l, 2 o 3. n ' ll'output deve aver la spaziatura ingoia, vale a dir eh se i tono due o più n wlin con cutivi nel file di input essi devono es er so tituiti da un unico n wlin in quello di output Se n è 2 il file di output deve aver paziatura e attam nt doppia, cio ' uno o più n wline contigui nel file di input d vono e er o tituiti da una coppia di n wlin n l fil di output. Se n ' 3 il file di output d v av r paziatura attam nte tripla. 3.

    Scrivet l funzioni getstring () e putstring (). La prima funzione d ve utilizzar un puntatore a file, per e empio i fp, la macro getc () p r l gg re una tringa dal file puntato da i fp. La seconda funzione d ve utilizzar un puntator a fil , per e mpio ofp, e la macro putc () per criver una tringa ul fil puntato da ofp. rivet un programma per provare levo tr funzioni.

    4.

    Scrivet un programma per num rar le righ in un file. n nom d l file di input er pa ato al programma come param tro ulla riga di comando, il d v programma d ve crivere su stdout. Ogni riga d l fil di input d v ritta ul file di output pr ceduta dal r lativo numero e da uno pazio.

    5.

    N ll'Appendice A, Paragrafo A12, vien d scritta, fra l'altro, la funzion ungete (); dit , dopo eh tre caratt ri ono tati l tti da un file, è po ibil riporre i tre caratteri n l fil m dian t ungete (). rivete un programma di prova.

    6.

    Scriv t un programma eh tampi sullo chermo 20 righ di un file alla volta. nom d l fil di input d v r dato com param tro ulla riga di comando. programma dev stampare le 20 righe succe iv dopo che tato premuto il tasto return; i tratta di una ver ione elem ntar dell'utility more di UNIX.

    7.

    Modificate il programma critto p r l'E rcizio 6 fac ndo i eh o tampi uno o più fil dati com param tri ulla riga di comando, permett ndo inoltre l'utilizzo di un'opzione -n, dov n rappr nta un intero po itivo che pecifica il num ro di righ da tampar ogni volta. In MS.DOS il comando per cane llar lo schermo si chiama cls; in UNIX clear. Provate uno di que ti comandi ul vo tro istema per compr nderne il comportamento, e utilizzate system ( "cls") o system ( "clear M) n l vo tro programma prima di stampare sullo sch rmo ciascun gruppo di righe.

    8.

    La funzione di libreria fgets () può venire utilizzata per legger da un file una riga alla volta. Consultate la part relativa a questa funzione nell'Appendice A, Paragrafo A.12, poi scrivete un programma di nome search per la ricerca di pattem. n comando

    n n

    search hello my_file deve far sì che la tringa hello venga ricercata nel file my_file, e che ogni riga contenente il pattern venga stampata; si tratta di una ver ione elementare di grep. Suggerimento: utilizzate il seguente codice.

    lnput/output e sistema operativo 485

    char line[MAXLINE], *pattern; FILE *ifp; if (argc ! = 3) { }

    if ((ifp = fopen(argv[2], "r")) == NULL) { fprintf(stderr, "\ nCannot open %s\n\n", argv[2]); exit(1); }

    pattern = argv[1]; while (fgets(line, MAXLINE, ifp) != NULL) { if (strstr(line, pattern) != NULL)

    9. lO.

    Modificate la funzione scritta per l'Esercizio 8 in modo che in presenza dell'opzione -n venga stampato anche il numero di riga. Compilate il eguent programma e ponetene il codice eseguibile in un file di nome

    try_me: #include int main(void) {

    fprintf ( stdout, "She sells sea shells \n"); fprintf ( stderr, " by the seashore. \n M); return 0; }

    E guite il programma per comprenderne il funzionam nto. Co a uccede redirig ndo l'output? Provat il comando try_ me > tmp d aminate poi il contenuto d l file tmp. In UNIX dovr t provare anche il comando: try_ me > & tmp Ciò fa ì eh anch l'output critto su stderr venga r dir tto. Esaminate il con nuto di tmp, potre t r tame orpresi! l l.

    Scrivete un programma di nome wrt_rand che crei un file di numeri uniform m nt distribuiti. Il nome del file deve poter essere in rito in maniera int rattiva. Il programma dovr bbe utilizzar tre funzioni, e di seguito ne è mostrata la prima. void get_ info(char *fname, int *n_ ptr) {

    printf("\n%s\n\n%s",

    486 Capitolo 11

    "This program creates a file of random numbers.", "How many random numbers would you like? "); scanf("%d", n_ ptr); printf("\nln what file would you like them? "); scanf("%s " , fname); }

    Dopo che questa funzione è stata chiamata in mai n () potete scrivere: ofp = fopen(fname, "w"); Tuttavia il file specificato potrebbe già esistere, e in questo caso una sovrascrittura provocherebbe la perdita del contenuto corrente. In questo esercizio si richiede di scrivere del codice che eviti questo problema: in caso di esistenza del file l'utente deve esserne informato, e il programma deve chiedere il permesso di riscrivere il file. Utilizzate come seconda funzione del programma la segu nte versione "prudente" di fopen (): FILE *cfopen(char *fname, char •mode) {

    char FILE

    reply [ 2] ; *fp;

    if (strcmp(mode, "w") == 0 && (access(fname, F_OK) -- 0) { printf( " \nFile exists. Overwrite it? "); scanf("%1s", reply); if (*reply l= 'y' && •reply l= 'Y') { printf("\nByel\n\n"); exit(1); }

    }

    fp gfopen(fname, mode); return fp; }

    Consultate nel Paragrafo A16 dell'Appendice A la parte riguardante access ().La terza funzione · gfopen (), la versione personalizzata di fopen () presentata nel Paragrafo 11.6. Suggerimento: per scrivere in maniera ordinata i numeri uniformemente distribuiti utilizzate il seguente codice: for (i = 1; i< = n; ++i) { fprintf(ofp, "%12d", rand()); i f ( i % 6 == 0 Il i == n) fprintf(ofp, "\n"); }

    12.

    L'accesso alle stringhe è differente dall'accesso ai file: quando un file viene aperto, l'indicatore di posizione nel file tiene traccia della posizione raggiunta nel file, mentre non esiste un meccanismo analogo per le stringhe.

    lnput/output e sistema operativo 487

    Scrivete un programma contente le seguenti righe: char int FILE

    c, s[] = "abc", *p= s; i; *ofp1, *ofp2;

    afp1 fopen ( • tmp1 ", "w") ; ofp2 fopen ( • tmp2", "w") ; for (i = 0; i < 3; ++i) { sscanf ( s, "%c" , &c) ; fprintf ( ofp1 , "%c", c) ; }

    for (i = 0; i < 3; ++i) { sscanf (p++, "%c" , &c); fprintf(ofp2, "%c", c); }

    Dite che cosa si ottiene in tmp1 e in tmp2, e fomite una spiegazione.

    13.

    In questo esercizio viene esaminato un tipico utilizzo di sscanf (). Supponete di scrivere un importante programma interattivo che chieda all'utente di inserire un intero positivo; per proteggersi dagli errori, l'input inserito dall'utente può essere posto in una stringa elaborabile con il codice seguente: char int

    line[MAXLINE]; errar, n;

    do { printf("Input a positive integer: "); fgets(line, MAXLINE, stdin); error = sscanf(line, "%d", &n) l = 1 Il n> >>





    After try_me: status = 0 A prima vista questo risultato può sorprendere, ma occorre ricordare che il valore di status cambia a ogni comando. Pertanto il valore di status scritto in questo caso è quello corrispondente al comando echo che ha stampato - - -, ed è quindi O e non 3. >> >>

    Input an integer: 7 Value being returned: 7

    After try_me again: val = 7 Dopo la nuova chiamata di try_me viene inserito 7. Subito dopo l'uscita da try_me, la variabile di shell val viene utilizzata per ricordare il valore corrente $status. Nello script di shell le righe corrispondenti sono: try_me set val = $status e c ho echo After try_me again: val

    = $val

    Il ciclo while nello script di shellgo_try permette all'utente di effettuare degli esperimenti. È possibile, per esempio, osservare come inserendo un valore intero n il valore tampato sia n mod 256, e in particolare come -l produca 255. Lo stato di un programma è quindi un intero compreso tra O e 255. Quando mai n ( ) restituisce un valor alla . h Il, essa può utilizzarlo per propri scopi. Sebbene in questo paragrafo la discussione sia stata svolta riferendosi alla C h 11 di UNIX, essa può essere estesa in maniera simile alle altre shell di UNIX e a M DO .

    520 Capitolo 12

    12.8

    Riepilogo

    l.

    Un processo concorrente è costituito da codice che viene eseguito simultaneamente al codice che lo ha richiamato. In UNIX, mediante fork () è possibile creare un proce so figlio che è una copia del processo padre, salvo che il figlio ha un numero identificativo di processo proprio e unico. Se la chiamata a fork () ha successo, al figlio viene restituito Oe al padre il numero identificativo del processo figlio; in caso di insuccesso viene restituito -l.

    2.

    Sia in MS.DOS che in UNIX, è possibile sostituire un processo con un altro utilizzando una funzione della famiglia ex e c ... ();non c'è ritorno al processo padre. In MS.DOS è disponibile la famiglia spawn ... (),con la quale è possibile ritornare al padre; il suo impiego è paragonabile, in un certo senso, all'utilizzo combinato di fork () ed exec ... () in UNIX.

    3.

    In UNIX, la chiamata di sistema pipe ( pd), dove pd è un array bidimensionale di int, crea un meccanismo per la comunicazione tra processi chiamato "pipe". Dopo che una pipe è stata aperta, mediante delle chiamate a fork () è possibile creare processi che comunicano tramite essa utilizzando le funzioni rea d () e wri te ().

    4.

    Nella libreria standard è disponibile la funzione signal (),che permette di associare a un segnale un gestore di segnale. Il gestore del segnale può essere una funzione scritta dal programmatore in sostituzione dell'azione di default del sistema. In presenza del segnale, il controllo del programma passa al gestore del segnale. L'insieme di segnali gestiti dal sistema operativo è definito come macro in signal.h. Questo insieme dipende dal sistema, ma alcuni segnali sono presenti sia in MSDOS che in UNIX.

    5.

    Il problema dei cinque filosofi è un modello standard di sincronizzazione di processi concorrenti che condividono risorse. n problema consiste nello scrivere un programma, con processi concorrenti che rappresentano i filosofi, in cui ogni filosofo riesca a mangiare, cioè a utilizzare risorse condivise, abbastanza spesso.

    6.

    Un semaforo è una particolare variabile che permette operazioni di wait e signal. La variabile è una locazione particolare per memorizzare valori non specificati. L'operazione di wait attende un oggetto e lo rimuove; l'operazione di signal aggiunge un oggetto. L'operazione di wait blocca un processo fino a quando esso può operare la rimozione; l'operazione di signal permette la ripresa di un processo eventualmente bloccato.

    7.

    È possibile costruire matrici di qualunque dimensione a partire dal tipo puntato re a puntatore a double. Tale puntatore può essere passato a funzioni progettate per lavorare con matrici di qualunque dimensione. Questa è un'idea importante per ingegneri e scienziati.

    Esercizi

    12.9 l.

    Modificate il programma presentato nel Paragrafo 12.1 in modo che contenga tr copie della riga: value = fork(); L'output del programma è nondeterministico. Spiegate che cosa significa. Suggerimento: eseguite il programma più volte.

    2. Se fork () fallisce non viene creato alcun processo figlio e viene restituito -l. Scrivete un programma contenente il seguente codice: #define

    N

    3

    for (i= 1; i 0) printf("%2d: Hello from parent\n", i); else printf("%2d: ERROR: Fork did not occur\n", i); }

    Quanto deve essere grande N sul vostro sistema affinché venga stampato il messaggio d'errore? 3.

    Nel Paragrafo 12.2 è stato presentato un programma che mostra come un processo possa sostituirne un altro. Modificate tale programma; cominciate col creare un altro file eseguibile di nome pgm3 che tampi la data corrente. Fomite questo programma come ulteriore scelta per la sostituzione del processo padre nel programma che modificate.

    4.

    Sul vostro sistema è disponibile il comando fortune? Se sì, localizzate il suo codice eseguibile. Prendete poi uno dei vostri programmi e inserite del codice del tipo: if (fork() == 0) execl( "/usr/games/fortune", "fortune " , 0); Che cosa succede?

    5. Scrivete un programma che utilizzi n proces i concorrenti per moltiplicare due matrici n x n. 6.

    Compilate due programmi in eseguibili chiamati prog l e prog2. Scrivete un programma che li esegua concorrentemente utilizzando due chiamate a fork () richiamando execl () per sostituire i due eseguibili.

    522 Capitolo 12

    7. 8.

    dal i t ma. Prova

    il

    gu nt programma u

    rvam i vari comportam nti: #i nclude #include #include

    l* contiene la definizione di HUGE_VAL *l

    int main(void) {

    x

    double

    = HUGE_ VAL,

    y

    HUGE_VALj

    signal(SIGFPE, SIG_ IGN); printf("Ignore signal: x* y = %e \ n " , x * y); signal(SIGFPE, SIG_DFL); printf( "Default signal: x* y = %e \ n " , x* y); return 0; }

    l .

    or o di qu to capitolo · tato mo trato om , a partir da una variabil di tipo double **, ia po ibil co truir in mani ra dinamica una matric pa aria a funzioni. È proprio n c ario eh la co truzion . v nga fatta in mani ra dinami? n id rat il gu nt codic : in t double double

    i•

    '

    *a[3], det; trace(double **

    '

    in t);

    for (i = 0; i < 3; ++i) a[i] = calloc(3, sizeof(double)); tr

    = trace(a,

    l* prototipo

    di funzione *l

    l* riempie

    la matrice a *l

    3);

    L'array a vi n pa ato com param tro, ma dal prototipo di funzion ri ulta n gnala d i probl c ario un param tro di tipo double * *. n vo tro ompilator rrù? pi gat n il p r h . 11.

    N gli anni '70, prima eh il C fo ampiam nt di ponibil , v niva pe so utilizzato il linguaggio PL/1, diffu o ui mainfram IBM. N i manuali d l PL/1 vi n ot-

    Applicazioni avanzate

    tolin ato mpr il fatto eh il compilator può r ar v ttori matrici eh parton da un qualunqu indie . P r mpio, con una dichiarazion d Ila forma

    int

    automobiles[1989 : 1999]; /*dichiarazione in stile PL / 1 */

    12.

    matrici.

    void fill matrix(double **a, int m, int n, int N) { 13.

    for (k = 1; k > n; assert(cin && n>= 0); for (i = 2; i

    ++ (prefisso)

    ++ (postjWo)

    -- (prefisso)

    + (unario) - (unario) • l %

    da sini tra a destra

    -- (po tjWo)

    sizeof

    l

    & (indirizzo)

    (type) • (dereferenziazione)

    da sinistra a destra da sinistra a destra

    + >

    da sinistra a destra

    >=

    da sinistra a destra

    l=

    da sinistra a destra

    &

    da sinistra a destra da sinistra a destra &&

    da sinistra a destra

    Il

    da sinistra a destra

    ?:

    da destra a sinistra +=

    l=

    ·=

    , (operatore virgola)

    /=

    %=

    >>=

    , 131, 132-134 maggiore di o uguale a>=, 131, 132-134 meno-, 74, 79 minore di , 5~51 register, 68, 183,193,196,577 regole, 476 release_matrix_space(), 513 remo ve ( ) , 464, 587 rename, 49 rename (), 587 report_and_ tabulate(), 315 reset (), 382, 384 return,8,31, 68,161,178-179 reverse (), 544 rewind ( ) , 581, 585 ricerca, funzioni di, 588 Richard , M., l ricorsione, 199-207, 401 riga di comando, 42 di dipendenza, 475, 479 numero di, 345 Ritchie, D., l running_sum, 24

    s %s, formato , 442 s _to_ l (), 402 alti, 157, 576 scalar, 251 scanf(), 16-19,446,586 cansion , campo di, 448 scores, 33 scorrimento, operatore di, 298-299

    sea, 6, 9 sed utility, 481 s gnali, gestione dei, 576-577 selection_by_machine(), 313 selection_ by_player(), 313 s maforo, 504, 507 set, 466 setbuf (), 582 setjmp(), 576

    648 Indice analitico

    setjmp.h, 569, 576

    sqrl_p ow, 113

    setlocale (), 573 setvbuf (), 582 shell, 516, 519 short,68,97-98, 104,116 shuff le ( ) , 375 SIG_DFL, 501 SIG_IGN, 501 SIGFPE, 501 SIGILL, 501 SIGINT,501 signal(),501, 506,576 signal.h, 501, 569, 576 signed,68,97, 116,611 signed char,97-98, 102 signed int, 97 signed long int,97 signed short int,97 SIGSEGV, 501 sin(), 112,263,574 sinh(), 574 sintassi, 61, 64 sintassi, sommario, 605--609 sistemi operativi, 47-51 sizeof,68, 108-109,232,399 sleep (), 604 small_integer structure, 380 sorl,256, 334,336-337 sorl.h, 253, 335 sorl_words, 254, 256 sort_words (), 256 sospensione dei programmi, funzioni, 604 sostituzione di un processo, 603-604 sottoalberi, 424 spawn ... (), 497-498,604 spawnl ( ) , 498, 601 spazi, 62, 63-64, 72 spazio dei nomi, 308, 363, 618 spostamento del puntatore, 234-235 sprintf(), 450-451 sqrt (), 112, 575

    square, classe (C++), 542 srand(),83,374, 589 srand48 (), 91 sscanf () ,450-451, 586 stack polacco, 415-417, 419 stack struttura, 382-383, 411, 415 stack, 382-383, 386, 411-413, 543 stack, class (C++), 543 stack, come lista, 410 stampa dei caratteri, 99 start_time(), 472 static,68, 183,193,194,197-199 stato, 515, 515-516, 519 stdarg.h, 569, 578, 585 __STDC__, 342, 616 stddef.h, 329, 569, 578 stderr,450,580 stdin,450, 580 stdio.h, 6-8, 13, 15, 16, 34, 80, 109-110, 338-339, 569, 579 stdlib.h, 25, 82, 232, 330, 430, 569, 587 stdout,450, 580 strcat(),243, 246,595 strchr (), 595 strcmp(),244, 595,597 strcoll(),595, 597 strcpy(),39, 244,595 strcspn (), 595 stream, 581 stderr,450,580 stdin,450, 580 stdout,450, 580 stream 1/0 (C++), 528 strerror(), 571,595 strftime (), 600--601 string classe (C++), 533, 535, 537 string.h, 38, 243, 569, 594 string_to_list(), 401 stringhe,32,34,41,241-243 costanti stringa, 8, 71, 241, 612 di controllo, 11, 16

    Indice analitico 649

    funzioni, 243-244 funzioni di conversione, 590 gestione,fiuucioni per,595 letterale stringa, 71 lunghezza, 241 multibyte, funzioni, 593 nulle, 242 specifica di conversione, 11, 17, 443, 449 trasformazione in, 342-343, 617 vuote, 39 strlen(), 244,596 strncat (), 595 st rncmp (), 595 st rncpy (), 596 strpbrk(), 596 strrchr( ), 596 s t r s pn ( ) , 596 strstr (), 597 strtod(), 590 strtok(), 597 strtol() , 591 st rtoul (), 592 struct,68,361-363 strutture, 361-379, 397-431, 616 abc, 380-381 accesso a, 364, 370 autoreferenzianti, 397 card, 361-363, 372 come parametri, 369 complex, 367 data, 415 date, 364 dept,369 elem,415,421 flower,378 fruit,363,379 home_address, 371 inizializzazione, 371, 613 lconv, 572 linked_list, 399 list,397

    liste, 397-431 membro, 364 node,425,429 pcard, 379 puntatori a, 367 small_integer, 380 stack,383,411,415 student, 364-365 tm, 598 vegetable,363,379 strxfrm( ), 597 student eia (C++), 540 student truttura, 364-365 Student,cm Oava), 560 StudentTest, eia Oava), 560 sum, 62 sum (), 200, 230 sum_array (), 234 sum_square, 262 swap(),226, 257,375 switch,68, 160,612 system(), 463,589

    T tab \t, 100 tabella di verità, 169 table_of_powers, 181-182 tabulatore verticale \v, 100 t an(), 112, 574 t an h(), 574 tastiera, input mediante la, 18 tbl_of_powers, 184 tec, comando, 47, 49 template (C++), 543 template (schema), 307, 362, 377 tempo, funzioni, 598 temporanei, file, 456 text editor (editor di testo), 5, 47-49 Thompson, K, l throw (C++), 544 time(),89,374,471,473,599

    650

    Indice analitico

    time.h, 311, 374, 569, 598 time.h, libr ria, 471 __ TIME __ , 342, 616 tipi, 611 aritm tici, 98 a tratti, 3 2 char, 68,97- 99,101-102 class (C++), 533 class Oava),553,555, 557 da i ( ++) , 533 d finiti dall'ut nt , 362, 534 d rivati,219, 362,377 di dati d rivati, 362 double,68,97-9, 105,611 enum,68, 612 num rativi, 116, 306-307, 612 float,68, 97- 9,105 fondam ntali, 95-121 funzioni, 176-177 int,68, 97-98,102-104,108 int ri,9, 102- 104,108 ]ava, 555 long, 6 , 97-98, 104-105 long double ,97-98, 105,611 long float,611 param t r li t, 26 puntatori g n ri i, 224, 410 qualificator , 15, 274 r ali (in virgola mobil ), 16, 9 , 105 short, 6 ,97-9, 104,116 signed,6 ,97, 116,611 signed char,97-98, 102 signed int,97 signed long int,97 signed short int,97 struct, 6 , 361-363 template (C++),543 union, , 377 unsigned, 6 , 97- 9 , 104-105, 116 unsigned char, 97- 98, 102 uns · gned int, 7 uns ned long, 7-98, 105

    unsigned long int,97 unsigned short,97-9 unsigned short int,97 void, 6, 176, l 0,611,615 void*, 224,410 tlib, ornando, 469 tm truttur , 59 tmpfile(), 582,593 tmpnam(), 464,5 2 toascii (), 339 tok n, 61-62, 71 tolower(), 339,570,617 top(), 3 3, 412 top-down, prog ttazion , 182 touch utility 4 O toupper(),339, 571,617 towers_of_hanoi, 205-206 tridim n ionali, array, 250 try ( ++), 544 try_me, 516 try_qsorl, 330 Turbo , parol hiav , 68 typedef,68, 10,251,329,362,364, 577

    u %u, formato, 442 #undef, 340 und r or _, 68-69 ungete(), 5 5 union, 6 , 377 unioni, 376-377, 613, 616 unità di traduzion , 4 UNIX, 1-2, 5, 47, 49-50, 115 unlink (), 587 unpack(), 304 unsigned, 68,97-9,104-105,116 unsigned char,97-9, 102 unsigned int, 97 unsigned long,97-98, 105 unsigned long int,97

    Indice analitico 651

    unsigned short,97-98 unsigned short int,97 upper_case, 464-465 u cita dai programmi, funzioni, 593-594 ut nt , tip d finit da, 362, 534 utility, 4 540, 5 7 awk 4 l bison, 4 l cb l csh 4 l dbx, 4 l diff, 4 l jlex 4 l gdb, 4 l grep, 4 l indent, 4 l lex, 4 l make, l 9, 301 474-475 nawk, 4 l perl 4 l sed, 481 touch, 5 O WC, 481 yacc 4 l

    v \ v, tabulator v rtical , 100 va_arg () , 577 va_ end(), 577 va_ start () 577 va_ sum(), 578 valori porchi, 199, 221 valutazion orto ircuitata, 139 variabili, 9, 11, 95 199, 222 a gr azion di, 361 allo azion di m moria, 192 bool an , 149-150 la i di m morizzazion , 193-197 di ambi nt , 465-466 t m , 177

    gl bali, 177, 193-196, 198 id ntifi at ri di, 618 Java 555 locali, 177, 193 param tri 577 puntat ri, l tati h m , 197 trin 2 l tipi l vi ibilità l l vegetable truttura 363,379 v r , l , l 1- 1 2 v ttor , 251, 511 Vedi array 2 vfork () 3 vfpr intf (), 5 5 vi, ditor, 48 vi, xt ditor, 5 vi ibilità, l 7 blocco, 190-191 funzioni, 199 r gol , 190 r trizion , 197 vi ita di alb ri binari, 425, 430 void, 6, 176,179,611,615 void*, 224,410 v o id *, 611 volatile, 68, 274-275, 613

    w wait, 504 wait (), 5 6 wc utility, l wchar_t, 329 wcstombs (), 593 wctomb (), 593 wgcd, la Qava) , 561 while, 21, 6 , 147-148, 152 wri te(), 602-603

    wrt, l 8 wrt (), 240

    652 Indice analitico

    wrt_ array (),

    234

    wrl_bkwrds, 201- 202 wrt_ info (), 188 wrt_ i t(), 201 wrt_words (), 258

    x %x, formato, 442

    y yacc utility 4 l

    z z ro, 37,39, 100,132,193,241- 242